--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2013 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.browsing.ui.swt;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.Deque;\r
+import java.util.HashMap;\r
+import java.util.HashSet;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Set;\r
+import java.util.WeakHashMap;\r
+import java.util.concurrent.CopyOnWriteArrayList;\r
+import java.util.concurrent.ExecutorService;\r
+import java.util.concurrent.ScheduledExecutorService;\r
+import java.util.concurrent.Semaphore;\r
+import java.util.concurrent.TimeUnit;\r
+import java.util.concurrent.atomic.AtomicBoolean;\r
+import java.util.concurrent.atomic.AtomicReference;\r
+import java.util.function.Consumer;\r
+\r
+import org.eclipse.core.runtime.Assert;\r
+import org.eclipse.core.runtime.IAdaptable;\r
+import org.eclipse.core.runtime.IProgressMonitor;\r
+import org.eclipse.core.runtime.IStatus;\r
+import org.eclipse.core.runtime.MultiStatus;\r
+import org.eclipse.core.runtime.Platform;\r
+import org.eclipse.core.runtime.Status;\r
+import org.eclipse.core.runtime.jobs.Job;\r
+import org.eclipse.jface.layout.GridDataFactory;\r
+import org.eclipse.jface.layout.TreeColumnLayout;\r
+import org.eclipse.jface.resource.ColorDescriptor;\r
+import org.eclipse.jface.resource.DeviceResourceException;\r
+import org.eclipse.jface.resource.DeviceResourceManager;\r
+import org.eclipse.jface.resource.FontDescriptor;\r
+import org.eclipse.jface.resource.ImageDescriptor;\r
+import org.eclipse.jface.resource.JFaceResources;\r
+import org.eclipse.jface.resource.LocalResourceManager;\r
+import org.eclipse.jface.viewers.CellEditor;\r
+import org.eclipse.jface.viewers.CellLabelProvider;\r
+import org.eclipse.jface.viewers.ColumnViewer;\r
+import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;\r
+import org.eclipse.jface.viewers.ColumnViewerEditorActivationListener;\r
+import org.eclipse.jface.viewers.ColumnViewerEditorDeactivationEvent;\r
+import org.eclipse.jface.viewers.ColumnWeightData;\r
+import org.eclipse.jface.viewers.ComboBoxCellEditor;\r
+import org.eclipse.jface.viewers.DialogCellEditor;\r
+import org.eclipse.jface.viewers.EditingSupport;\r
+import org.eclipse.jface.viewers.ICellEditorValidator;\r
+import org.eclipse.jface.viewers.IPostSelectionProvider;\r
+import org.eclipse.jface.viewers.ISelection;\r
+import org.eclipse.jface.viewers.ISelectionChangedListener;\r
+import org.eclipse.jface.viewers.ISelectionProvider;\r
+import org.eclipse.jface.viewers.ITreeContentProvider;\r
+import org.eclipse.jface.viewers.ITreeViewerListener;\r
+import org.eclipse.jface.viewers.SelectionChangedEvent;\r
+import org.eclipse.jface.viewers.StructuredSelection;\r
+import org.eclipse.jface.viewers.TextCellEditor;\r
+import org.eclipse.jface.viewers.TreeExpansionEvent;\r
+import org.eclipse.jface.viewers.TreeSelection;\r
+import org.eclipse.jface.viewers.TreeViewer;\r
+import org.eclipse.jface.viewers.TreeViewerColumn;\r
+import org.eclipse.jface.viewers.Viewer;\r
+import org.eclipse.jface.viewers.ViewerCell;\r
+import org.eclipse.swt.SWT;\r
+import org.eclipse.swt.events.DisposeEvent;\r
+import org.eclipse.swt.events.DisposeListener;\r
+import org.eclipse.swt.events.FocusEvent;\r
+import org.eclipse.swt.events.FocusListener;\r
+import org.eclipse.swt.events.KeyEvent;\r
+import org.eclipse.swt.events.KeyListener;\r
+import org.eclipse.swt.events.MouseEvent;\r
+import org.eclipse.swt.events.MouseListener;\r
+import org.eclipse.swt.events.SelectionListener;\r
+import org.eclipse.swt.graphics.Color;\r
+import org.eclipse.swt.graphics.Font;\r
+import org.eclipse.swt.graphics.Image;\r
+import org.eclipse.swt.graphics.RGB;\r
+import org.eclipse.swt.layout.FillLayout;\r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.swt.widgets.Control;\r
+import org.eclipse.swt.widgets.Display;\r
+import org.eclipse.swt.widgets.Event;\r
+import org.eclipse.swt.widgets.Listener;\r
+import org.eclipse.swt.widgets.ScrollBar;\r
+import org.eclipse.swt.widgets.Tree;\r
+import org.eclipse.swt.widgets.TreeColumn;\r
+import org.eclipse.ui.PlatformUI;\r
+import org.eclipse.ui.contexts.IContextActivation;\r
+import org.eclipse.ui.contexts.IContextService;\r
+import org.eclipse.ui.services.IServiceLocator;\r
+import org.eclipse.ui.swt.IFocusService;\r
+import org.simantics.browsing.ui.BuiltinKeys;\r
+import org.simantics.browsing.ui.Column;\r
+import org.simantics.browsing.ui.Column.Align;\r
+import org.simantics.browsing.ui.DataSource;\r
+import org.simantics.browsing.ui.ExplorerState;\r
+import org.simantics.browsing.ui.GraphExplorer;\r
+import org.simantics.browsing.ui.NodeContext;\r
+import org.simantics.browsing.ui.NodeContext.CacheKey;\r
+import org.simantics.browsing.ui.NodeContext.PrimitiveQueryKey;\r
+import org.simantics.browsing.ui.NodeContext.QueryKey;\r
+import org.simantics.browsing.ui.NodeQueryManager;\r
+import org.simantics.browsing.ui.NodeQueryProcessor;\r
+import org.simantics.browsing.ui.PrimitiveQueryProcessor;\r
+import org.simantics.browsing.ui.PrimitiveQueryUpdater;\r
+import org.simantics.browsing.ui.SelectionDataResolver;\r
+import org.simantics.browsing.ui.SelectionFilter;\r
+import org.simantics.browsing.ui.StatePersistor;\r
+import org.simantics.browsing.ui.common.ColumnKeys;\r
+import org.simantics.browsing.ui.common.ErrorLogger;\r
+import org.simantics.browsing.ui.common.NodeContextBuilder;\r
+import org.simantics.browsing.ui.common.NodeContextUtil;\r
+import org.simantics.browsing.ui.common.internal.GENodeQueryManager;\r
+import org.simantics.browsing.ui.common.internal.IGECache;\r
+import org.simantics.browsing.ui.common.internal.IGraphExplorerContext;\r
+import org.simantics.browsing.ui.common.internal.UIElementReference;\r
+import org.simantics.browsing.ui.common.processors.AbstractPrimitiveQueryProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultCheckedStateProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultComparableChildrenProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultFinalChildrenProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultImageDecoratorProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultImagerFactoriesProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultImagerProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultLabelDecoratorProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultLabelerFactoriesProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultLabelerProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultPrunedChildrenProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultSelectedImageDecoratorFactoriesProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelDecoratorFactoriesProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelerProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointFactoryProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionsProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultViewpointProcessor;\r
+import org.simantics.browsing.ui.common.processors.IsExpandedProcessor;\r
+import org.simantics.browsing.ui.common.processors.NoSelectionRequestProcessor;\r
+import org.simantics.browsing.ui.common.processors.ProcessorLifecycle;\r
+import org.simantics.browsing.ui.content.ImageDecorator;\r
+import org.simantics.browsing.ui.content.Imager;\r
+import org.simantics.browsing.ui.content.LabelDecorator;\r
+import org.simantics.browsing.ui.content.Labeler;\r
+import org.simantics.browsing.ui.content.Labeler.CustomModifier;\r
+import org.simantics.browsing.ui.content.Labeler.DialogModifier;\r
+import org.simantics.browsing.ui.content.Labeler.EnumerationModifier;\r
+import org.simantics.browsing.ui.content.Labeler.Modifier;\r
+import org.simantics.browsing.ui.swt.internal.Threads;\r
+import org.simantics.db.layer0.SelectionHints;\r
+import org.simantics.ui.SimanticsUI;\r
+import org.simantics.utils.datastructures.BijectionMap;\r
+import org.simantics.utils.datastructures.BinaryFunction;\r
+import org.simantics.utils.datastructures.MapList;\r
+import org.simantics.utils.datastructures.disposable.AbstractDisposable;\r
+import org.simantics.utils.datastructures.hints.IHintContext;\r
+import org.simantics.utils.threads.IThreadWorkQueue;\r
+import org.simantics.utils.threads.SWTThread;\r
+import org.simantics.utils.threads.ThreadUtils;\r
+import org.simantics.utils.ui.AdaptionUtils;\r
+import org.simantics.utils.ui.ISelectionUtils;\r
+import org.simantics.utils.ui.jface.BasePostSelectionProvider;\r
+\r
+import gnu.trove.map.hash.THashMap;\r
+import gnu.trove.map.hash.TObjectIntHashMap;\r
+\r
+/**\r
+ * TreeView based GraphExplorer\r
+ * \r
+ * \r
+ * @author Marko Luukkainen <marko.luukkainen@vtt.fi>\r
+ */\r
+public class GraphExplorerImpl2 extends GraphExplorerImplBase implements GraphExplorer {\r
+ \r
+ private static final boolean DEBUG_SELECTION_LISTENERS = false;\r
+ private static final boolean DEBUG = false;\r
+ \r
+ private TreeViewer viewer;\r
+ \r
+ private LocalResourceManager localResourceManager;\r
+ private DeviceResourceManager resourceManager;\r
+ \r
+ \r
+ private IThreadWorkQueue thread;\r
+ \r
+ @SuppressWarnings({ "rawtypes" })\r
+ final HashMap<CacheKey<?>, NodeQueryProcessor> processors = new HashMap<CacheKey<?>, NodeQueryProcessor>();\r
+ @SuppressWarnings({ "rawtypes" })\r
+ final HashMap<Object, PrimitiveQueryProcessor> primitiveProcessors = new HashMap<Object, PrimitiveQueryProcessor>();\r
+ @SuppressWarnings({ "rawtypes" })\r
+ final HashMap<Class, DataSource> dataSources = new HashMap<Class, DataSource>();\r
+ \r
+ private FontDescriptor originalFont;\r
+ protected ColorDescriptor originalForeground;\r
+ protected ColorDescriptor originalBackground;\r
+ private Color invalidModificationColor;\r
+\r
+ private Column[] columns;\r
+ private Map<String,Integer> columnKeyToIndex;\r
+ private boolean columnsAreVisible = true;\r
+\r
+ \r
+ private NodeContext rootContext;\r
+ private TreeNode rootNode;\r
+ private StatePersistor persistor = null;\r
+\r
+ private boolean editable = true;\r
+ \r
+ private boolean disposed = false;\r
+ \r
+ private final CopyOnWriteArrayList<FocusListener> focusListeners = new CopyOnWriteArrayList<FocusListener>();\r
+ private final CopyOnWriteArrayList<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();\r
+ private final CopyOnWriteArrayList<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>();\r
+ \r
+ private int autoExpandLevel = 0;\r
+ private IServiceLocator serviceLocator;\r
+ private IContextService contextService = null;\r
+ private IFocusService focusService = null;\r
+ private IContextActivation editingContext = null;\r
+ \r
+ GeViewerContext explorerContext = new GeViewerContext(this);\r
+ \r
+ private GraphExplorerPostSelectionProvider postSelectionProvider = new GraphExplorerPostSelectionProvider(this);\r
+ private BasePostSelectionProvider selectionProvider = new BasePostSelectionProvider();\r
+ private SelectionDataResolver selectionDataResolver;\r
+ private SelectionFilter selectionFilter;\r
+ \r
+ private Set<TreeNode> collapsedNodes = new HashSet<TreeNode>();\r
+ private MapList<NodeContext, TreeNode> contextToNodeMap = new MapList<NodeContext, TreeNode>();\r
+ \r
+ private ModificationContext modificationContext = null;\r
+ \r
+ private boolean filterSelectionEdit = true;\r
+ \r
+ private TreeColumnLayout treeColumnLayout;\r
+ \r
+ private boolean expand;\r
+ private boolean verticalBarVisible = false;\r
+ \r
+ private BinaryFunction<Object[], GraphExplorer, Object[]> selectionTransformation = new BinaryFunction<Object[], GraphExplorer, Object[]>() {\r
+\r
+ @Override\r
+ public Object[] call(GraphExplorer explorer, Object[] objects) {\r
+ Object[] result = new Object[objects.length];\r
+ for (int i = 0; i < objects.length; i++) {\r
+ IHintContext context = new AdaptableHintContext(SelectionHints.KEY_MAIN);\r
+ context.setHint(SelectionHints.KEY_MAIN, objects[i]);\r
+ result[i] = context;\r
+ }\r
+ return result;\r
+ }\r
+\r
+ };\r
+ \r
+ static class TransientStateImpl implements TransientExplorerState {\r
+\r
+ private Integer activeColumn = null;\r
+ \r
+ @Override\r
+ public synchronized Integer getActiveColumn() {\r
+ return activeColumn;\r
+ }\r
+ \r
+ public synchronized void setActiveColumn(Integer column) {\r
+ activeColumn = column;\r
+ }\r
+ \r
+ }\r
+ \r
+ private TransientStateImpl transientState = new TransientStateImpl();\r
+ \r
+ \r
+ public GraphExplorerImpl2(Composite parent) {\r
+ this(parent, SWT.BORDER | SWT.MULTI );\r
+ }\r
+\r
+ public GraphExplorerImpl2(Composite parent, int style) {\r
+ this.localResourceManager = new LocalResourceManager(JFaceResources.getResources());\r
+ this.resourceManager = new DeviceResourceManager(parent.getDisplay());\r
+\r
+ this.imageLoaderJob = new ImageLoaderJob(this);\r
+ this.imageLoaderJob.setPriority(Job.DECORATE);\r
+\r
+ invalidModificationColor = (Color) localResourceManager.get(ColorDescriptor.createFrom(new RGB(255, 128, 128)));\r
+\r
+ this.thread = SWTThread.getThreadAccess(parent);\r
+\r
+ for (int i = 0; i < 10; i++)\r
+ explorerContext.activity.push(0);\r
+\r
+ boolean useLayout = true;\r
+ // FIXME: hack, GraphExplorerComposite uses its own TreeColumnLayout. \r
+ if (useLayout && !(parent.getLayout() instanceof TreeColumnLayout)) {\r
+\r
+ Composite rootTreeComposite = new Composite(parent, SWT.NONE);\r
+ treeColumnLayout = new TreeColumnLayout();\r
+ rootTreeComposite.setLayout(treeColumnLayout);\r
+\r
+ viewer = new TreeViewer(rootTreeComposite,style|SWT.H_SCROLL|SWT.V_SCROLL);\r
+ \r
+ GridDataFactory.fillDefaults().grab(true, true).span(3,1).applyTo(rootTreeComposite);\r
+ \r
+ } else {\r
+ viewer = new TreeViewer(parent,style | SWT.H_SCROLL | SWT.V_SCROLL);\r
+ }\r
+ \r
+ viewer.getColumnViewerEditor().addEditorActivationListener(new ColumnViewerEditorActivationListener() {\r
+ \r
+ @Override\r
+ public void beforeEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {\r
+ \r
+ }\r
+ \r
+ @Override\r
+ public void beforeEditorActivated(ColumnViewerEditorActivationEvent event) {\r
+ // cancel editor activation for double click events.\r
+ // TODO: this may not work similarly to GraphExplorerImpl\r
+ if ((event.time - focusGainedAt) < 250L) {\r
+ event.cancel = true;\r
+ }\r
+ }\r
+ \r
+ @Override\r
+ public void afterEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {\r
+ \r
+ }\r
+ \r
+ @Override\r
+ public void afterEditorActivated(ColumnViewerEditorActivationEvent event) {\r
+ \r
+ }\r
+ });\r
+ \r
+ viewer.setUseHashlookup(true);\r
+ viewer.setContentProvider(new GeViewerContentProvider());\r
+ \r
+ \r
+\r
+ originalFont = JFaceResources.getDefaultFontDescriptor();\r
+\r
+ viewer.getTree().setFont((Font) localResourceManager.get(originalFont));\r
+ \r
+ setBasicListeners();\r
+ setDefaultProcessors();\r
+ \r
+ viewer.getTree().addDisposeListener(new DisposeListener() {\r
+ \r
+ @Override\r
+ public void widgetDisposed(DisposeEvent e) {\r
+ doDispose();\r
+ \r
+ }\r
+ });\r
+ \r
+ \r
+ // Add listener to tree for delayed tree population. \r
+ \r
+ Listener listener = new Listener() {\r
+ \r
+ @Override\r
+ public void handleEvent(Event event) {\r
+ \r
+ switch (event.type) {\r
+ case SWT.Activate:\r
+ case SWT.Show:\r
+ case SWT.Paint:\r
+ {\r
+ visible = true;\r
+ activate();\r
+ break;\r
+ }\r
+ case SWT.Deactivate:\r
+ case SWT.Hide:\r
+ visible = false;\r
+ }\r
+ }\r
+ };\r
+ \r
+ viewer.getTree().addListener(SWT.Activate, listener);\r
+ viewer.getTree().addListener(SWT.Deactivate, listener);\r
+ viewer.getTree().addListener(SWT.Show, listener);\r
+ viewer.getTree().addListener(SWT.Hide, listener);\r
+ viewer.getTree().addListener(SWT.Paint,listener);\r
+ \r
+ \r
+ viewer.addTreeListener(new ITreeViewerListener() {\r
+ \r
+ @Override\r
+ public void treeExpanded(TreeExpansionEvent event) {\r
+ \r
+ }\r
+ \r
+ @Override\r
+ public void treeCollapsed(TreeExpansionEvent event) {\r
+ collapsedNodes.add((TreeNode)event.getElement());\r
+ }\r
+ });\r
+ \r
+ setColumns( new Column[] { new Column(ColumnKeys.SINGLE) });\r
+ }\r
+ \r
+ private long focusGainedAt = 0L;\r
+ private boolean visible = false;\r
+ \r
+ private Collection<TreeNode> selectedNodes = new ArrayList<TreeNode>();\r
+ \r
+ protected void setBasicListeners() {\r
+ Tree tree = viewer.getTree();\r
+ \r
+ tree.addFocusListener(new FocusListener() {\r
+ @Override\r
+ public void focusGained(FocusEvent e) {\r
+ focusGainedAt = ((long) e.time) & 0xFFFFFFFFL;\r
+ for (FocusListener listener : focusListeners)\r
+ listener.focusGained(e);\r
+ }\r
+ @Override\r
+ public void focusLost(FocusEvent e) {\r
+ for (FocusListener listener : focusListeners)\r
+ listener.focusLost(e);\r
+ }\r
+ });\r
+ tree.addMouseListener(new MouseListener() {\r
+ @Override\r
+ public void mouseDoubleClick(MouseEvent e) {\r
+ for (MouseListener listener : mouseListeners) {\r
+ listener.mouseDoubleClick(e);\r
+ }\r
+ }\r
+ @Override\r
+ public void mouseDown(MouseEvent e) {\r
+ for (MouseListener listener : mouseListeners) {\r
+ listener.mouseDown(e);\r
+ }\r
+ }\r
+ @Override\r
+ public void mouseUp(MouseEvent e) {\r
+ for (MouseListener listener : mouseListeners) {\r
+ listener.mouseUp(e);\r
+ }\r
+ }\r
+ });\r
+ tree.addKeyListener(new KeyListener() {\r
+ @Override\r
+ public void keyPressed(KeyEvent e) {\r
+ for (KeyListener listener : keyListeners) {\r
+ listener.keyPressed(e);\r
+ }\r
+ }\r
+ @Override\r
+ public void keyReleased(KeyEvent e) {\r
+ for (KeyListener listener : keyListeners) {\r
+ listener.keyReleased(e);\r
+ }\r
+ }\r
+ });\r
+ \r
+ viewer.addSelectionChangedListener(new ISelectionChangedListener() {\r
+ \r
+ @Override\r
+ public void selectionChanged(SelectionChangedEvent event) {\r
+ //System.out.println("GraphExplorerImpl2.fireSelection");\r
+ selectedNodes = AdaptionUtils.adaptToCollection(event.getSelection(), TreeNode.class);\r
+ Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);\r
+ selectionProvider.setAndFireSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));\r
+ }\r
+ });\r
+ \r
+ viewer.addPostSelectionChangedListener(new ISelectionChangedListener() {\r
+ \r
+ @Override\r
+ public void selectionChanged(SelectionChangedEvent event) {\r
+ //System.out.println("GraphExplorerImpl2.firePostSelection");\r
+ Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);\r
+ selectionProvider.firePostSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));\r
+ \r
+ }\r
+ });\r
+\r
+ }\r
+ \r
+ private NodeContext pendingRoot;\r
+ \r
+ private void activate() {\r
+ if (pendingRoot != null && !expand) {\r
+ doSetRoot(pendingRoot);\r
+ pendingRoot = null;\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Invoke only from SWT thread to reset the root of the graph explorer tree.\r
+ * \r
+ * @param root\r
+ */\r
+ private void doSetRoot(NodeContext root) {\r
+ Display display = viewer.getTree().getDisplay();\r
+ if (display.getThread() != Thread.currentThread()) {\r
+ throw new RuntimeException("Invoke from SWT thread only");\r
+ }\r
+// System.out.println("doSetRoot " + root);\r
+ if (isDisposed())\r
+ return;\r
+ if (viewer.getTree().isDisposed())\r
+ return;\r
+ if (root.getConstant(BuiltinKeys.INPUT) == null) {\r
+ ErrorLogger.defaultLogError("root node context does not contain BuiltinKeys.INPUT key. Node = " + root, new Exception("trace"));\r
+ return;\r
+ }\r
+ \r
+ \r
+\r
+ // Empty caches, release queries.\r
+ if (rootNode != null) {\r
+ rootNode.dispose();\r
+ } \r
+ GeViewerContext oldContext = explorerContext;\r
+ GeViewerContext newContext = new GeViewerContext(this);\r
+ this.explorerContext = newContext;\r
+ oldContext.safeDispose();\r
+\r
+ // Need to empty these or otherwise they won't be emptied until the\r
+ // explorer is disposed which would mean that many unwanted references\r
+ // will be held by this map.\r
+ clearPrimitiveProcessors();\r
+\r
+ this.rootContext = root.getConstant(BuiltinKeys.IS_ROOT) != null ? root\r
+ : NodeContextUtil.withConstant(root, BuiltinKeys.IS_ROOT, Boolean.TRUE);\r
+\r
+ explorerContext.getCache().incRef(this.rootContext);\r
+\r
+ initializeState();\r
+ \r
+ \r
+ select(rootContext);\r
+ //refreshColumnSizes();\r
+ rootNode = new TreeNode(rootContext);\r
+ if (DEBUG) System.out.println("setRoot " + rootNode);\r
+ \r
+ viewer.setInput(rootNode);\r
+ \r
+ // Delay content reading.\r
+ \r
+ // This is required for cases when GEImpl2 is attached to selection view. Reading content\r
+ // instantly could stagnate SWT thread under rapid changes in selection. By delaying the \r
+ // content reading we give the system a change to dispose the GEImpl2 before the content is read.\r
+ display.asyncExec(new Runnable() {\r
+ \r
+ @Override\r
+ public void run() {\r
+ if (rootNode != null) {\r
+ rootNode.updateChildren();\r
+ }\r
+ }\r
+ });\r
+ \r
+ }\r
+ \r
+ private void initializeState() {\r
+ if (persistor == null)\r
+ return;\r
+\r
+ ExplorerState state = persistor.deserialize(\r
+ Platform.getStateLocation(Activator.getDefault().getBundle()).toFile(),\r
+ getRoot());\r
+\r
+\r
+ Object processor = getPrimitiveProcessor(BuiltinKeys.IS_EXPANDED);\r
+ if (processor instanceof DefaultIsExpandedProcessor) {\r
+ DefaultIsExpandedProcessor isExpandedProcessor = (DefaultIsExpandedProcessor)processor;\r
+ for(NodeContext expanded : state.expandedNodes) {\r
+ isExpandedProcessor.setExpanded(expanded, true);\r
+ }\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public NodeContext getRoot() {\r
+ return rootContext;\r
+ }\r
+ \r
+ @Override\r
+ public IThreadWorkQueue getThread() {\r
+ return thread;\r
+ }\r
+\r
+ @Override\r
+ public NodeContext getParentContext(NodeContext context) {\r
+ if (disposed)\r
+ throw new IllegalStateException("disposed");\r
+ if (!thread.currentThreadAccess())\r
+ throw new IllegalStateException("not in SWT display thread " + thread.getThread());\r
+\r
+ List<TreeNode> nodes = contextToNodeMap.getValuesUnsafe(context);\r
+ for (int i = 0; i < nodes.size(); i++) {\r
+ if (nodes.get(i).getParent() != null)\r
+ return nodes.get(i).getParent().getContext();\r
+ }\r
+ return null;\r
+ \r
+ }\r
+ \r
+ \r
+ @SuppressWarnings("unchecked")\r
+ @Override\r
+ public <T> T getAdapter(Class<T> adapter) {\r
+ if(ISelectionProvider.class == adapter) return (T) postSelectionProvider;\r
+ else if(IPostSelectionProvider.class == adapter) return (T) postSelectionProvider;\r
+ return null;\r
+ }\r
+\r
+ \r
+ protected void setDefaultProcessors() {\r
+ // Add a simple IMAGER query processor that always returns null.\r
+ // With this processor no images will ever be shown.\r
+ // setPrimitiveProcessor(new StaticImagerProcessor(null));\r
+\r
+ setProcessor(new DefaultComparableChildrenProcessor());\r
+ setProcessor(new DefaultLabelDecoratorsProcessor());\r
+ setProcessor(new DefaultImageDecoratorsProcessor());\r
+ setProcessor(new DefaultSelectedLabelerProcessor());\r
+ setProcessor(new DefaultLabelerFactoriesProcessor());\r
+ setProcessor(new DefaultSelectedImagerProcessor());\r
+ setProcessor(new DefaultImagerFactoriesProcessor());\r
+ setPrimitiveProcessor(new DefaultLabelerProcessor());\r
+ setPrimitiveProcessor(new DefaultCheckedStateProcessor());\r
+ setPrimitiveProcessor(new DefaultImagerProcessor());\r
+ setPrimitiveProcessor(new DefaultLabelDecoratorProcessor());\r
+ setPrimitiveProcessor(new DefaultImageDecoratorProcessor());\r
+ setPrimitiveProcessor(new NoSelectionRequestProcessor());\r
+\r
+ setProcessor(new DefaultFinalChildrenProcessor(this));\r
+\r
+ setProcessor(new DefaultPrunedChildrenProcessor());\r
+ setProcessor(new DefaultSelectedViewpointProcessor());\r
+ setProcessor(new DefaultSelectedLabelDecoratorFactoriesProcessor());\r
+ setProcessor(new DefaultSelectedImageDecoratorFactoriesProcessor());\r
+ setProcessor(new DefaultViewpointContributionsProcessor());\r
+\r
+ setPrimitiveProcessor(new DefaultViewpointProcessor());\r
+ setPrimitiveProcessor(new DefaultViewpointContributionProcessor());\r
+ setPrimitiveProcessor(new DefaultSelectedViewpointFactoryProcessor());\r
+ setPrimitiveProcessor(new TreeNodeIsExpandedProcessor());\r
+ setPrimitiveProcessor(new DefaultShowMaxChildrenProcessor());\r
+ }\r
+ \r
+ @Override\r
+ public Column[] getColumns() {\r
+ return Arrays.copyOf(columns, columns.length);\r
+ }\r
+ \r
+ @Override\r
+ public void setColumnsVisible(boolean visible) {\r
+ columnsAreVisible = visible;\r
+ if(viewer.getTree() != null) viewer.getTree().setHeaderVisible(columnsAreVisible);\r
+ }\r
+\r
+ @Override\r
+ public void setColumns(final Column[] columns) {\r
+ setColumns(columns, null);\r
+ }\r
+\r
+ @Override\r
+ public void setColumns(final Column[] columns, Consumer<Map<Column, Object>> callback) {\r
+ assertNotDisposed();\r
+ checkUniqueColumnKeys(columns);\r
+\r
+ Display d = viewer.getTree().getDisplay();\r
+ if (d.getThread() == Thread.currentThread()) {\r
+ doSetColumns(columns, callback);\r
+ viewer.refresh(true);\r
+ }else\r
+ d.asyncExec(new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ if (viewer == null)\r
+ return;\r
+ if (viewer.getTree().isDisposed())\r
+ return;\r
+ doSetColumns(columns, callback);\r
+ viewer.refresh(true);\r
+ viewer.getTree().getParent().layout();\r
+ }\r
+ });\r
+ }\r
+ \r
+ private void checkUniqueColumnKeys(Column[] cols) {\r
+ Set<String> usedColumnKeys = new HashSet<String>();\r
+ List<Column> duplicateColumns = new ArrayList<Column>();\r
+ for (Column c : cols) {\r
+ if (!usedColumnKeys.add(c.getKey()))\r
+ duplicateColumns.add(c);\r
+ }\r
+ if (!duplicateColumns.isEmpty()) {\r
+ throw new IllegalArgumentException("All columns do not have unique keys: " + cols + ", overlapping: " + duplicateColumns);\r
+ }\r
+ }\r
+ \r
+ private List<TreeViewerColumn> treeViewerColumns = new ArrayList<TreeViewerColumn>();\r
+ private CellLabelProvider cellLabelProvider = new GeViewerLabelProvider();\r
+ private List<EditingSupport> editingSupports = new ArrayList<EditingSupport>();\r
+ \r
+ private void doSetColumns(Column[] cols, Consumer<Map<Column, Object>> callback) {\r
+ // Attempt to keep previous column widths.\r
+ Map<String, Integer> prevWidths = new HashMap<String, Integer>();\r
+ \r
+ for (TreeViewerColumn c : treeViewerColumns) {\r
+ prevWidths.put(c.getColumn().getText(), c.getColumn().getWidth());\r
+ c.getColumn().dispose();\r
+ }\r
+ \r
+ treeViewerColumns.clear();\r
+ \r
+ HashMap<String, Integer> keyToIndex = new HashMap<String, Integer>();\r
+ for (int i = 0; i < cols.length; ++i) {\r
+ keyToIndex.put(cols[i].getKey(), i);\r
+ }\r
+\r
+ this.columns = Arrays.copyOf(cols, cols.length);\r
+ //this.columns[cols.length] = FILLER_COLUMN;\r
+ this.columnKeyToIndex = keyToIndex;\r
+\r
+ Map<Column, Object> map = new HashMap<Column, Object>();\r
+\r
+ // FIXME : temporary workaround for ModelBrowser.\r
+ viewer.getTree().setHeaderVisible(columns.length == 1 ? false : columnsAreVisible);\r
+ \r
+ int columnIndex = 0;\r
+\r
+ for (Column column : columns) {\r
+ TreeViewerColumn tvc = new TreeViewerColumn(viewer, toSWT(column.getAlignment()));\r
+ treeViewerColumns.add(tvc);\r
+ tvc.setLabelProvider(cellLabelProvider);\r
+ EditingSupport support = null;\r
+ if (editingSupports.size() > columnIndex)\r
+ support = editingSupports.get(columnIndex);\r
+ else {\r
+ support = new GeEditingSupport(viewer, columnIndex);\r
+ editingSupports.add(support);\r
+ }\r
+ \r
+ tvc.setEditingSupport(support);\r
+ \r
+ TreeColumn c = tvc.getColumn();\r
+ map.put(column, c);\r
+ c.setData(column);\r
+ c.setText(column.getLabel());\r
+ c.setToolTipText(column.getTooltip());\r
+\r
+ int cw = column.getWidth();\r
+\r
+ // Try to keep previous widths\r
+ Integer w = prevWidths.get(column);\r
+ if (w != null)\r
+ c.setWidth(w);\r
+ else if (cw != Column.DEFAULT_CONTROL_WIDTH)\r
+ c.setWidth(cw);\r
+ else if (columns.length == 1) {\r
+ // FIXME : how to handle single column properly?\r
+ c.setWidth(1000);\r
+ }\r
+ else {\r
+ // Go for some kind of default settings then...\r
+ if (ColumnKeys.PROPERTY.equals(column.getKey()))\r
+ c.setWidth(150);\r
+ else\r
+ c.setWidth(50);\r
+ }\r
+ if (treeColumnLayout != null) {\r
+ treeColumnLayout.setColumnData(c, new ColumnWeightData(column.getWeight(), true));\r
+ }\r
+\r
+// if (!column.hasGrab() && !FILLER.equals(column.getKey())) {\r
+// c.addListener(SWT.Resize, resizeListener);\r
+// c.setResizable(true);\r
+// } else {\r
+// //c.setResizable(false);\r
+// }\r
+ columnIndex++;\r
+ }\r
+ \r
+ \r
+\r
+ if(callback != null) callback.accept(map);\r
+ }\r
+\r
+ int toSWT(Align alignment) {\r
+ switch (alignment) {\r
+ case LEFT: return SWT.LEFT;\r
+ case CENTER: return SWT.CENTER;\r
+ case RIGHT: return SWT.RIGHT;\r
+ default: throw new Error("unhandled alignment: " + alignment);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public <T> void setProcessor(NodeQueryProcessor<T> processor) {\r
+ assertNotDisposed();\r
+ if (processor == null)\r
+ throw new IllegalArgumentException("null processor");\r
+\r
+ processors.put(processor.getIdentifier(), processor);\r
+ }\r
+\r
+ @Override\r
+ public <T> void setPrimitiveProcessor(PrimitiveQueryProcessor<T> processor) {\r
+ assertNotDisposed();\r
+ if (processor == null)\r
+ throw new IllegalArgumentException("null processor");\r
+\r
+ PrimitiveQueryProcessor<?> oldProcessor = primitiveProcessors.put(\r
+ processor.getIdentifier(), processor);\r
+\r
+ if (oldProcessor instanceof ProcessorLifecycle)\r
+ ((ProcessorLifecycle) oldProcessor).detached(this);\r
+ if (processor instanceof ProcessorLifecycle)\r
+ ((ProcessorLifecycle) processor).attached(this);\r
+ }\r
+\r
+ @Override\r
+ public <T> void setDataSource(DataSource<T> provider) {\r
+ assertNotDisposed();\r
+ if (provider == null)\r
+ throw new IllegalArgumentException("null provider");\r
+ dataSources.put(provider.getProvidedClass(), provider);\r
+ }\r
+\r
+ @SuppressWarnings("unchecked")\r
+ @Override\r
+ public <T> DataSource<T> removeDataSource(Class<T> forProvidedClass) {\r
+ assertNotDisposed();\r
+ if (forProvidedClass == null)\r
+ throw new IllegalArgumentException("null class");\r
+ return dataSources.remove(forProvidedClass);\r
+ }\r
+\r
+ @Override\r
+ public void setPersistor(StatePersistor persistor) {\r
+ this.persistor = persistor;\r
+ }\r
+\r
+ @Override\r
+ public SelectionDataResolver getSelectionDataResolver() {\r
+ return selectionDataResolver;\r
+ }\r
+\r
+ @Override\r
+ public void setSelectionDataResolver(SelectionDataResolver r) {\r
+ this.selectionDataResolver = r;\r
+ }\r
+\r
+ @Override\r
+ public SelectionFilter getSelectionFilter() {\r
+ return selectionFilter;\r
+ }\r
+\r
+ @Override\r
+ public void setSelectionFilter(SelectionFilter f) {\r
+ this.selectionFilter = f;\r
+ // TODO: re-filter current selection?\r
+ }\r
+ \r
+ protected ISelection constructSelection(NodeContext... contexts) {\r
+ if (contexts == null)\r
+ throw new IllegalArgumentException("null contexts");\r
+ if (contexts.length == 0)\r
+ return StructuredSelection.EMPTY;\r
+ if (selectionFilter == null)\r
+ return new StructuredSelection(transformSelection(contexts));\r
+ return new StructuredSelection( transformSelection(filter(selectionFilter, contexts)) );\r
+ }\r
+ \r
+ protected Object[] transformSelection(Object[] objects) {\r
+ return selectionTransformation.call(this, objects);\r
+ }\r
+ \r
+ protected static Object[] filter(SelectionFilter filter, NodeContext[] contexts) {\r
+ int len = contexts.length;\r
+ Object[] objects = new Object[len];\r
+ for (int i = 0; i < len; ++i)\r
+ objects[i] = filter.filter(contexts[i]);\r
+ return objects;\r
+ }\r
+\r
+ @Override\r
+ public void setSelectionTransformation(\r
+ BinaryFunction<Object[], GraphExplorer, Object[]> f) {\r
+ this.selectionTransformation = f;\r
+ }\r
+ \r
+ public ISelection getWidgetSelection() {\r
+ return viewer.getSelection();\r
+ }\r
+\r
+ @Override\r
+ public <T> void addListener(T listener) {\r
+ if (listener instanceof FocusListener) {\r
+ focusListeners.add((FocusListener) listener);\r
+ } else if (listener instanceof MouseListener) {\r
+ mouseListeners.add((MouseListener) listener);\r
+ } else if (listener instanceof KeyListener) {\r
+ keyListeners.add((KeyListener) listener);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public <T> void removeListener(T listener) {\r
+ if (listener instanceof FocusListener) {\r
+ focusListeners.remove(listener);\r
+ } else if (listener instanceof MouseListener) {\r
+ mouseListeners.remove(listener);\r
+ } else if (listener instanceof KeyListener) {\r
+ keyListeners.remove(listener);\r
+ }\r
+ }\r
+\r
+ public void addSelectionListener(SelectionListener listener) {\r
+ viewer.getTree().addSelectionListener(listener);\r
+ }\r
+\r
+ public void removeSelectionListener(SelectionListener listener) {\r
+ viewer.getTree().removeSelectionListener(listener);\r
+ }\r
+\r
+ private Set<String> uiContexts;\r
+ \r
+ @Override\r
+ public void setUIContexts(Set<String> contexts) {\r
+ this.uiContexts = contexts;\r
+ }\r
+ \r
+ @Override\r
+ public void setRoot(final Object root) {\r
+ if(uiContexts != null && uiContexts.size() == 1)\r
+ setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root, BuiltinKeys.UI_CONTEXT, uiContexts.iterator().next()));\r
+ else\r
+ setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root));\r
+ }\r
+\r
+ @Override\r
+ public void setRootContext(final NodeContext context) {\r
+ setRootContext0(context);\r
+ }\r
+ \r
+ private void setRoot(NodeContext context) {\r
+ if (!visible) {\r
+ pendingRoot = context;\r
+ Display.getDefault().asyncExec(new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ if (viewer != null && !viewer.getTree().isDisposed())\r
+ viewer.getTree().redraw();\r
+ }\r
+ });\r
+ return;\r
+ }\r
+ doSetRoot(context);\r
+ }\r
+\r
+ private void setRootContext0(final NodeContext context) {\r
+ Assert.isNotNull(context, "root must not be null");\r
+ if (isDisposed() || viewer.getTree().isDisposed())\r
+ return;\r
+ Display display = viewer.getTree().getDisplay();\r
+ if (display.getThread() == Thread.currentThread()) {\r
+ setRoot(context);\r
+ } else {\r
+ display.asyncExec(new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ setRoot(context);\r
+ }\r
+ });\r
+ }\r
+ }\r
+ \r
+ @Override\r
+ public void setFocus() {\r
+ viewer.getTree().setFocus();\r
+ }\r
+ \r
+ @SuppressWarnings("unchecked")\r
+ @Override\r
+ public <T> T getControl() {\r
+ return (T)viewer.getTree();\r
+ }\r
+ \r
+ \r
+ @Override\r
+ public boolean isDisposed() {\r
+ return disposed;\r
+ }\r
+\r
+ protected void assertNotDisposed() {\r
+ if (isDisposed())\r
+ throw new IllegalStateException("disposed");\r
+ }\r
+ \r
+ @Override\r
+ public boolean isEditable() {\r
+ return editable;\r
+ }\r
+\r
+ @Override\r
+ public void setEditable(boolean editable) {\r
+ if (!thread.currentThreadAccess())\r
+ throw new IllegalStateException("not in SWT display thread " + thread.getThread());\r
+\r
+ this.editable = editable;\r
+ Display display = viewer.getTree().getDisplay();\r
+ viewer.getTree().setBackground(editable ? null : display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));\r
+ }\r
+ \r
+ private void doDispose() {\r
+ if (disposed)\r
+ return;\r
+ disposed = true;\r
+ // TODO: Since GENodeQueryManager is cached in QueryChache and it refers to this class\r
+ // we have to remove all references here to reduce memory consumption.\r
+ // \r
+ // Proper fix would be to remove references between QueryCache and GENodeQueryManagers.\r
+ explorerContext.dispose();\r
+ explorerContext = null;\r
+ processors.clear();\r
+ detachPrimitiveProcessors();\r
+ primitiveProcessors.clear();\r
+ dataSources.clear(); \r
+ pendingItems.clear();\r
+ rootContext = null;\r
+ mouseListeners.clear();\r
+ selectionProvider.clearListeners();\r
+ selectionProvider = null;\r
+ selectionDataResolver = null;\r
+ selectedNodes.clear();\r
+ selectedNodes = null;\r
+ selectionTransformation = null;\r
+ originalFont = null;\r
+ localResourceManager.dispose();\r
+ localResourceManager = null;\r
+ // Must shutdown image loader job before disposing its ResourceManager\r
+ imageLoaderJob.dispose();\r
+ imageLoaderJob.cancel();\r
+ try {\r
+ imageLoaderJob.join();\r
+ imageLoaderJob = null;\r
+ } catch (InterruptedException e) {\r
+ ErrorLogger.defaultLogError(e);\r
+ }\r
+ resourceManager.dispose();\r
+ resourceManager = null;\r
+ collapsedNodes.clear();\r
+ collapsedNodes = null;\r
+ if (rootNode != null) {\r
+ rootNode.dispose();\r
+ rootNode = null; \r
+ } \r
+ contextToNodeMap.clear(); // should be empty at this point.\r
+ contextToNodeMap = null;\r
+ if (postSelectionProvider != null) {\r
+ postSelectionProvider.dispose();\r
+ postSelectionProvider = null;\r
+ }\r
+ imageTasks = null;\r
+ modificationContext = null;\r
+ focusService = null;\r
+ contextService = null;\r
+ serviceLocator = null;\r
+ columns = null;\r
+ columnKeyToIndex.clear();\r
+ columnKeyToIndex = null;\r
+ viewer = null;\r
+\r
+ }\r
+ \r
+ @Override\r
+ public boolean select(NodeContext context) {\r
+\r
+ assertNotDisposed();\r
+\r
+ if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) {\r
+ viewer.setSelection(new StructuredSelection());\r
+ selectionProvider.setAndFireNonEqualSelection(TreeSelection.EMPTY);\r
+ return true;\r
+ }\r
+\r
+ viewer.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0)));\r
+ \r
+ return false;\r
+ \r
+ }\r
+ \r
+ @Override\r
+ public boolean selectPath(Collection<NodeContext> contexts) {\r
+ \r
+ if(contexts == null) throw new IllegalArgumentException("Null list is not allowed");\r
+ if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed");\r
+ \r
+ return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0);\r
+ \r
+ }\r
+ \r
+ private boolean selectPathInternal(NodeContext[] contexts, int position) {\r
+\r
+ NodeContext head = contexts[position];\r
+\r
+ if(position == contexts.length-1) {\r
+ return select(head);\r
+ \r
+ }\r
+\r
+ setExpanded(head, true);\r
+ if(!waitVisible(contexts[position+1])) return false;\r
+ \r
+ return selectPathInternal(contexts, position+1);\r
+ \r
+ }\r
+ \r
+ private boolean waitVisible(NodeContext context) {\r
+ long start = System.nanoTime();\r
+ while(!isVisible(context)) {\r
+ Display.getCurrent().readAndDispatch();\r
+ long duration = System.nanoTime() - start;\r
+ if(duration > 10e9) return false;\r
+ }\r
+ return true;\r
+ }\r
+ \r
+ @Override\r
+ public boolean isVisible(NodeContext context) {\r
+ if (contextToNodeMap.getValuesUnsafe(context).size() == 0)\r
+ return false;\r
+ \r
+ Object elements[] = viewer.getVisibleExpandedElements();\r
+ return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0));\r
+ \r
+ \r
+ }\r
+ \r
+ @Override\r
+ public TransientExplorerState getTransientState() {\r
+ if (!thread.currentThreadAccess())\r
+ throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread());\r
+ return transientState;\r
+ }\r
+ \r
+ @Override\r
+ public <T> T query(NodeContext context, CacheKey<T> key) {\r
+ return this.explorerContext.cache.get(context, key);\r
+ }\r
+ \r
+ /**\r
+ * For setting a more local service locator for the explorer than the global\r
+ * workbench service locator. Sometimes required to give this implementation\r
+ * access to local workbench services like IFocusService.\r
+ * \r
+ * <p>\r
+ * Must be invoked during right after construction.\r
+ * \r
+ * @param serviceLocator\r
+ * a specific service locator or <code>null</code> to use the\r
+ * workbench global service locator\r
+ */\r
+ public void setServiceLocator(IServiceLocator serviceLocator) {\r
+ if (serviceLocator == null && PlatformUI.isWorkbenchRunning())\r
+ serviceLocator = PlatformUI.getWorkbench();\r
+ this.serviceLocator = serviceLocator;\r
+ if (serviceLocator != null) {\r
+ this.contextService = (IContextService) serviceLocator.getService(IContextService.class);\r
+ this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class);\r
+ }\r
+ }\r
+ \r
+ private void detachPrimitiveProcessors() {\r
+ for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {\r
+ if (p instanceof ProcessorLifecycle) {\r
+ ((ProcessorLifecycle) p).detached(this);\r
+ }\r
+ }\r
+ }\r
+\r
+ private void clearPrimitiveProcessors() {\r
+ for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {\r
+ if (p instanceof ProcessorLifecycle) {\r
+ ((ProcessorLifecycle) p).clear();\r
+ }\r
+ }\r
+ }\r
+ \r
+ @Override\r
+ public void setExpanded(NodeContext context, boolean expanded) {\r
+ viewer.setExpandedState(context, expanded);\r
+ \r
+ }\r
+ \r
+ @Override\r
+ public void setAutoExpandLevel(int level) {\r
+ this.autoExpandLevel = level;\r
+ viewer.setAutoExpandLevel(level);\r
+ }\r
+ \r
+ int maxChildren = GraphExplorerImpl.DEFAULT_MAX_CHILDREN;\r
+ \r
+ @Override\r
+ public int getMaxChildren() {\r
+ return maxChildren;\r
+ }\r
+ \r
+ @Override\r
+ public void setMaxChildren(int maxChildren) {\r
+ this.maxChildren = maxChildren;\r
+ \r
+ }\r
+ \r
+ @Override\r
+ public int getMaxChildren(NodeQueryManager manager, NodeContext context) {\r
+ Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN);\r
+ //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result);\r
+ if (result != null) {\r
+ if (result < 0)\r
+ throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result);\r
+ return result;\r
+ }\r
+ return maxChildren;\r
+ }\r
+ \r
+ @Override\r
+ public <T> NodeQueryProcessor<T> getProcessor(QueryKey<T> key) {\r
+ return explorerContext.getProcessor(key);\r
+ }\r
+\r
+ @Override\r
+ public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(PrimitiveQueryKey<T> key) {\r
+ return explorerContext.getPrimitiveProcessor(key);\r
+ }\r
+ \r
+ private HashSet<UpdateItem> pendingItems = new HashSet<UpdateItem>();\r
+ private boolean updating = false;\r
+ private int updateCounter = 0;\r
+ final ScheduledExecutorService uiUpdateScheduler = ThreadUtils.getNonBlockingWorkExecutor();\r
+ \r
+ private class UpdateItem {\r
+ TreeNode element;\r
+ int columnIndex;\r
+ \r
+ public UpdateItem(TreeNode element) {\r
+ this(element,-1);\r
+ }\r
+ \r
+ public UpdateItem(TreeNode element, int columnIndex) {\r
+ this.element = element;\r
+ this.columnIndex = columnIndex;\r
+ if (element != null && element.isDisposed()) {\r
+ throw new IllegalArgumentException("Node is disposed. " + element);\r
+ }\r
+ }\r
+ \r
+ public void update(TreeViewer viewer) {\r
+ if (element != null) {\r
+\r
+ if (element.isDisposed()) {\r
+ return;\r
+ }\r
+ if (((TreeNode)element).updateChildren()) {\r
+ viewer.refresh(element,true);\r
+ } else {\r
+ if (columnIndex >= 0) {\r
+ viewer.update(element, new String[]{columns[columnIndex].getKey()});\r
+ } else {\r
+ viewer.refresh(element,true);\r
+ }\r
+ }\r
+ \r
+ if (!element.isDisposed() && autoExpandLevel > 1 && !collapsedNodes.contains(element) && ((TreeNode)element).distanceToRoot() <= autoExpandLevel) {\r
+ expand = true;\r
+ viewer.setExpandedState(element, true);\r
+ expand = false;\r
+ }\r
+ } else {\r
+ if (rootNode.updateChildren())\r
+ viewer.refresh(rootNode,true);\r
+ }\r
+ }\r
+ \r
+ @Override\r
+ public boolean equals(Object obj) {\r
+ if (obj == null)\r
+ return false;\r
+ if (obj.getClass() != getClass())\r
+ return false;\r
+ UpdateItem other = (UpdateItem)obj;\r
+ if (columnIndex != other.columnIndex)\r
+ return false;\r
+ if (element != null)\r
+ return element.equals(other.element);\r
+ return other.element == null;\r
+ }\r
+ \r
+ @Override\r
+ public int hashCode() {\r
+ if (element != null)\r
+ return element.hashCode() + columnIndex;\r
+ return 0;\r
+ }\r
+ }\r
+ \r
+ private void update(final TreeNode element, final int columnIndex) {\r
+ if (DEBUG)System.out.println("update " + element + " " + columnIndex);\r
+ if (viewer.getTree().isDisposed())\r
+ return;\r
+ synchronized (pendingItems) {\r
+ pendingItems.add(new UpdateItem(element, columnIndex));\r
+ if (updating) return;\r
+ updateCounter++;\r
+ scheduleUpdater();\r
+ }\r
+ }\r
+\r
+ private void update(final TreeNode element) {\r
+ if (DEBUG)System.out.println("update " + element);\r
+ if (viewer.getTree().isDisposed())\r
+ return;\r
+ if (element != null && element.isDisposed())\r
+ return;\r
+ synchronized (pendingItems) {\r
+ \r
+ pendingItems.add(new UpdateItem(element));\r
+ if (updating) return;\r
+ updateCounter++;\r
+ scheduleUpdater();\r
+ }\r
+ }\r
+ \r
+ boolean scheduleUpdater() {\r
+\r
+ if (viewer.getTree().isDisposed())\r
+ return false;\r
+\r
+ if (!pendingItems.isEmpty()) {\r
+ \r
+ int activity = explorerContext.activityInt;\r
+ long delay = 30;\r
+ if (activity < 100) {\r
+ //System.out.println("Scheduling update immediately.");\r
+ } else if (activity < 1000) {\r
+ //System.out.println("Scheduling update after 500ms.");\r
+ delay = 500;\r
+ } else {\r
+ //System.out.println("Scheduling update after 3000ms.");\r
+ delay = 3000;\r
+ }\r
+\r
+ updateCounter = 0;\r
+ \r
+ //System.out.println("Scheduling UI update after " + delay + " ms.");\r
+ uiUpdateScheduler.schedule(new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ \r
+ if (viewer == null || viewer.getTree().isDisposed())\r
+ return;\r
+ \r
+ if (updateCounter > 0) {\r
+ updateCounter = 0;\r
+ uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS);\r
+ } else {\r
+ viewer.getTree().getDisplay().asyncExec(new UpdateRunner(GraphExplorerImpl2.this, GraphExplorerImpl2.this.explorerContext));\r
+ }\r
+ \r
+ }\r
+ }, delay, TimeUnit.MILLISECONDS);\r
+\r
+ updating = true;\r
+ return true;\r
+ }\r
+\r
+ return false;\r
+ }\r
+ \r
+ @Override\r
+ public String startEditing(NodeContext context, String columnKey) {\r
+ assertNotDisposed();\r
+ if (!thread.currentThreadAccess())\r
+ throw new IllegalStateException("not in SWT display thread " + thread.getThread());\r
+\r
+ if(columnKey.startsWith("#")) {\r
+ columnKey = columnKey.substring(1);\r
+ }\r
+\r
+ Integer columnIndex = columnKeyToIndex.get(columnKey);\r
+ if (columnIndex == null)\r
+ return "Rename not supported for selection";\r
+\r
+ viewer.editElement(context, columnIndex);\r
+ if(viewer.isCellEditorActive()) return null;\r
+ return "Rename not supported for selection";\r
+ }\r
+\r
+ @Override\r
+ public String startEditing(String columnKey) {\r
+ ISelection selection = postSelectionProvider.getSelection();\r
+ if(selection == null) return "Rename not supported for selection";\r
+ NodeContext context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class);\r
+ if(context == null) return "Rename not supported for selection";\r
+\r
+ return startEditing(context, columnKey);\r
+\r
+ }\r
+ \r
+ public void setSelection(final ISelection selection, boolean forceControlUpdate) {\r
+ assertNotDisposed();\r
+ boolean equalsOld = selectionProvider.selectionEquals(selection);\r
+ if (equalsOld && !forceControlUpdate) {\r
+ // Just set the selection object instance, fire no events nor update\r
+ // the viewer selection.\r
+ selectionProvider.setSelection(selection);\r
+ } else {\r
+ Collection<NodeContext> coll = AdaptionUtils.adaptToCollection(selection, NodeContext.class);\r
+ Collection<TreeNode> nodes = new ArrayList<TreeNode>();\r
+ for (NodeContext c : coll) {\r
+ List<TreeNode> match = contextToNodeMap.getValuesUnsafe(c);\r
+ if(match.size() > 0)\r
+ nodes.add(match.get(0));\r
+ }\r
+ final ISelection sel = new StructuredSelection(nodes.toArray());\r
+ if (coll.size() == 0)\r
+ return;\r
+ // Schedule viewer and selection update if necessary.\r
+ if (viewer.getTree().isDisposed())\r
+ return;\r
+ Display d = viewer.getTree().getDisplay();\r
+ if (d.getThread() == Thread.currentThread()) {\r
+ viewer.setSelection(sel);\r
+ } else {\r
+ d.asyncExec(new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ if (viewer.getTree().isDisposed())\r
+ return;\r
+ viewer.setSelection(sel);\r
+ }\r
+ });\r
+ }\r
+ }\r
+ }\r
+ \r
+ @Override\r
+ public void setModificationContext(ModificationContext modificationContext) {\r
+ this.modificationContext = modificationContext;\r
+ \r
+ }\r
+ \r
+ final ExecutorService queryUpdateScheduler = Threads.getExecutor();\r
+ \r
+ private static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {\r
+ // This is for query debugging only.\r
+ \r
+ private GraphExplorerImpl2 ge;\r
+ int queryIndent = 0;\r
+\r
+ GECache2 cache = new GECache2();\r
+ AtomicBoolean propagating = new AtomicBoolean(false);\r
+ Object propagateList = new Object();\r
+ Object propagate = new Object();\r
+ List<Runnable> scheduleList = new ArrayList<Runnable>();\r
+ final Deque<Integer> activity = new LinkedList<Integer>();\r
+ int activityInt = 0;\r
+ \r
+ AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();\r
+\r
+ /**\r
+ * Keeps track of nodes that have already been auto-expanded. After\r
+ * being inserted into this set, nodes will not be forced to stay in an\r
+ * expanded state after that. This makes it possible for the user to\r
+ * close auto-expanded nodes.\r
+ */\r
+ Map<NodeContext, Boolean> autoExpanded = new WeakHashMap<NodeContext, Boolean>();\r
+\r
+ public GeViewerContext(GraphExplorerImpl2 ge) {\r
+ this.ge = ge;\r
+ }\r
+ \r
+ @Override\r
+ protected void doDispose() {\r
+ //saveState();\r
+ autoExpanded.clear();\r
+ }\r
+\r
+ @Override\r
+ public IGECache getCache() {\r
+ return cache;\r
+ }\r
+\r
+ @Override\r
+ public int queryIndent() {\r
+ return queryIndent;\r
+ }\r
+\r
+ @Override\r
+ public int queryIndent(int offset) {\r
+ queryIndent += offset;\r
+ return queryIndent;\r
+ }\r
+\r
+ @Override\r
+ @SuppressWarnings("unchecked")\r
+ public <T> NodeQueryProcessor<T> getProcessor(Object o) {\r
+ if (ge == null)\r
+ return null;\r
+ return ge.processors.get(o);\r
+ }\r
+\r
+ @Override\r
+ @SuppressWarnings("unchecked")\r
+ public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {\r
+ return ge.primitiveProcessors.get(o);\r
+ }\r
+\r
+ @SuppressWarnings("unchecked")\r
+ @Override\r
+ public <T> DataSource<T> getDataSource(Class<T> clazz) {\r
+ return ge.dataSources.get(clazz);\r
+ }\r
+\r
+ @Override\r
+ public void update(UIElementReference ref) {\r
+ if (ref instanceof ViewerCellReference) {\r
+ ViewerCellReference tiref = (ViewerCellReference) ref;\r
+ Object element = tiref.getElement();\r
+ int columnIndex = tiref.getColumn();\r
+ // NOTE: must be called regardless of the the item value.\r
+ // A null item is currently used to indicate a tree root update.\r
+ ge.update((TreeNode)element,columnIndex);\r
+ } else if (ref instanceof ViewerRowReference) {\r
+ ViewerRowReference rref = (ViewerRowReference)ref;\r
+ Object element = rref.getElement();\r
+ ge.update((TreeNode)element);\r
+ } else {\r
+ throw new IllegalArgumentException("Ui Reference is unknkown " + ref);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public Object getPropagateLock() {\r
+ return propagate;\r
+ }\r
+\r
+ @Override\r
+ public Object getPropagateListLock() {\r
+ return propagateList;\r
+ }\r
+\r
+ @Override\r
+ public boolean isPropagating() {\r
+ return propagating.get();\r
+ }\r
+\r
+ @Override\r
+ public void setPropagating(boolean b) {\r
+ this.propagating.set(b);\r
+ }\r
+\r
+ @Override\r
+ public List<Runnable> getScheduleList() {\r
+ return scheduleList;\r
+ }\r
+\r
+ @Override\r
+ public void setScheduleList(List<Runnable> list) {\r
+ this.scheduleList = list;\r
+ }\r
+\r
+ @Override\r
+ public Deque<Integer> getActivity() {\r
+ return activity;\r
+ }\r
+\r
+ @Override\r
+ public void setActivityInt(int i) {\r
+ this.activityInt = i;\r
+ }\r
+\r
+ @Override\r
+ public int getActivityInt() {\r
+ return activityInt;\r
+ }\r
+\r
+ @Override\r
+ public void scheduleQueryUpdate(Runnable r) {\r
+ if (ge == null)\r
+ return;\r
+ if (ge.isDisposed())\r
+ return;\r
+ if (currentQueryUpdater.compareAndSet(null, r)) {\r
+ ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER);\r
+ }\r
+ }\r
+\r
+ Runnable QUERY_UPDATE_SCHEDULER = new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ Runnable r = currentQueryUpdater.getAndSet(null);\r
+ if (r != null) {\r
+ r.run();\r
+ }\r
+ }\r
+ };\r
+ \r
+ @Override\r
+ public void dispose() {\r
+ cache.dispose();\r
+ cache = new DummyCache();\r
+ scheduleList.clear();\r
+ autoExpanded.clear();\r
+ autoExpanded = null;\r
+ ge = null;\r
+ \r
+ }\r
+\r
+ }\r
+ \r
+ \r
+ \r
+ \r
+ private static class GeViewerContentProvider implements ITreeContentProvider {\r
+ @Override\r
+ public Object[] getElements(Object inputElement) {\r
+ return getChildren(inputElement);\r
+ }\r
+ \r
+ @Override\r
+ public Object[] getChildren(Object element) {\r
+ TreeNode node = (TreeNode)element;\r
+ return node.getChildren().toArray();\r
+ \r
+ }\r
+ \r
+ @Override\r
+ public Object getParent(Object element) {\r
+ TreeNode node = (TreeNode)element;\r
+ return node.getParent();\r
+ }\r
+ \r
+ @Override\r
+ public boolean hasChildren(Object element) {\r
+ return getChildren(element).length > 0;\r
+ }\r
+ \r
+ @Override\r
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {\r
+ \r
+ }\r
+ \r
+ @Override\r
+ public void dispose() {\r
+ \r
+ }\r
+ }\r
+ \r
+ private class GeViewerLabelProvider extends CellLabelProvider {\r
+ private TreeNode node;\r
+ \r
+ private Labeler labeler;\r
+ private Imager imager;\r
+ Collection<LabelDecorator> labelDecorators;\r
+ Collection<ImageDecorator> imageDecorators;\r
+ \r
+ Map<String, String> labels;\r
+ Map<String, String> runtimeLabels;\r
+ @Override\r
+ public void update(ViewerCell cell) {\r
+ TreeNode node = (TreeNode)cell.getElement();\r
+ NodeContext ctx = node.getContext();\r
+ int columnIndex = cell.getColumnIndex();\r
+ String columnKey = columns[columnIndex].getKey();\r
+ \r
+ // using columnIndex 0 to refresh data.\r
+ // Note that this does not work if ViewerCellReferences are used. (At the moment there is no code that would use them).\r
+ if (node != this.node || columnIndex == 0) { \r
+ this.node = node;\r
+ GENodeQueryManager manager = node.getManager();\r
+ labeler = manager.query(ctx, BuiltinKeys.SELECTED_LABELER);\r
+ imager = manager.query(ctx, BuiltinKeys.SELECTED_IMAGER);\r
+ labelDecorators = manager.query(ctx, BuiltinKeys.LABEL_DECORATORS);\r
+ imageDecorators = manager.query(ctx, BuiltinKeys.IMAGE_DECORATORS);\r
+ \r
+ if (labeler != null) {\r
+ labels = labeler.getLabels();\r
+ runtimeLabels = labeler.getRuntimeLabels();\r
+ } else {\r
+ labels = null;\r
+ runtimeLabels = null;\r
+ }\r
+ }\r
+\r
+ //if (DEBUG) System.out.println("GeViewerLabelProvider.update " + context + " " + columnIndex);\r
+ \r
+ setText(cell, columnKey);\r
+ setImage(cell, columnKey);\r
+ }\r
+\r
+ void setImage(ViewerCell cell, String columnKey) {\r
+ if (imager != null) {\r
+ Object descOrImage = null;\r
+ boolean hasUncachedImages = false;\r
+\r
+ ImageDescriptor desc = imager.getImage(columnKey);\r
+ if (desc != null) {\r
+ int index = 0;\r
+ // Attempt to decorate the label\r
+ if (!imageDecorators.isEmpty()) {\r
+ for (ImageDecorator id : imageDecorators) {\r
+ ImageDescriptor ds = id.decorateImage(desc, columnKey, index);\r
+ if (ds != null)\r
+ desc = ds;\r
+ }\r
+ }\r
+\r
+ // Try resolving only cached images here and now\r
+ Object img = localResourceManager.find(desc);\r
+ if (img == null)\r
+ img = resourceManager.find(desc);\r
+\r
+ descOrImage = img != null ? img : desc;\r
+ hasUncachedImages |= img == null;\r
+ }\r
+\r
+ if (!hasUncachedImages) {\r
+ cell.setImage((Image) descOrImage);\r
+ } else {\r
+ // Schedule loading to another thread to refrain from\r
+ // blocking\r
+ // the UI with database operations.\r
+ queueImageTask(node, new ImageTask(node, descOrImage));\r
+ }\r
+ } else {\r
+ cell.setImage(null);\r
+ }\r
+ }\r
+\r
+ private void queueImageTask(TreeNode node, ImageTask task) {\r
+ synchronized (imageTasks) {\r
+ imageTasks.put(node, task);\r
+ }\r
+ imageLoaderJob.scheduleIfNecessary(100);\r
+ }\r
+\r
+ void setText(ViewerCell cell, String key) {\r
+ if (labeler != null) {\r
+ String s = null;\r
+ if (runtimeLabels != null)\r
+ s = runtimeLabels.get(key);\r
+ if (s == null)\r
+ s = labels.get(key);\r
+ //if (DEBUG) System.out.println(cell.getElement() + " " + cell.getColumnIndex() + " label:" + s + " key:" + key + " " + labels.size() + " " + (runtimeLabels == null ? "-1" : runtimeLabels.size()));\r
+ if (s != null) {\r
+ FontDescriptor font = originalFont;\r
+ ColorDescriptor bg = originalBackground;\r
+ ColorDescriptor fg = originalForeground;\r
+ \r
+ // Attempt to decorate the label\r
+ if (!labelDecorators.isEmpty()) {\r
+ int index = 0;\r
+ for (LabelDecorator ld : labelDecorators) {\r
+ String ds = ld.decorateLabel(s, key, index);\r
+ if (ds != null)\r
+ s = ds;\r
+\r
+ FontDescriptor dfont = ld.decorateFont(font, key, index);\r
+ if (dfont != null)\r
+ font = dfont;\r
+\r
+ ColorDescriptor dbg = ld.decorateBackground(bg, key, index);\r
+ if (dbg != null)\r
+ bg = dbg;\r
+\r
+ ColorDescriptor dfg = ld.decorateForeground(fg, key, index);\r
+ if (dfg != null)\r
+ fg = dfg;\r
+ }\r
+ }\r
+\r
+ if (font != originalFont) {\r
+ // System.out.println("set font: " + index + ": " +\r
+ // font);\r
+ cell.setFont((Font) localResourceManager.get(font));\r
+ } else {\r
+ cell.setFont((Font) (originalFont != null ? localResourceManager.get(originalFont) : null));\r
+ }\r
+ if (bg != originalBackground)\r
+ cell.setBackground((Color) localResourceManager.get(bg));\r
+ else\r
+ cell.setBackground((Color) (originalBackground != null ? localResourceManager.get(originalBackground) : null));\r
+ if (fg != originalForeground)\r
+ cell.setForeground((Color) localResourceManager.get(fg));\r
+ else\r
+ cell.setForeground((Color) (originalForeground != null ? localResourceManager.get(originalForeground) : null));\r
+\r
+ cell.setText(s);\r
+ }\r
+ } else {\r
+ cell.setText(Labeler.NO_LABEL);\r
+ }\r
+ \r
+ }\r
+ }\r
+ \r
+ \r
+ \r
+ private class GeEditingSupport extends EditingSupport {\r
+ private Object lastElement;\r
+ \r
+ private Modifier lastModifier;\r
+ \r
+ private int columnIndex;\r
+ public GeEditingSupport(ColumnViewer viewer, int columnIndex) {\r
+ super(viewer);\r
+ this.columnIndex = columnIndex;\r
+ }\r
+\r
+ @Override\r
+ protected boolean canEdit(Object element) {\r
+ if (filterSelectionEdit && !selectedNodes.contains(element)) {\r
+ // When item is clicked, canEdit is called before the selection is updated.\r
+ // This allows filtering edit attempts when the item is selected. \r
+ return false;\r
+ }\r
+ lastElement = null; // clear cached element + modifier.\r
+ Modifier modifier = getModifier((TreeNode)element);\r
+ if (modifier == null)\r
+ return false;\r
+ return true;\r
+ }\r
+ \r
+ @Override\r
+ protected CellEditor getCellEditor(Object element) {\r
+ TreeNode node = (TreeNode) element;\r
+ Modifier modifier = getModifier((TreeNode)element);\r
+ NodeContext context = node.getContext();\r
+ if (modifier instanceof DialogModifier) {\r
+ return performDialogEditing(context, (DialogModifier) modifier);\r
+ } else if (modifier instanceof CustomModifier) {\r
+ return startCustomEditing(node, (CustomModifier) modifier);\r
+ } else if (modifier instanceof EnumerationModifier) {\r
+ return startEnumerationEditing((EnumerationModifier) modifier);\r
+ } else {\r
+ return startTextEditing(modifier);\r
+ }\r
+ \r
+ }\r
+ \r
+ @Override\r
+ protected Object getValue(Object element) {\r
+ Modifier modifier = getModifier((TreeNode)element);\r
+ return modifier.getValue();\r
+ }\r
+ @Override\r
+ protected void setValue(Object element, Object value) {\r
+ Modifier modifier = getModifier((TreeNode)element);\r
+ // CustomModifiers have internal set value mechanism.\r
+ if (!(modifier instanceof CustomModifier))\r
+ modifier.modify((String)value);\r
+ \r
+ }\r
+ \r
+ CellEditor startTextEditing( Modifier modifier) {\r
+ TextCellEditor editor = new ValidatedTextEditor(viewer.getTree());//new TextCellEditor(viewer.getTree());\r
+ editor.setValidator(new ModifierValidator(modifier));\r
+ return editor;\r
+ }\r
+ \r
+ CellEditor startEnumerationEditing(EnumerationModifier modifier) {\r
+ if (SimanticsUI.isLinuxGTK()) {\r
+ // ComboBoxCellEditor2 does not work when GEImpl2 is embedded into dialog (It does work in SelectionView)\r
+ // CBCE2 does not work because it receives a focus lost event when the combo/popup is opened. \r
+ return new EnumModifierEditor(viewer.getTree(),modifier);\r
+ } else {\r
+ return new EnumModifierEditor2(viewer.getTree(),modifier);\r
+ }\r
+ }\r
+ \r
+ CellEditor performDialogEditing(final NodeContext context, final DialogModifier modifier) {\r
+ DialogCellEditor editor = new DialogCellEditor(viewer.getTree()) {\r
+ String res = null;\r
+ @Override\r
+ protected Object openDialogBox(Control cellEditorWindow) {\r
+\r
+ final Semaphore sem = new Semaphore(1);\r
+ Consumer<String> callback = result -> {\r
+ res = result;\r
+ sem.release();\r
+ };\r
+ String status = modifier.query(cellEditorWindow, null, columnIndex, context, callback);\r
+ if (status != null)\r
+ return null;\r
+ try {\r
+ sem.acquire();\r
+ } catch (InterruptedException e) {\r
+ e.printStackTrace();\r
+ }\r
+ return res;\r
+ }\r
+ \r
+ \r
+ };\r
+ editor.setValidator(new ModifierValidator(modifier));\r
+ return editor;\r
+ }\r
+ \r
+ CellEditor startCustomEditing(TreeNode node, CustomModifier modifier) {\r
+ CustomModifierEditor editor = new CustomModifierEditor(viewer.getTree(), modifier, node, columnIndex);\r
+ return editor;\r
+ }\r
+ \r
+ private Modifier getModifier(TreeNode element) {\r
+ if (element == lastElement)\r
+ return lastModifier;\r
+ lastModifier = GraphExplorerImpl2.this.getModifier(element, columnIndex);\r
+ lastElement = element;\r
+ return lastModifier;\r
+ }\r
+ \r
+ \r
+ }\r
+ \r
+ private Modifier getModifier(TreeNode element, int columnIndex) {\r
+ GENodeQueryManager manager = element.getManager();\r
+ final NodeContext context = element.getContext();\r
+ Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);\r
+ if (labeler == null)\r
+ return null;\r
+ Column column = columns[columnIndex];\r
+\r
+ return labeler.getModifier(modificationContext, column.getKey());\r
+\r
+ }\r
+\r
+ static class ImageTask {\r
+ TreeNode node;\r
+ Object descsOrImage;\r
+ public ImageTask(TreeNode node, Object descsOrImage) {\r
+ this.node = node;\r
+ this.descsOrImage = descsOrImage;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * The job that is used for off-loading image loading tasks (see\r
+ * {@link ImageTask} to a worker thread from the main UI thread.\r
+ */\r
+ ImageLoaderJob imageLoaderJob;\r
+ \r
+ // Map<NodeContext, ImageTask> imageTasks = new THashMap<NodeContext, ImageTask>();\r
+ Map<TreeNode, ImageTask> imageTasks = new THashMap<TreeNode, ImageTask>();\r
+ \r
+ /**\r
+ * Invoked in a job worker thread.\r
+ * \r
+ * @param monitor\r
+ */\r
+ @Override\r
+ protected IStatus setPendingImages(IProgressMonitor monitor) {\r
+ ImageTask[] tasks = null;\r
+ synchronized (imageTasks) {\r
+ tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]);\r
+ imageTasks.clear();\r
+ }\r
+\r
+ MultiStatus status = null;\r
+\r
+ // Load missing images\r
+ for (ImageTask task : tasks) {\r
+ Object desc = task.descsOrImage;\r
+ if (desc instanceof ImageDescriptor) {\r
+ try {\r
+ desc = resourceManager.get((ImageDescriptor) desc);\r
+ task.descsOrImage = desc;\r
+ } catch (DeviceResourceException e) {\r
+ if (status == null)\r
+ status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null);\r
+ status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e));\r
+ }\r
+ }\r
+ \r
+ }\r
+\r
+ // Perform final UI updates in the UI thread.\r
+ final ImageTask[] _tasks = tasks;\r
+ thread.asyncExec(new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ setImages(_tasks);\r
+ }\r
+ });\r
+\r
+ return status != null ? status : Status.OK_STATUS;\r
+ }\r
+ \r
+\r
+ void setImages(ImageTask[] tasks) {\r
+ for (ImageTask task : tasks)\r
+ if (task != null)\r
+ setImage(task);\r
+ }\r
+ \r
+ void setImage(ImageTask task) {\r
+ if (!task.node.isDisposed())\r
+ update(task.node, 0);\r
+ }\r
+ \r
+ private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider {\r
+ \r
+ private GraphExplorerImpl2 ge;\r
+ \r
+ GraphExplorerPostSelectionProvider(GraphExplorerImpl2 ge) {\r
+ this.ge = ge;\r
+ }\r
+ \r
+ void dispose() {\r
+ ge = null;\r
+ }\r
+ \r
+ @Override\r
+ public void setSelection(final ISelection selection) {\r
+ if(ge == null) return;\r
+ ge.setSelection(selection, false);\r
+ \r
+ }\r
+ \r
+\r
+ @Override\r
+ public void removeSelectionChangedListener(ISelectionChangedListener listener) {\r
+ if(ge == null) return;\r
+ if(ge.isDisposed()) {\r
+ if (DEBUG_SELECTION_LISTENERS)\r
+ System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener);\r
+ return;\r
+ }\r
+ ge.selectionProvider.removeSelectionChangedListener(listener);\r
+ }\r
+ \r
+ @Override\r
+ public void addPostSelectionChangedListener(ISelectionChangedListener listener) {\r
+ if(ge == null) return;\r
+ if (!ge.thread.currentThreadAccess())\r
+ throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());\r
+ if(ge.isDisposed()) {\r
+ System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener);\r
+ return;\r
+ }\r
+ ge.selectionProvider.addPostSelectionChangedListener(listener);\r
+ }\r
+\r
+ @Override\r
+ public void removePostSelectionChangedListener(ISelectionChangedListener listener) {\r
+ if(ge == null) return;\r
+ if(ge.isDisposed()) {\r
+ if (DEBUG_SELECTION_LISTENERS)\r
+ System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener);\r
+ return;\r
+ }\r
+ ge.selectionProvider.removePostSelectionChangedListener(listener);\r
+ }\r
+ \r
+\r
+ @Override\r
+ public void addSelectionChangedListener(ISelectionChangedListener listener) {\r
+ if(ge == null) return;\r
+ if (!ge.thread.currentThreadAccess())\r
+ throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());\r
+ if (ge.viewer.getTree().isDisposed() || ge.selectionProvider == null) {\r
+ System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener);\r
+ return;\r
+ }\r
+\r
+ ge.selectionProvider.addSelectionChangedListener(listener);\r
+ }\r
+\r
+ \r
+ @Override\r
+ public ISelection getSelection() {\r
+ if(ge == null) return StructuredSelection.EMPTY;\r
+ if (!ge.thread.currentThreadAccess())\r
+ throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread());\r
+ if (ge.viewer.getTree().isDisposed() || ge.selectionProvider == null)\r
+ return StructuredSelection.EMPTY;\r
+ return ge.selectionProvider.getSelection();\r
+ }\r
+ \r
+ }\r
+ \r
+ static class ModifierValidator implements ICellEditorValidator {\r
+ private Modifier modifier;\r
+ public ModifierValidator(Modifier modifier) {\r
+ this.modifier = modifier;\r
+ }\r
+ \r
+ @Override\r
+ public String isValid(Object value) {\r
+ return modifier.isValid((String)value);\r
+ }\r
+ }\r
+ \r
+ static class UpdateRunner implements Runnable {\r
+\r
+ final GraphExplorerImpl2 ge;\r
+\r
+ UpdateRunner(GraphExplorerImpl2 ge, IGraphExplorerContext geContext) {\r
+ this.ge = ge;\r
+ }\r
+\r
+ public void run() {\r
+ try {\r
+ doRun();\r
+ } catch (Throwable t) {\r
+ t.printStackTrace();\r
+ }\r
+ }\r
+\r
+ public void doRun() {\r
+ \r
+ if (ge.isDisposed())\r
+ return;\r
+\r
+ HashSet<UpdateItem> items;\r
+\r
+ ScrollBar verticalBar = ge.viewer.getTree().getVerticalBar();\r
+ \r
+ \r
+ synchronized (ge.pendingItems) {\r
+ items = ge.pendingItems;\r
+ ge.pendingItems = new HashSet<UpdateItem>();\r
+ }\r
+ if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size());\r
+\r
+ ge.viewer.getTree().setRedraw(false);\r
+ for (UpdateItem item : items) {\r
+ item.update(ge.viewer);\r
+ }\r
+ \r
+ // check if vertical scroll bar has become visible and refresh layout.\r
+ boolean currentlyVerticalBarVisible = verticalBar.isVisible();\r
+ if (ge.verticalBarVisible != currentlyVerticalBarVisible) {\r
+ ge.verticalBarVisible = currentlyVerticalBarVisible;\r
+ ge.viewer.getTree().getParent().layout();\r
+ }\r
+ \r
+ ge.viewer.getTree().setRedraw(true);\r
+ \r
+ synchronized (ge.pendingItems) {\r
+ if (!ge.scheduleUpdater()) {\r
+ ge.updating = false;\r
+ }\r
+ }\r
+ if (DEBUG) {\r
+ if (!ge.updating) {\r
+ ge.printTree(ge.rootNode, 0);\r
+ }\r
+ }\r
+ }\r
+\r
+ }\r
+ \r
+ private class ValidatedTextEditor extends TextCellEditor {\r
+ \r
+\r
+ public ValidatedTextEditor(Composite parent) {\r
+ super(parent);\r
+ }\r
+\r
+ protected void editOccured(org.eclipse.swt.events.ModifyEvent e) {\r
+ String value = text.getText();\r
+ if (value == null) {\r
+ value = "";//$NON-NLS-1$\r
+ }\r
+ Object typedValue = value;\r
+ boolean oldValidState = isValueValid();\r
+ boolean newValidState = isCorrect(typedValue);\r
+ if (!newValidState) {\r
+ text.setBackground(invalidModificationColor);\r
+ text.setToolTipText(getErrorMessage());\r
+ } else {\r
+ text.setBackground(null);\r
+ text.setToolTipText(null);\r
+ }\r
+ valueChanged(oldValidState, newValidState);\r
+ };\r
+ }\r
+ \r
+ private class EnumModifierEditor2 extends ComboBoxCellEditor2 {\r
+ \r
+ List<String> values;\r
+ public EnumModifierEditor2(Composite parent, EnumerationModifier modifier) {\r
+ super(parent,modifier.getValues().toArray(new String[modifier.getValues().size()]),SWT.READ_ONLY);\r
+ values = modifier.getValues();\r
+ setValidator(new ModifierValidator(modifier));\r
+ }\r
+ @Override\r
+ protected void doSetValue(Object value) {\r
+ super.doSetValue((Integer)values.indexOf(value));\r
+ }\r
+ \r
+ @Override\r
+ protected Object doGetValue() {\r
+ return values.get((Integer)super.doGetValue());\r
+ }\r
+ };\r
+ \r
+ private class EnumModifierEditor extends ComboBoxCellEditor {\r
+ \r
+ List<String> values;\r
+ public EnumModifierEditor(Composite parent, EnumerationModifier modifier) {\r
+ super(parent,modifier.getValues().toArray(new String[modifier.getValues().size()]),SWT.READ_ONLY);\r
+ values = modifier.getValues();\r
+ setValidator(new ModifierValidator(modifier));\r
+ }\r
+ @Override\r
+ protected void doSetValue(Object value) {\r
+ super.doSetValue((Integer)values.indexOf(value));\r
+ }\r
+ \r
+ @Override\r
+ protected Object doGetValue() {\r
+ return values.get((Integer)super.doGetValue());\r
+ }\r
+ };\r
+ \r
+ \r
+ private class CustomModifierEditor extends CellEditor implements ICellEditorValidator, DisposeListener {\r
+ private CustomModifier modifier;\r
+ private TreeNode node;\r
+ private NodeContext context;\r
+ private int columnIndex;\r
+ private Composite control;\r
+ private Control origControl;\r
+ \r
+ public CustomModifierEditor(Composite parent, CustomModifier modifier, TreeNode node, int columnIndex) {\r
+ this.modifier = modifier;\r
+ this.node = node;\r
+ this.context = node.getContext();\r
+ this.columnIndex = columnIndex;\r
+ setValidator(this);\r
+ create(parent);\r
+ }\r
+ \r
+ @Override\r
+ protected Control createControl(Composite parent) {\r
+ control = new Composite(parent, SWT.NONE);\r
+ control.setLayout(new FillLayout());\r
+ origControl = (Control) modifier.createControl(control, null, columnIndex, context);\r
+ return control;\r
+ }\r
+ \r
+ \r
+ \r
+ @Override\r
+ protected Object doGetValue() {\r
+ return modifier.getValue();\r
+ }\r
+ \r
+ @Override\r
+ protected void doSetValue(Object value) {\r
+ //CustomModifier handles value setting internally.\r
+ }\r
+ \r
+ \r
+ private void reCreate() {\r
+ modifier = (CustomModifier)getModifier(node, columnIndex);\r
+ if (control != null && !control.isDisposed()) {\r
+ if (!origControl.isDisposed())\r
+ origControl.dispose();\r
+ origControl = (Control)modifier.createControl(control, null, columnIndex, context);\r
+ origControl.addDisposeListener(this);\r
+ }\r
+ }\r
+ protected void doSetFocus() {\r
+ if (control != null && !control.isDisposed())\r
+ control.setFocus();\r
+ };\r
+ \r
+ @Override\r
+ public void widgetDisposed(DisposeEvent e) {\r
+ if (e.widget == origControl) {\r
+ reCreate();\r
+ }\r
+ \r
+ }\r
+ \r
+ @Override\r
+ public String isValid(Object value) {\r
+ return modifier.isValid((String)value);\r
+ }\r
+\r
+ }\r
+ \r
+ private class TreeNode implements IAdaptable {\r
+ private NodeContext context;\r
+ \r
+ private TreeNode parent;\r
+ private List<TreeNode> children = new ArrayList<TreeNode>();\r
+ private GENodeQueryManager manager;\r
+ \r
+ private TreeNode(NodeContext context) {\r
+ if (context == null)\r
+ throw new NullPointerException();\r
+ this.context = context;\r
+ contextToNodeMap.add(context, this);\r
+ manager = new GENodeQueryManager(explorerContext, null, null, ViewerRowReference.create(this));\r
+ }\r
+ \r
+ public List<TreeNode> getChildren() {\r
+ synchronized (children) {\r
+ return children;\r
+ }\r
+ }\r
+ \r
+ public TreeNode getParent() {\r
+ return parent;\r
+ }\r
+ \r
+ public NodeContext getContext() {\r
+ return context;\r
+ }\r
+ \r
+ public GENodeQueryManager getManager() {\r
+ return manager;\r
+ }\r
+ \r
+ public TreeNode addChild(NodeContext context) {\r
+ TreeNode child = new TreeNode(context);\r
+ child.parent = this;\r
+ children.add(child);\r
+ if (DEBUG) System.out.println("Add " + this + " -> " + child);\r
+ return child;\r
+ }\r
+ \r
+ public TreeNode addChild(int index, NodeContext context) {\r
+ \r
+ TreeNode child = new TreeNode(context);\r
+ child.parent = this;\r
+ children.add(index,child);\r
+ if (DEBUG) System.out.println("Add " + this + " -> " + child + " at " + index);\r
+ return child;\r
+ }\r
+ \r
+ public TreeNode setChild(int index, NodeContext context) {\r
+ \r
+ TreeNode child = new TreeNode(context);\r
+ child.parent = this;\r
+ children.set(index,child);\r
+ if (DEBUG) System.out.println("Set " + this + " -> " + child + " at " + index);\r
+ return child;\r
+ }\r
+ \r
+ public int distanceToRoot() {\r
+ int i = 0;\r
+ TreeNode n = getParent();\r
+ while (n != null) {\r
+ n = n.getParent();\r
+ i++;\r
+ }\r
+ return i;\r
+ \r
+ }\r
+ \r
+ public void dispose() {\r
+ if (parent != null)\r
+ parent.children.remove(this);\r
+ dispose2();\r
+ }\r
+ \r
+ public void dispose2() {\r
+ if (DEBUG) System.out.println("dispose " + this);\r
+ parent = null;\r
+ for (TreeNode n : children) {\r
+ n.dispose2();\r
+ }\r
+ clearCache();\r
+ children.clear();\r
+ contextToNodeMap.remove(context, this);\r
+ context = null;\r
+ manager.dispose();\r
+ manager = null; \r
+ }\r
+ \r
+ private void clearCache() {\r
+ if (explorerContext != null) {\r
+ GECache2 cache = explorerContext.cache;\r
+ \r
+ if (cache != null) {\r
+ cache.dispose(context);\r
+ }\r
+ }\r
+ }\r
+ \r
+ public boolean updateChildren() {\r
+ if (context == null)\r
+ throw new IllegalStateException("Node is disposed.");\r
+ \r
+ NodeContext[] childContexts = manager.query(context, BuiltinKeys.FINAL_CHILDREN);\r
+ \r
+ if (DEBUG) System.out.println("updateChildren " + childContexts.length + " " + this);\r
+ \r
+ \r
+ boolean modified = false;\r
+ synchronized (children) {\r
+ \r
+ int oldCount = children.size();\r
+ BijectionMap<Integer, Integer> indexes = new BijectionMap<Integer, Integer>();\r
+ Set<Integer> mapped = new HashSet<Integer>();\r
+ boolean reorder = false;\r
+ // locate matching pairs form old and new children\r
+ for (int i = 0; i < oldCount; i++) {\r
+ NodeContext oldCtx = children.get(i).context;\r
+ for (int j = 0; j <childContexts.length; j++) {\r
+ if (mapped.contains(j))\r
+ continue;\r
+ if (oldCtx.equals(childContexts[j])) {\r
+ indexes.map(i, j);\r
+ mapped.add(j);\r
+ if (i != j)\r
+ reorder = true;\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ // update children if required\r
+ if (childContexts.length != oldCount || reorder || childContexts.length != indexes.size()) {\r
+ modified = true;\r
+ List<TreeNode> oldChildren = new ArrayList<TreeNode>(oldCount);\r
+ oldChildren.addAll(children);\r
+ if (childContexts.length >= oldCount) {\r
+ for (int i = 0; i < oldCount; i++) {\r
+ Integer oldIndex = indexes.getLeft(i);\r
+ if (oldIndex == null) {\r
+ setChild(i, childContexts[i]);\r
+ } else {\r
+ TreeNode n = oldChildren.get(oldIndex);\r
+ children.set(i, n);\r
+ }\r
+ \r
+ }\r
+ for (int i = oldCount; i < childContexts.length; i++) {\r
+ addChild(childContexts[i]);\r
+ }\r
+ } else {\r
+ for (int i = 0; i < childContexts.length; i++) {\r
+ Integer oldIndex = indexes.getLeft(i);\r
+ if (oldIndex == null) {\r
+ setChild(i, childContexts[i]);\r
+ } else {\r
+ TreeNode n = oldChildren.get(oldIndex);\r
+ children.set(i, n);\r
+ }\r
+ }\r
+ for (int i = oldCount -1; i >= childContexts.length; i--) {\r
+ children.remove(i);\r
+ }\r
+ }\r
+ for (int i = 0; i < oldChildren.size(); i++) {\r
+ if (!indexes.containsLeft(i)) {\r
+ oldChildren.get(i).dispose2();\r
+ }\r
+ }\r
+ \r
+ }\r
+ \r
+ }\r
+ return modified;\r
+ }\r
+ \r
+ public boolean isDisposed() {\r
+ return context == null;\r
+ }\r
+ \r
+ @SuppressWarnings("rawtypes")\r
+ @Override\r
+ public Object getAdapter(Class adapter) {\r
+ if (adapter == NodeContext.class)\r
+ return context;\r
+ return context.getAdapter(adapter);\r
+ }\r
+ \r
+// @Override\r
+// public String toString() {\r
+// String s = "";\r
+// if (manager != null) {\r
+// \r
+// s+= super.toString() + " ";\r
+// try {\r
+// Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);\r
+// Map<String,String> labels = labeler.getLabels();\r
+// for (Entry<String, String> l : labels.entrySet()) {\r
+// s+= l.getKey() + " : " + l.getValue() + " ";\r
+// }\r
+// } catch (Exception e) {\r
+// \r
+// }\r
+// } else {\r
+// s = super.toString(); \r
+// }\r
+// if (context != null)\r
+// s += " context " + context.hashCode();\r
+// return s;\r
+// \r
+// }\r
+ \r
+ }\r
+ \r
+ private static class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor<Boolean> implements\r
+ IsExpandedProcessor, ProcessorLifecycle {\r
+ /**\r
+ * The set of currently expanded node contexts.\r
+ */\r
+ private final HashSet<NodeContext> expanded = new HashSet<NodeContext>();\r
+ private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();\r
+\r
+ private Tree tree;\r
+\r
+ public TreeNodeIsExpandedProcessor() {\r
+ }\r
+\r
+ @Override\r
+ public Object getIdentifier() {\r
+ return BuiltinKeys.IS_EXPANDED;\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ return "IsExpandedProcessor";\r
+ }\r
+\r
+ @Override\r
+ public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Boolean> key) {\r
+ boolean isExpanded = expanded.contains(context);\r
+ expandedQueries.put(context, updater);\r
+ return Boolean.valueOf(isExpanded);\r
+ }\r
+\r
+ @Override\r
+ public Collection<NodeContext> getExpanded() {\r
+ return new HashSet<NodeContext>(expanded);\r
+ }\r
+\r
+ @Override\r
+ public boolean getExpanded(NodeContext context) {\r
+ return this.expanded.contains(context);\r
+ }\r
+\r
+ @Override\r
+ public boolean setExpanded(NodeContext context, boolean expanded) {\r
+ return _setExpanded(context, expanded);\r
+ }\r
+\r
+ @Override\r
+ public boolean replaceExpanded(NodeContext context, boolean expanded) {\r
+ return nodeStatusChanged(context, expanded);\r
+ }\r
+\r
+ private boolean _setExpanded(NodeContext context, boolean expanded) {\r
+ if (expanded) {\r
+ return this.expanded.add(context);\r
+ } else {\r
+ return this.expanded.remove(context);\r
+ }\r
+ }\r
+\r
+ Listener treeListener = new Listener() {\r
+ @Override\r
+ public void handleEvent(Event event) {\r
+ TreeNode node = (TreeNode) event.item.getData();\r
+ NodeContext context = node.getContext();\r
+ switch (event.type) {\r
+ case SWT.Expand:\r
+ nodeStatusChanged(context, true);\r
+ break;\r
+ case SWT.Collapse:\r
+ nodeStatusChanged(context, false);\r
+ break;\r
+ }\r
+ }\r
+ };\r
+\r
+ protected boolean nodeStatusChanged(NodeContext context, boolean expanded) {\r
+ boolean result = _setExpanded(context, expanded);\r
+ PrimitiveQueryUpdater updater = expandedQueries.get(context);\r
+ if (updater != null)\r
+ updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded);\r
+ return result;\r
+ }\r
+\r
+ @Override\r
+ public void attached(GraphExplorer explorer) {\r
+ Object control = explorer.getControl();\r
+ if (control instanceof Tree) {\r
+ this.tree = (Tree) control;\r
+ tree.addListener(SWT.Expand, treeListener);\r
+ tree.addListener(SWT.Collapse, treeListener);\r
+ } else {\r
+ System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void clear() {\r
+ expanded.clear();\r
+ expandedQueries.clear();\r
+ }\r
+\r
+ @Override\r
+ public void detached(GraphExplorer explorer) {\r
+ clear();\r
+ if (tree != null) {\r
+ tree.removeListener(SWT.Expand, treeListener);\r
+ tree.removeListener(SWT.Collapse, treeListener);\r
+ tree = null;\r
+ }\r
+ }\r
+ }\r
+ \r
+ private void printTree(TreeNode node, int depth) {\r
+ String s = "";\r
+ for (int i = 0; i < depth; i++) {\r
+ s += " ";\r
+ }\r
+ s += node;\r
+ System.out.println(s);\r
+ int d = depth+1;\r
+ for (TreeNode n : node.getChildren()) {\r
+ printTree(n, d);\r
+ }\r
+ \r
+ }\r
+ \r
+ /**\r
+ * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used)\r
+ */\r
+ final private static class GECacheKey {\r
+\r
+ private NodeContext context;\r
+ private CacheKey<?> key;\r
+\r
+ GECacheKey(NodeContext context, CacheKey<?> key) {\r
+ this.context = context;\r
+ this.key = key;\r
+ if (context == null || key == null)\r
+ throw new IllegalArgumentException("Null context or key is not accepted");\r
+ }\r
+\r
+ GECacheKey(GECacheKey other) {\r
+ this.context = other.context;\r
+ this.key = other.key;\r
+ if (context == null || key == null)\r
+ throw new IllegalArgumentException("Null context or key is not accepted");\r
+ }\r
+\r
+ void setValues(NodeContext context, CacheKey<?> key) {\r
+ this.context = context;\r
+ this.key = key;\r
+ if (context == null || key == null)\r
+ throw new IllegalArgumentException("Null context or key is not accepted");\r
+ }\r
+\r
+ @Override\r
+ public int hashCode() {\r
+ return context.hashCode() | key.hashCode();\r
+ }\r
+\r
+ @Override\r
+ public boolean equals(Object object) {\r
+\r
+ if (this == object)\r
+ return true;\r
+ else if (object == null)\r
+ return false;\r
+\r
+ GECacheKey i = (GECacheKey) object;\r
+\r
+ return key.equals(i.key) && context.equals(i.context);\r
+\r
+ }\r
+\r
+ };\r
+ \r
+ /**\r
+ * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data.\r
+ */\r
+ private static class GECache2 implements IGECache {\r
+ \r
+ final HashMap<GECacheKey, IGECacheEntry> entries = new HashMap<GECacheKey, IGECacheEntry>();\r
+ final HashMap<GECacheKey, Set<UIElementReference>> treeReferences = new HashMap<GECacheKey, Set<UIElementReference>>();\r
+ final HashMap<NodeContext, Set<GECacheKey>> keyRefs = new HashMap<NodeContext, Set<GECacheKey>>();\r
+ \r
+ /**\r
+ * This single instance is used for all get operations from the cache. This\r
+ * should work since the GE cache is meant to be single-threaded within the\r
+ * current UI thread, what ever that thread is. For put operations which\r
+ * store the key, this is not used.\r
+ */\r
+ NodeContext getNC = new NodeContext() {\r
+ @SuppressWarnings("rawtypes")\r
+ @Override\r
+ public Object getAdapter(Class adapter) {\r
+ return null;\r
+ }\r
+ \r
+ @Override\r
+ public <T> T getConstant(ConstantKey<T> key) {\r
+ return null;\r
+ }\r
+ \r
+ @Override\r
+ public Set<ConstantKey<?>> getKeys() {\r
+ return Collections.emptySet();\r
+ }\r
+ };\r
+ CacheKey<?> getCK = new CacheKey<Object>() {\r
+ @Override\r
+ public Object processorIdenfitier() {\r
+ return this;\r
+ }\r
+ };\r
+ GECacheKey getKey = new GECacheKey(getNC, getCK);\r
+ \r
+ \r
+ private void addKey(GECacheKey key) {\r
+ Set<GECacheKey> refs = keyRefs.get(key.context);\r
+ if (refs != null) {\r
+ refs.add(key);\r
+ } else {\r
+ refs = new HashSet<GECacheKey>();\r
+ refs.add(key);\r
+ keyRefs.put(key.context, refs);\r
+ }\r
+ }\r
+ \r
+ private void removeKey(GECacheKey key) {\r
+ Set<GECacheKey> refs = keyRefs.get(key.context);\r
+ if (refs != null) {\r
+ refs.remove(key);\r
+ } \r
+ }\r
+\r
+ public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key, T value) {\r
+// if (DEBUG) System.out.println("Add entry " + context + " " + key);\r
+ IGECacheEntry entry = new GECacheEntry(context, key, value);\r
+ GECacheKey gekey = new GECacheKey(context, key);\r
+ entries.put(gekey, entry);\r
+ addKey(gekey);\r
+ return entry;\r
+ }\r
+\r
+ @SuppressWarnings("unchecked")\r
+ public <T> T get(NodeContext context, CacheKey<T> key) {\r
+ getKey.setValues(context, key);\r
+ IGECacheEntry entry = entries.get(getKey);\r
+ if (entry == null)\r
+ return null;\r
+ return (T) entry.getValue();\r
+ }\r
+\r
+ @Override\r
+ public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {\r
+ assert(context != null);\r
+ assert(key != null);\r
+ getKey.setValues(context, key);\r
+ return entries.get(getKey);\r
+ }\r
+\r
+ @Override\r
+ public <T> void remove(NodeContext context, CacheKey<T> key) {\r
+// if (DEBUG) System.out.println("Remove entry " + context + " " + key);\r
+ getKey.setValues(context, key);\r
+ entries.remove(getKey);\r
+ removeKey(getKey);\r
+ }\r
+\r
+ @Override\r
+ public <T> Set<UIElementReference> getTreeReference(NodeContext context, CacheKey<T> key) {\r
+ assert(context != null);\r
+ assert(key != null);\r
+ getKey.setValues(context, key);\r
+ return treeReferences.get(getKey);\r
+ }\r
+\r
+ @Override\r
+ public <T> void putTreeReference(NodeContext context, CacheKey<T> key, UIElementReference reference) {\r
+ assert(context != null);\r
+ assert(key != null);\r
+ //if (DEBUG) System.out.println("Add tree reference " + context + " " + key);\r
+ getKey.setValues(context, key);\r
+ Set<UIElementReference> refs = treeReferences.get(getKey);\r
+ if (refs != null) {\r
+ refs.add(reference);\r
+ } else {\r
+ refs = new HashSet<UIElementReference>(4);\r
+ refs.add(reference);\r
+ GECacheKey gekey = new GECacheKey(getKey);\r
+ treeReferences.put(gekey, refs);\r
+ addKey(gekey);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public <T> Set<UIElementReference> removeTreeReference(NodeContext context, CacheKey<T> key) {\r
+ assert(context != null);\r
+ assert(key != null);\r
+ //if (DEBUG) System.out.println("Remove tree reference " + context + " " + key);\r
+ getKey.setValues(context, key);\r
+ removeKey(getKey);\r
+ return treeReferences.remove(getKey);\r
+ }\r
+ \r
+ @Override\r
+ public boolean isShown(NodeContext context) {\r
+ return references.get(context) > 0;\r
+ }\r
+\r
+ private TObjectIntHashMap<NodeContext> references = new TObjectIntHashMap<NodeContext>();\r
+ \r
+ @Override\r
+ public void incRef(NodeContext context) {\r
+ int exist = references.get(context);\r
+ references.put(context, exist+1);\r
+ }\r
+ \r
+ @Override\r
+ public void decRef(NodeContext context) {\r
+ int exist = references.get(context);\r
+ references.put(context, exist-1);\r
+ if(exist == 1) {\r
+ references.remove(context);\r
+ }\r
+ }\r
+ \r
+ public void dispose() {\r
+ references.clear();\r
+ entries.clear();\r
+ treeReferences.clear();\r
+ keyRefs.clear();\r
+ }\r
+ \r
+ public void dispose(NodeContext context) {\r
+ Set<GECacheKey> keys = keyRefs.remove(context);\r
+ if (keys != null) {\r
+ for (GECacheKey key : keys) {\r
+ entries.remove(key);\r
+ treeReferences.remove(key);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ \r
+ \r
+ /**\r
+ * Non-functional cache to replace actual cache when GEContext is disposed.\r
+ * \r
+ * @author mlmarko\r
+ *\r
+ */\r
+ private static class DummyCache extends GECache2 {\r
+\r
+ @Override\r
+ public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {\r
+ return null;\r
+ }\r
+\r
+ @Override\r
+ public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key,\r
+ T value) {\r
+ return null;\r
+ }\r
+\r
+ @Override\r
+ public <T> void putTreeReference(NodeContext context, CacheKey<T> key,\r
+ UIElementReference reference) {\r
+ }\r
+\r
+ @Override\r
+ public <T> T get(NodeContext context, CacheKey<T> key) {\r
+ return null;\r
+ }\r
+\r
+ @Override\r
+ public <T> Set<UIElementReference> getTreeReference(\r
+ NodeContext context, CacheKey<T> key) {\r
+ return null;\r
+ }\r
+\r
+ @Override\r
+ public <T> void remove(NodeContext context, CacheKey<T> key) {\r
+ \r
+ }\r
+\r
+ @Override\r
+ public <T> Set<UIElementReference> removeTreeReference(\r
+ NodeContext context, CacheKey<T> key) {\r
+ return null;\r
+ }\r
+\r
+ @Override\r
+ public boolean isShown(NodeContext context) {\r
+ return false;\r
+ }\r
+\r
+ @Override\r
+ public void incRef(NodeContext context) {\r
+ \r
+ }\r
+\r
+ @Override\r
+ public void decRef(NodeContext context) {\r
+ \r
+ }\r
+ \r
+ @Override\r
+ public void dispose() {\r
+ super.dispose();\r
+ }\r
+ }\r
+}\r