X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.browsing.ui.swt%2Fsrc%2Forg%2Fsimantics%2Fbrowsing%2Fui%2Fswt%2FGraphExplorerImpl2.java;h=c21158e31a65785d8a9dd5d7df728871af63a785;hb=HEAD;hp=7cd8efcfd044eda96e2ad3020aee9b4025fa7fdf;hpb=e02b8761385c8b353cccabab32897353f764cac6;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl2.java b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl2.java index 7cd8efcfd..c21158e31 100644 --- a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl2.java +++ b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl2.java @@ -1,2949 +1,2950 @@ -/******************************************************************************* - * Copyright (c) 2013 Association for Decentralized Information Management - * in Industry THTH ry. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * VTT Technical Research Centre of Finland - initial API and implementation - *******************************************************************************/ -package org.simantics.browsing.ui.swt; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Deque; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.WeakHashMap; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; - -import org.eclipse.core.runtime.Assert; -import org.eclipse.core.runtime.IAdaptable; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.MultiStatus; -import org.eclipse.core.runtime.Platform; -import org.eclipse.core.runtime.Status; -import org.eclipse.core.runtime.jobs.Job; -import org.eclipse.jface.layout.GridDataFactory; -import org.eclipse.jface.layout.TreeColumnLayout; -import org.eclipse.jface.resource.ColorDescriptor; -import org.eclipse.jface.resource.DeviceResourceException; -import org.eclipse.jface.resource.DeviceResourceManager; -import org.eclipse.jface.resource.FontDescriptor; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.resource.JFaceResources; -import org.eclipse.jface.resource.LocalResourceManager; -import org.eclipse.jface.viewers.CellEditor; -import org.eclipse.jface.viewers.CellLabelProvider; -import org.eclipse.jface.viewers.ColumnViewer; -import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent; -import org.eclipse.jface.viewers.ColumnViewerEditorActivationListener; -import org.eclipse.jface.viewers.ColumnViewerEditorDeactivationEvent; -import org.eclipse.jface.viewers.ColumnWeightData; -import org.eclipse.jface.viewers.ComboBoxCellEditor; -import org.eclipse.jface.viewers.DialogCellEditor; -import org.eclipse.jface.viewers.EditingSupport; -import org.eclipse.jface.viewers.ICellEditorValidator; -import org.eclipse.jface.viewers.IPostSelectionProvider; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.ISelectionProvider; -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.ITreeViewerListener; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.StructuredSelection; -import org.eclipse.jface.viewers.TextCellEditor; -import org.eclipse.jface.viewers.TreeExpansionEvent; -import org.eclipse.jface.viewers.TreeSelection; -import org.eclipse.jface.viewers.TreeViewer; -import org.eclipse.jface.viewers.TreeViewerColumn; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.jface.viewers.ViewerCell; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.DisposeEvent; -import org.eclipse.swt.events.DisposeListener; -import org.eclipse.swt.events.FocusEvent; -import org.eclipse.swt.events.FocusListener; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.KeyListener; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.RGB; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Listener; -import org.eclipse.swt.widgets.ScrollBar; -import org.eclipse.swt.widgets.Tree; -import org.eclipse.swt.widgets.TreeColumn; -import org.eclipse.swt.widgets.TreeItem; -import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.contexts.IContextActivation; -import org.eclipse.ui.contexts.IContextService; -import org.eclipse.ui.services.IServiceLocator; -import org.eclipse.ui.swt.IFocusService; -import org.simantics.browsing.ui.BuiltinKeys; -import org.simantics.browsing.ui.Column; -import org.simantics.browsing.ui.Column.Align; -import org.simantics.browsing.ui.DataSource; -import org.simantics.browsing.ui.ExplorerState; -import org.simantics.browsing.ui.GraphExplorer; -import org.simantics.browsing.ui.NodeContext; -import org.simantics.browsing.ui.NodeContext.CacheKey; -import org.simantics.browsing.ui.NodeContext.PrimitiveQueryKey; -import org.simantics.browsing.ui.NodeContext.QueryKey; -import org.simantics.browsing.ui.NodeQueryManager; -import org.simantics.browsing.ui.NodeQueryProcessor; -import org.simantics.browsing.ui.PrimitiveQueryProcessor; -import org.simantics.browsing.ui.PrimitiveQueryUpdater; -import org.simantics.browsing.ui.SelectionDataResolver; -import org.simantics.browsing.ui.SelectionFilter; -import org.simantics.browsing.ui.StatePersistor; -import org.simantics.browsing.ui.common.ColumnKeys; -import org.simantics.browsing.ui.common.ErrorLogger; -import org.simantics.browsing.ui.common.NodeContextBuilder; -import org.simantics.browsing.ui.common.NodeContextUtil; -import org.simantics.browsing.ui.common.internal.GENodeQueryManager; -import org.simantics.browsing.ui.common.internal.IGECache; -import org.simantics.browsing.ui.common.internal.IGraphExplorerContext; -import org.simantics.browsing.ui.common.internal.UIElementReference; -import org.simantics.browsing.ui.common.processors.AbstractPrimitiveQueryProcessor; -import org.simantics.browsing.ui.common.processors.DefaultCheckedStateProcessor; -import org.simantics.browsing.ui.common.processors.DefaultComparableChildrenProcessor; -import org.simantics.browsing.ui.common.processors.DefaultFinalChildrenProcessor; -import org.simantics.browsing.ui.common.processors.DefaultImageDecoratorProcessor; -import org.simantics.browsing.ui.common.processors.DefaultImagerFactoriesProcessor; -import org.simantics.browsing.ui.common.processors.DefaultImagerProcessor; -import org.simantics.browsing.ui.common.processors.DefaultLabelDecoratorProcessor; -import org.simantics.browsing.ui.common.processors.DefaultLabelerFactoriesProcessor; -import org.simantics.browsing.ui.common.processors.DefaultLabelerProcessor; -import org.simantics.browsing.ui.common.processors.DefaultPrunedChildrenProcessor; -import org.simantics.browsing.ui.common.processors.DefaultSelectedImageDecoratorFactoriesProcessor; -import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelDecoratorFactoriesProcessor; -import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelerProcessor; -import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointFactoryProcessor; -import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointProcessor; -import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionProcessor; -import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionsProcessor; -import org.simantics.browsing.ui.common.processors.DefaultViewpointProcessor; -import org.simantics.browsing.ui.common.processors.IsExpandedProcessor; -import org.simantics.browsing.ui.common.processors.NoSelectionRequestProcessor; -import org.simantics.browsing.ui.common.processors.ProcessorLifecycle; -import org.simantics.browsing.ui.content.ImageDecorator; -import org.simantics.browsing.ui.content.Imager; -import org.simantics.browsing.ui.content.LabelDecorator; -import org.simantics.browsing.ui.content.Labeler; -import org.simantics.browsing.ui.content.Labeler.CustomModifier; -import org.simantics.browsing.ui.content.Labeler.DialogModifier; -import org.simantics.browsing.ui.content.Labeler.EnumerationModifier; -import org.simantics.browsing.ui.content.Labeler.Modifier; -import org.simantics.browsing.ui.swt.internal.Threads; -import org.simantics.db.layer0.SelectionHints; -import org.simantics.ui.SimanticsUI; -import org.simantics.utils.datastructures.BijectionMap; -import org.simantics.utils.datastructures.BinaryFunction; -import org.simantics.utils.datastructures.MapList; -import org.simantics.utils.datastructures.disposable.AbstractDisposable; -import org.simantics.utils.datastructures.hints.IHintContext; -import org.simantics.utils.threads.IThreadWorkQueue; -import org.simantics.utils.threads.SWTThread; -import org.simantics.utils.threads.ThreadUtils; -import org.simantics.utils.ui.AdaptionUtils; -import org.simantics.utils.ui.ISelectionUtils; -import org.simantics.utils.ui.jface.BasePostSelectionProvider; - -import gnu.trove.map.hash.THashMap; -import gnu.trove.map.hash.TObjectIntHashMap; - -/** - * TreeView based GraphExplorer - * - * - * @author Marko Luukkainen - */ -public class GraphExplorerImpl2 extends GraphExplorerImplBase implements GraphExplorer { - - private static final boolean DEBUG_SELECTION_LISTENERS = false; - private static final boolean DEBUG = false; - - private TreeViewer viewer; - - private LocalResourceManager localResourceManager; - private DeviceResourceManager resourceManager; - - - private IThreadWorkQueue thread; - - @SuppressWarnings({ "rawtypes" }) - final HashMap, NodeQueryProcessor> processors = new HashMap, NodeQueryProcessor>(); - @SuppressWarnings({ "rawtypes" }) - final HashMap primitiveProcessors = new HashMap(); - @SuppressWarnings({ "rawtypes" }) - final HashMap dataSources = new HashMap(); - - private FontDescriptor originalFont; - protected ColorDescriptor originalForeground; - protected ColorDescriptor originalBackground; - private Color invalidModificationColor; - - private Column[] columns; - private Map columnKeyToIndex; - private boolean columnsAreVisible = true; - - - private NodeContext rootContext; - private TreeNode rootNode; - private StatePersistor persistor = null; - - private boolean editable = true; - - private boolean disposed = false; - - private final CopyOnWriteArrayList focusListeners = new CopyOnWriteArrayList(); - private final CopyOnWriteArrayList mouseListeners = new CopyOnWriteArrayList(); - private final CopyOnWriteArrayList keyListeners = new CopyOnWriteArrayList(); - - private int autoExpandLevel = 0; - private IServiceLocator serviceLocator; - private IContextService contextService = null; - private IFocusService focusService = null; - private IContextActivation editingContext = null; - - GeViewerContext explorerContext = new GeViewerContext(this); - - private GraphExplorerPostSelectionProvider postSelectionProvider = new GraphExplorerPostSelectionProvider(this); - private BasePostSelectionProvider selectionProvider = new BasePostSelectionProvider(); - private SelectionDataResolver selectionDataResolver; - private SelectionFilter selectionFilter; - - private Set collapsedNodes = new HashSet(); - private MapList contextToNodeMap = new MapList(); - - private ModificationContext modificationContext = null; - - private boolean filterSelectionEdit = true; - - private TreeColumnLayout treeColumnLayout; - - private boolean expand; - private boolean verticalBarVisible = false; - - private BinaryFunction selectionTransformation = new BinaryFunction() { - - @Override - public Object[] call(GraphExplorer explorer, Object[] objects) { - Object[] result = new Object[objects.length]; - for (int i = 0; i < objects.length; i++) { - IHintContext context = new AdaptableHintContext(SelectionHints.KEY_MAIN); - context.setHint(SelectionHints.KEY_MAIN, objects[i]); - result[i] = context; - } - return result; - } - - }; - - static class TransientStateImpl implements TransientExplorerState { - - private Integer activeColumn = null; - - @Override - public synchronized Integer getActiveColumn() { - return activeColumn; - } - - public synchronized void setActiveColumn(Integer column) { - activeColumn = column; - } - - } - - private TransientStateImpl transientState = new TransientStateImpl(); - - - public GraphExplorerImpl2(Composite parent) { - this(parent, SWT.BORDER | SWT.MULTI ); - } - - public GraphExplorerImpl2(Composite parent, int style) { - this.localResourceManager = new LocalResourceManager(JFaceResources.getResources()); - this.resourceManager = new DeviceResourceManager(parent.getDisplay()); - - this.imageLoaderJob = new ImageLoaderJob(this); - this.imageLoaderJob.setPriority(Job.DECORATE); - - invalidModificationColor = (Color) localResourceManager.get(ColorDescriptor.createFrom(new RGB(255, 128, 128))); - - this.thread = SWTThread.getThreadAccess(parent); - - for (int i = 0; i < 10; i++) - explorerContext.activity.push(0); - - boolean useLayout = true; - // FIXME: hack, GraphExplorerComposite uses its own TreeColumnLayout. - if (useLayout && !(parent.getLayout() instanceof TreeColumnLayout)) { - - Composite rootTreeComposite = new Composite(parent, SWT.NONE); - treeColumnLayout = new TreeColumnLayout(); - rootTreeComposite.setLayout(treeColumnLayout); - - viewer = new TreeViewer(rootTreeComposite,style|SWT.H_SCROLL|SWT.V_SCROLL); - - GridDataFactory.fillDefaults().grab(true, true).span(3,1).applyTo(rootTreeComposite); - - } else { - viewer = new TreeViewer(parent,style | SWT.H_SCROLL | SWT.V_SCROLL); - } - - viewer.getColumnViewerEditor().addEditorActivationListener(new ColumnViewerEditorActivationListener() { - - @Override - public void beforeEditorDeactivated(ColumnViewerEditorDeactivationEvent event) { - - } - - @Override - public void beforeEditorActivated(ColumnViewerEditorActivationEvent event) { - // cancel editor activation for double click events. - // TODO: this may not work similarly to GraphExplorerImpl - if ((event.time - focusGainedAt) < 250L) { - event.cancel = true; - } - } - - @Override - public void afterEditorDeactivated(ColumnViewerEditorDeactivationEvent event) { - - } - - @Override - public void afterEditorActivated(ColumnViewerEditorActivationEvent event) { - - } - }); - - viewer.setUseHashlookup(true); - viewer.setContentProvider(new GeViewerContentProvider()); - - - - originalFont = JFaceResources.getDefaultFontDescriptor(); - - viewer.getTree().setFont((Font) localResourceManager.get(originalFont)); - - setBasicListeners(); - setDefaultProcessors(); - - viewer.getTree().addDisposeListener(new DisposeListener() { - - @Override - public void widgetDisposed(DisposeEvent e) { - doDispose(); - - } - }); - - - // Add listener to tree for delayed tree population. - - Listener listener = new Listener() { - - @Override - public void handleEvent(Event event) { - - switch (event.type) { - case SWT.Activate: - case SWT.Show: - case SWT.Paint: - { - visible = true; - activate(); - break; - } - case SWT.Deactivate: - case SWT.Hide: - visible = false; - } - } - }; - - viewer.getTree().addListener(SWT.Activate, listener); - viewer.getTree().addListener(SWT.Deactivate, listener); - viewer.getTree().addListener(SWT.Show, listener); - viewer.getTree().addListener(SWT.Hide, listener); - viewer.getTree().addListener(SWT.Paint,listener); - - - viewer.addTreeListener(new ITreeViewerListener() { - - @Override - public void treeExpanded(TreeExpansionEvent event) { - - } - - @Override - public void treeCollapsed(TreeExpansionEvent event) { - collapsedNodes.add((TreeNode)event.getElement()); - } - }); - - setColumns( new Column[] { new Column(ColumnKeys.SINGLE) }); - } - - private long focusGainedAt = 0L; - private boolean visible = false; - - private Collection selectedNodes = new ArrayList(); - - protected void setBasicListeners() { - Tree tree = viewer.getTree(); - - tree.addFocusListener(new FocusListener() { - @Override - public void focusGained(FocusEvent e) { - focusGainedAt = ((long) e.time) & 0xFFFFFFFFL; - for (FocusListener listener : focusListeners) - listener.focusGained(e); - } - @Override - public void focusLost(FocusEvent e) { - for (FocusListener listener : focusListeners) - listener.focusLost(e); - } - }); - tree.addMouseListener(new MouseListener() { - @Override - public void mouseDoubleClick(MouseEvent e) { - for (MouseListener listener : mouseListeners) { - listener.mouseDoubleClick(e); - } - } - @Override - public void mouseDown(MouseEvent e) { - for (MouseListener listener : mouseListeners) { - listener.mouseDown(e); - } - } - @Override - public void mouseUp(MouseEvent e) { - for (MouseListener listener : mouseListeners) { - listener.mouseUp(e); - } - } - }); - tree.addKeyListener(new KeyListener() { - @Override - public void keyPressed(KeyEvent e) { - for (KeyListener listener : keyListeners) { - listener.keyPressed(e); - } - } - @Override - public void keyReleased(KeyEvent e) { - for (KeyListener listener : keyListeners) { - listener.keyReleased(e); - } - } - }); - - viewer.addSelectionChangedListener(new ISelectionChangedListener() { - - @Override - public void selectionChanged(SelectionChangedEvent event) { - //System.out.println("GraphExplorerImpl2.fireSelection"); - selectedNodes = AdaptionUtils.adaptToCollection(event.getSelection(), TreeNode.class); - Collection selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class); - selectionProvider.setAndFireSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()]))); - } - }); - - viewer.addPostSelectionChangedListener(new ISelectionChangedListener() { - - @Override - public void selectionChanged(SelectionChangedEvent event) { - //System.out.println("GraphExplorerImpl2.firePostSelection"); - Collection selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class); - selectionProvider.firePostSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()]))); - - } - }); - - } - - private NodeContext pendingRoot; - - private void activate() { - if (pendingRoot != null && !expand) { - doSetRoot(pendingRoot); - pendingRoot = null; - } - } - - /** - * Invoke only from SWT thread to reset the root of the graph explorer tree. - * - * @param root - */ - private void doSetRoot(NodeContext root) { - Display display = viewer.getTree().getDisplay(); - if (display.getThread() != Thread.currentThread()) { - throw new RuntimeException("Invoke from SWT thread only"); - } -// System.out.println("doSetRoot " + root); - if (isDisposed()) - return; - if (viewer.getTree().isDisposed()) - return; - if (root.getConstant(BuiltinKeys.INPUT) == null) { - ErrorLogger.defaultLogError("root node context does not contain BuiltinKeys.INPUT key. Node = " + root, new Exception("trace")); - return; - } - - - - // Empty caches, release queries. - if (rootNode != null) { - rootNode.dispose(); - } - GeViewerContext oldContext = explorerContext; - GeViewerContext newContext = new GeViewerContext(this); - this.explorerContext = newContext; - oldContext.safeDispose(); - - // Need to empty these or otherwise they won't be emptied until the - // explorer is disposed which would mean that many unwanted references - // will be held by this map. - clearPrimitiveProcessors(); - - this.rootContext = root.getConstant(BuiltinKeys.IS_ROOT) != null ? root - : NodeContextUtil.withConstant(root, BuiltinKeys.IS_ROOT, Boolean.TRUE); - - explorerContext.getCache().incRef(this.rootContext); - - initializeState(); - - - select(rootContext); - //refreshColumnSizes(); - rootNode = new TreeNode(rootContext); - if (DEBUG) System.out.println("setRoot " + rootNode); - - viewer.setInput(rootNode); - - // Delay content reading. - - // This is required for cases when GEImpl2 is attached to selection view. Reading content - // instantly could stagnate SWT thread under rapid changes in selection. By delaying the - // content reading we give the system a change to dispose the GEImpl2 before the content is read. - display.asyncExec(new Runnable() { - - @Override - public void run() { - if (rootNode != null) { - rootNode.updateChildren(); - } - } - }); - - } - - private void initializeState() { - if (persistor == null) - return; - - ExplorerState state = persistor.deserialize( - Platform.getStateLocation(Activator.getDefault().getBundle()).toFile(), - getRoot()); - - - Object processor = getPrimitiveProcessor(BuiltinKeys.IS_EXPANDED); - if (processor instanceof DefaultIsExpandedProcessor) { - DefaultIsExpandedProcessor isExpandedProcessor = (DefaultIsExpandedProcessor)processor; - for(NodeContext expanded : state.expandedNodes) { - isExpandedProcessor.setExpanded(expanded, true); - } - } - } - - @Override - public NodeContext getRoot() { - return rootContext; - } - - @Override - public IThreadWorkQueue getThread() { - return thread; - } - - @Override - public NodeContext getParentContext(NodeContext context) { - if (disposed) - throw new IllegalStateException("disposed"); - if (!thread.currentThreadAccess()) - throw new IllegalStateException("not in SWT display thread " + thread.getThread()); - - List nodes = contextToNodeMap.getValuesUnsafe(context); - for (int i = 0; i < nodes.size(); i++) { - if (nodes.get(i).getParent() != null) - return nodes.get(i).getParent().getContext(); - } - return null; - - } - - - @SuppressWarnings("unchecked") - @Override - public T getAdapter(Class adapter) { - if(ISelectionProvider.class == adapter) return (T) postSelectionProvider; - else if(IPostSelectionProvider.class == adapter) return (T) postSelectionProvider; - return null; - } - - - protected void setDefaultProcessors() { - // Add a simple IMAGER query processor that always returns null. - // With this processor no images will ever be shown. - // setPrimitiveProcessor(new StaticImagerProcessor(null)); - - setProcessor(new DefaultComparableChildrenProcessor()); - setProcessor(new DefaultLabelDecoratorsProcessor()); - setProcessor(new DefaultImageDecoratorsProcessor()); - setProcessor(new DefaultSelectedLabelerProcessor()); - setProcessor(new DefaultLabelerFactoriesProcessor()); - setProcessor(new DefaultSelectedImagerProcessor()); - setProcessor(new DefaultImagerFactoriesProcessor()); - setPrimitiveProcessor(new DefaultLabelerProcessor()); - setPrimitiveProcessor(new DefaultCheckedStateProcessor()); - setPrimitiveProcessor(new DefaultImagerProcessor()); - setPrimitiveProcessor(new DefaultLabelDecoratorProcessor()); - setPrimitiveProcessor(new DefaultImageDecoratorProcessor()); - setPrimitiveProcessor(new NoSelectionRequestProcessor()); - - setProcessor(new DefaultFinalChildrenProcessor(this)); - - setProcessor(new DefaultPrunedChildrenProcessor()); - setProcessor(new DefaultSelectedViewpointProcessor()); - setProcessor(new DefaultSelectedLabelDecoratorFactoriesProcessor()); - setProcessor(new DefaultSelectedImageDecoratorFactoriesProcessor()); - setProcessor(new DefaultViewpointContributionsProcessor()); - - setPrimitiveProcessor(new DefaultViewpointProcessor()); - setPrimitiveProcessor(new DefaultViewpointContributionProcessor()); - setPrimitiveProcessor(new DefaultSelectedViewpointFactoryProcessor()); - setPrimitiveProcessor(new TreeNodeIsExpandedProcessor()); - setPrimitiveProcessor(new DefaultShowMaxChildrenProcessor()); - } - - @Override - public Column[] getColumns() { - return Arrays.copyOf(columns, columns.length); - } - - @Override - public void setColumnsVisible(boolean visible) { - columnsAreVisible = visible; - if(viewer.getTree() != null) viewer.getTree().setHeaderVisible(columnsAreVisible); - } - - @Override - public void setColumns(final Column[] columns) { - setColumns(columns, null); - } - - @Override - public void setColumns(final Column[] columns, Consumer> callback) { - assertNotDisposed(); - checkUniqueColumnKeys(columns); - - Display d = viewer.getTree().getDisplay(); - if (d.getThread() == Thread.currentThread()) { - doSetColumns(columns, callback); - viewer.refresh(true); - }else - d.asyncExec(new Runnable() { - @Override - public void run() { - if (viewer == null) - return; - if (viewer.getTree().isDisposed()) - return; - doSetColumns(columns, callback); - viewer.refresh(true); - viewer.getTree().getParent().layout(); - } - }); - } - - private void checkUniqueColumnKeys(Column[] cols) { - Set usedColumnKeys = new HashSet(); - List duplicateColumns = new ArrayList(); - for (Column c : cols) { - if (!usedColumnKeys.add(c.getKey())) - duplicateColumns.add(c); - } - if (!duplicateColumns.isEmpty()) { - throw new IllegalArgumentException("All columns do not have unique keys: " + cols + ", overlapping: " + duplicateColumns); - } - } - - private List treeViewerColumns = new ArrayList(); - private CellLabelProvider cellLabelProvider = new GeViewerLabelProvider(); - private List editingSupports = new ArrayList(); - - private void doSetColumns(Column[] cols, Consumer> callback) { - // Attempt to keep previous column widths. - Map prevWidths = new HashMap(); - - for (TreeViewerColumn c : treeViewerColumns) { - prevWidths.put(c.getColumn().getText(), c.getColumn().getWidth()); - c.getColumn().dispose(); - } - - treeViewerColumns.clear(); - - HashMap keyToIndex = new HashMap(); - for (int i = 0; i < cols.length; ++i) { - keyToIndex.put(cols[i].getKey(), i); - } - - this.columns = Arrays.copyOf(cols, cols.length); - //this.columns[cols.length] = FILLER_COLUMN; - this.columnKeyToIndex = keyToIndex; - - Map map = new HashMap(); - - // FIXME : temporary workaround for ModelBrowser. - viewer.getTree().setHeaderVisible(columns.length == 1 ? false : columnsAreVisible); - - int columnIndex = 0; - - for (Column column : columns) { - TreeViewerColumn tvc = new TreeViewerColumn(viewer, toSWT(column.getAlignment())); - treeViewerColumns.add(tvc); - tvc.setLabelProvider(cellLabelProvider); - EditingSupport support = null; - if (editingSupports.size() > columnIndex) - support = editingSupports.get(columnIndex); - else { - support = new GeEditingSupport(viewer, columnIndex); - editingSupports.add(support); - } - - tvc.setEditingSupport(support); - - TreeColumn c = tvc.getColumn(); - map.put(column, c); - c.setData(column); - c.setText(column.getLabel()); - c.setToolTipText(column.getTooltip()); - - int cw = column.getWidth(); - - // Try to keep previous widths - Integer w = prevWidths.get(column); - if (w != null) - c.setWidth(w); - else if (cw != Column.DEFAULT_CONTROL_WIDTH) - c.setWidth(cw); - else if (columns.length == 1) { - // FIXME : how to handle single column properly? - c.setWidth(1000); - } - else { - // Go for some kind of default settings then... - if (ColumnKeys.PROPERTY.equals(column.getKey())) - c.setWidth(150); - else - c.setWidth(50); - } - if (treeColumnLayout != null) { - treeColumnLayout.setColumnData(c, new ColumnWeightData(column.getWeight(), true)); - } - -// if (!column.hasGrab() && !FILLER.equals(column.getKey())) { -// c.addListener(SWT.Resize, resizeListener); -// c.setResizable(true); -// } else { -// //c.setResizable(false); -// } - columnIndex++; - } - - - - if(callback != null) callback.accept(map); - } - - int toSWT(Align alignment) { - switch (alignment) { - case LEFT: return SWT.LEFT; - case CENTER: return SWT.CENTER; - case RIGHT: return SWT.RIGHT; - default: throw new Error("unhandled alignment: " + alignment); - } - } - - @Override - public void setProcessor(NodeQueryProcessor processor) { - assertNotDisposed(); - if (processor == null) - throw new IllegalArgumentException("null processor"); - - processors.put(processor.getIdentifier(), processor); - } - - @Override - public void setPrimitiveProcessor(PrimitiveQueryProcessor processor) { - assertNotDisposed(); - if (processor == null) - throw new IllegalArgumentException("null processor"); - - PrimitiveQueryProcessor oldProcessor = primitiveProcessors.put( - processor.getIdentifier(), processor); - - if (oldProcessor instanceof ProcessorLifecycle) - ((ProcessorLifecycle) oldProcessor).detached(this); - if (processor instanceof ProcessorLifecycle) - ((ProcessorLifecycle) processor).attached(this); - } - - @Override - public void setDataSource(DataSource provider) { - assertNotDisposed(); - if (provider == null) - throw new IllegalArgumentException("null provider"); - dataSources.put(provider.getProvidedClass(), provider); - } - - @SuppressWarnings("unchecked") - @Override - public DataSource removeDataSource(Class forProvidedClass) { - assertNotDisposed(); - if (forProvidedClass == null) - throw new IllegalArgumentException("null class"); - return dataSources.remove(forProvidedClass); - } - - @Override - public void setPersistor(StatePersistor persistor) { - this.persistor = persistor; - } - - @Override - public SelectionDataResolver getSelectionDataResolver() { - return selectionDataResolver; - } - - @Override - public void setSelectionDataResolver(SelectionDataResolver r) { - this.selectionDataResolver = r; - } - - @Override - public SelectionFilter getSelectionFilter() { - return selectionFilter; - } - - @Override - public void setSelectionFilter(SelectionFilter f) { - this.selectionFilter = f; - // TODO: re-filter current selection? - } - - protected ISelection constructSelection(NodeContext... contexts) { - if (contexts == null) - throw new IllegalArgumentException("null contexts"); - if (contexts.length == 0) - return StructuredSelection.EMPTY; - if (selectionFilter == null) - return new StructuredSelection(transformSelection(contexts)); - return new StructuredSelection( transformSelection(filter(selectionFilter, contexts)) ); - } - - protected Object[] transformSelection(Object[] objects) { - return selectionTransformation.call(this, objects); - } - - protected static Object[] filter(SelectionFilter filter, NodeContext[] contexts) { - int len = contexts.length; - Object[] objects = new Object[len]; - for (int i = 0; i < len; ++i) - objects[i] = filter.filter(contexts[i]); - return objects; - } - - @Override - public void setSelectionTransformation( - BinaryFunction f) { - this.selectionTransformation = f; - } - - public ISelection getWidgetSelection() { - return viewer.getSelection(); - } - - @Override - public void addListener(T listener) { - if (listener instanceof FocusListener) { - focusListeners.add((FocusListener) listener); - } else if (listener instanceof MouseListener) { - mouseListeners.add((MouseListener) listener); - } else if (listener instanceof KeyListener) { - keyListeners.add((KeyListener) listener); - } - } - - @Override - public void removeListener(T listener) { - if (listener instanceof FocusListener) { - focusListeners.remove(listener); - } else if (listener instanceof MouseListener) { - mouseListeners.remove(listener); - } else if (listener instanceof KeyListener) { - keyListeners.remove(listener); - } - } - - public void addSelectionListener(SelectionListener listener) { - viewer.getTree().addSelectionListener(listener); - } - - public void removeSelectionListener(SelectionListener listener) { - viewer.getTree().removeSelectionListener(listener); - } - - private Set uiContexts; - - @Override - public void setUIContexts(Set contexts) { - this.uiContexts = contexts; - } - - @Override - public void setRoot(final Object root) { - if(uiContexts != null && uiContexts.size() == 1) - setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root, BuiltinKeys.UI_CONTEXT, uiContexts.iterator().next())); - else - setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root)); - } - - @Override - public void setRootContext(final NodeContext context) { - setRootContext0(context); - } - - private void setRoot(NodeContext context) { - if (!visible) { - pendingRoot = context; - Display.getDefault().asyncExec(new Runnable() { - @Override - public void run() { - if (viewer != null && !viewer.getTree().isDisposed()) - viewer.getTree().redraw(); - } - }); - return; - } - doSetRoot(context); - } - - private void setRootContext0(final NodeContext context) { - Assert.isNotNull(context, "root must not be null"); - if (isDisposed() || viewer.getTree().isDisposed()) - return; - Display display = viewer.getTree().getDisplay(); - if (display.getThread() == Thread.currentThread()) { - setRoot(context); - } else { - display.asyncExec(new Runnable() { - @Override - public void run() { - setRoot(context); - } - }); - } - } - - @Override - public void setFocus() { - viewer.getTree().setFocus(); - } - - @SuppressWarnings("unchecked") - @Override - public T getControl() { - return (T)viewer.getTree(); - } - - - @Override - public boolean isDisposed() { - return disposed; - } - - protected void assertNotDisposed() { - if (isDisposed()) - throw new IllegalStateException("disposed"); - } - - @Override - public boolean isEditable() { - return editable; - } - - @Override - public void setEditable(boolean editable) { - if (!thread.currentThreadAccess()) - throw new IllegalStateException("not in SWT display thread " + thread.getThread()); - - this.editable = editable; - Display display = viewer.getTree().getDisplay(); - viewer.getTree().setBackground(editable ? null : display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND)); - } - - private void doDispose() { - if (disposed) - return; - disposed = true; - // TODO: Since GENodeQueryManager is cached in QueryChache and it refers to this class - // we have to remove all references here to reduce memory consumption. - // - // Proper fix would be to remove references between QueryCache and GENodeQueryManagers. - explorerContext.dispose(); - explorerContext = null; - processors.clear(); - detachPrimitiveProcessors(); - primitiveProcessors.clear(); - dataSources.clear(); - pendingItems.clear(); - rootContext = null; - mouseListeners.clear(); - selectionProvider.clearListeners(); - selectionProvider = null; - selectionDataResolver = null; - selectedNodes.clear(); - selectedNodes = null; - selectionTransformation = null; - originalFont = null; - localResourceManager.dispose(); - localResourceManager = null; - // Must shutdown image loader job before disposing its ResourceManager - imageLoaderJob.dispose(); - imageLoaderJob.cancel(); - try { - imageLoaderJob.join(); - imageLoaderJob = null; - } catch (InterruptedException e) { - ErrorLogger.defaultLogError(e); - } - resourceManager.dispose(); - resourceManager = null; - collapsedNodes.clear(); - collapsedNodes = null; - if (rootNode != null) { - rootNode.dispose(); - rootNode = null; - } - contextToNodeMap.clear(); // should be empty at this point. - contextToNodeMap = null; - if (postSelectionProvider != null) { - postSelectionProvider.dispose(); - postSelectionProvider = null; - } - imageTasks = null; - modificationContext = null; - focusService = null; - contextService = null; - serviceLocator = null; - columns = null; - columnKeyToIndex.clear(); - columnKeyToIndex = null; - viewer = null; - - } - - @Override - public boolean select(NodeContext context) { - - assertNotDisposed(); - - if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) { - viewer.setSelection(new StructuredSelection()); - selectionProvider.setAndFireNonEqualSelection(TreeSelection.EMPTY); - return true; - } - - viewer.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0))); - - return false; - - } - - @Override - public boolean selectPath(Collection contexts) { - - if(contexts == null) throw new IllegalArgumentException("Null list is not allowed"); - if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed"); - - return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0); - - } - - private boolean selectPathInternal(NodeContext[] contexts, int position) { - - NodeContext head = contexts[position]; - - if(position == contexts.length-1) { - return select(head); - - } - - setExpanded(head, true); - if(!waitVisible(contexts[position+1])) return false; - - return selectPathInternal(contexts, position+1); - - } - - private boolean waitVisible(NodeContext context) { - long start = System.nanoTime(); - while(!isVisible(context)) { - Display.getCurrent().readAndDispatch(); - long duration = System.nanoTime() - start; - if(duration > 10e9) return false; - } - return true; - } - - @Override - public boolean isVisible(NodeContext context) { - if (contextToNodeMap.getValuesUnsafe(context).size() == 0) - return false; - - Object elements[] = viewer.getVisibleExpandedElements(); - return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0)); - - - } - - @Override - public TransientExplorerState getTransientState() { - if (!thread.currentThreadAccess()) - throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread()); - return transientState; - } - - @Override - public T query(NodeContext context, CacheKey key) { - return this.explorerContext.cache.get(context, key); - } - - /** - * For setting a more local service locator for the explorer than the global - * workbench service locator. Sometimes required to give this implementation - * access to local workbench services like IFocusService. - * - *

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

+ * Must be invoked during right after construction. + * + * @param serviceLocator + * a specific service locator or null to use the + * workbench global service locator + */ + public void setServiceLocator(IServiceLocator serviceLocator) { + if (serviceLocator == null && PlatformUI.isWorkbenchRunning()) + serviceLocator = PlatformUI.getWorkbench(); + this.serviceLocator = serviceLocator; + if (serviceLocator != null) { + this.contextService = (IContextService) serviceLocator.getService(IContextService.class); + this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class); + } + } + + private void detachPrimitiveProcessors() { + for (PrimitiveQueryProcessor p : primitiveProcessors.values()) { + if (p instanceof ProcessorLifecycle) { + ((ProcessorLifecycle) p).detached(this); + } + } + } + + private void clearPrimitiveProcessors() { + for (PrimitiveQueryProcessor p : primitiveProcessors.values()) { + if (p instanceof ProcessorLifecycle) { + ((ProcessorLifecycle) p).clear(); + } + } + } + + @Override + public void setExpanded(NodeContext context, boolean expanded) { + viewer.setExpandedState(context, expanded); + + } + + @Override + public void setAutoExpandLevel(int level) { + this.autoExpandLevel = level; + viewer.setAutoExpandLevel(level); + } + + int maxChildren = GraphExplorerImpl.DEFAULT_MAX_CHILDREN; + + @Override + public int getMaxChildren() { + return maxChildren; + } + + @Override + public void setMaxChildren(int maxChildren) { + this.maxChildren = maxChildren; + + } + + @Override + public int getMaxChildren(NodeQueryManager manager, NodeContext context) { + Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN); + //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result); + if (result != null) { + if (result < 0) + throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result); + return result; + } + return maxChildren; + } + + @Override + public NodeQueryProcessor getProcessor(QueryKey key) { + return explorerContext.getProcessor(key); + } + + @Override + public PrimitiveQueryProcessor getPrimitiveProcessor(PrimitiveQueryKey key) { + return explorerContext.getPrimitiveProcessor(key); + } + + private HashSet pendingItems = new HashSet(); + private boolean updating = false; + private int updateCounter = 0; + final ScheduledExecutorService uiUpdateScheduler = ThreadUtils.getNonBlockingWorkExecutor(); + + private class UpdateItem { + TreeNode element; + int columnIndex; + + public UpdateItem(TreeNode element) { + this(element,-1); + } + + public UpdateItem(TreeNode element, int columnIndex) { + this.element = element; + this.columnIndex = columnIndex; + if (element != null && element.isDisposed()) { + throw new IllegalArgumentException("Node is disposed. " + element); + } + } + + public void update(TreeViewer viewer) { + if (element != null) { + + if (element.isDisposed()) { + return; + } + if (((TreeNode)element).updateChildren()) { + viewer.refresh(element,true); + } else { + if (columnIndex >= 0) { + viewer.update(element, new String[]{columns[columnIndex].getKey()}); + } else { + viewer.refresh(element,true); + } + } + + if (!element.isDisposed() && autoExpandLevel > 1 && !collapsedNodes.contains(element) && ((TreeNode)element).distanceToRoot() <= autoExpandLevel) { + expand = true; + viewer.setExpandedState(element, true); + expand = false; + } + } else { + if (rootNode.updateChildren()) + viewer.refresh(rootNode,true); + } + } + + @Override + public boolean equals(Object obj) { + if (obj == null) + return false; + if (obj.getClass() != getClass()) + return false; + UpdateItem other = (UpdateItem)obj; + if (columnIndex != other.columnIndex) + return false; + if (element != null) + return element.equals(other.element); + return other.element == null; + } + + @Override + public int hashCode() { + if (element != null) + return element.hashCode() + columnIndex; + return 0; + } + } + + private void update(final TreeNode element, final int columnIndex) { + if (DEBUG)System.out.println("update " + element + " " + columnIndex); + if (viewer.getTree().isDisposed()) + return; + synchronized (pendingItems) { + pendingItems.add(new UpdateItem(element, columnIndex)); + if (updating) return; + updateCounter++; + scheduleUpdater(); + } + } + + private void update(final TreeNode element) { + if (DEBUG)System.out.println("update " + element); + if (viewer.getTree().isDisposed()) + return; + if (element != null && element.isDisposed()) + return; + synchronized (pendingItems) { + + pendingItems.add(new UpdateItem(element)); + if (updating) return; + updateCounter++; + scheduleUpdater(); + } + } + + boolean scheduleUpdater() { + + if (viewer.getTree().isDisposed()) + return false; + + if (!pendingItems.isEmpty()) { + + int activity = explorerContext.activityInt; + long delay = 30; + if (activity < 100) { + //System.out.println("Scheduling update immediately."); + } else if (activity < 1000) { + //System.out.println("Scheduling update after 500ms."); + delay = 500; + } else { + //System.out.println("Scheduling update after 3000ms."); + delay = 3000; + } + + updateCounter = 0; + + //System.out.println("Scheduling UI update after " + delay + " ms."); + uiUpdateScheduler.schedule(new Runnable() { + @Override + public void run() { + + if (viewer == null || viewer.getTree().isDisposed()) + return; + + if (updateCounter > 0) { + updateCounter = 0; + uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS); + } else { + viewer.getTree().getDisplay().asyncExec(new UpdateRunner(GraphExplorerImpl2.this, GraphExplorerImpl2.this.explorerContext)); + } + + } + }, delay, TimeUnit.MILLISECONDS); + + updating = true; + return true; + } + + return false; + } + + @Override + public String startEditing(NodeContext context, String columnKey) { + assertNotDisposed(); + if (!thread.currentThreadAccess()) + throw new IllegalStateException("not in SWT display thread " + thread.getThread()); + + if(columnKey.startsWith("#")) { + columnKey = columnKey.substring(1); + } + + Integer columnIndex = columnKeyToIndex.get(columnKey); + if (columnIndex == null) + return "Rename not supported for selection"; + + viewer.editElement(context, columnIndex); + if(viewer.isCellEditorActive()) return null; + return "Rename not supported for selection"; + } + + @Override + public String startEditing(String columnKey) { + ISelection selection = postSelectionProvider.getSelection(); + if(selection == null) return "Rename not supported for selection"; + NodeContext context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class); + if(context == null) return "Rename not supported for selection"; + + return startEditing(context, columnKey); + + } + + public void setSelection(final ISelection selection, boolean forceControlUpdate) { + assertNotDisposed(); + boolean equalsOld = selectionProvider.selectionEquals(selection); + if (equalsOld && !forceControlUpdate) { + // Just set the selection object instance, fire no events nor update + // the viewer selection. + selectionProvider.setSelection(selection); + } else { + Collection coll = AdaptionUtils.adaptToCollection(selection, NodeContext.class); + Collection nodes = new ArrayList(); + for (NodeContext c : coll) { + List match = contextToNodeMap.getValuesUnsafe(c); + if(match.size() > 0) + nodes.add(match.get(0)); + } + final ISelection sel = new StructuredSelection(nodes.toArray()); + if (coll.size() == 0) + return; + // Schedule viewer and selection update if necessary. + if (viewer.getTree().isDisposed()) + return; + Display d = viewer.getTree().getDisplay(); + if (d.getThread() == Thread.currentThread()) { + viewer.setSelection(sel); + } else { + d.asyncExec(new Runnable() { + @Override + public void run() { + if (viewer.getTree().isDisposed()) + return; + viewer.setSelection(sel); + } + }); + } + } + } + + @Override + public void setModificationContext(ModificationContext modificationContext) { + this.modificationContext = modificationContext; + + } + + final ExecutorService queryUpdateScheduler = Threads.getExecutor(); + + private static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext { + // This is for query debugging only. + + private GraphExplorerImpl2 ge; + int queryIndent = 0; + + GECache2 cache = new GECache2(); + AtomicBoolean propagating = new AtomicBoolean(false); + Object propagateList = new Object(); + Object propagate = new Object(); + List scheduleList = new ArrayList(); + final Deque activity = new LinkedList(); + int activityInt = 0; + + AtomicReference currentQueryUpdater = new AtomicReference(); + + /** + * Keeps track of nodes that have already been auto-expanded. After + * being inserted into this set, nodes will not be forced to stay in an + * expanded state after that. This makes it possible for the user to + * close auto-expanded nodes. + */ + Map autoExpanded = new WeakHashMap(); + + public GeViewerContext(GraphExplorerImpl2 ge) { + this.ge = ge; + } + + @Override + protected void doDispose() { + //saveState(); + autoExpanded.clear(); + } + + @Override + public IGECache getCache() { + return cache; + } + + @Override + public int queryIndent() { + return queryIndent; + } + + @Override + public int queryIndent(int offset) { + queryIndent += offset; + return queryIndent; + } + + @Override + @SuppressWarnings("unchecked") + public NodeQueryProcessor getProcessor(Object o) { + if (ge == null) + return null; + return ge.processors.get(o); + } + + @Override + @SuppressWarnings("unchecked") + public PrimitiveQueryProcessor getPrimitiveProcessor(Object o) { + return ge.primitiveProcessors.get(o); + } + + @SuppressWarnings("unchecked") + @Override + public DataSource getDataSource(Class clazz) { + return ge.dataSources.get(clazz); + } + + @Override + public void update(UIElementReference ref) { + if (ref instanceof ViewerCellReference) { + ViewerCellReference tiref = (ViewerCellReference) ref; + Object element = tiref.getElement(); + int columnIndex = tiref.getColumn(); + // NOTE: must be called regardless of the the item value. + // A null item is currently used to indicate a tree root update. + ge.update((TreeNode)element,columnIndex); + } else if (ref instanceof ViewerRowReference) { + ViewerRowReference rref = (ViewerRowReference)ref; + Object element = rref.getElement(); + ge.update((TreeNode)element); + } else { + throw new IllegalArgumentException("Ui Reference is unknkown " + ref); + } + } + + @Override + public Object getPropagateLock() { + return propagate; + } + + @Override + public Object getPropagateListLock() { + return propagateList; + } + + @Override + public boolean isPropagating() { + return propagating.get(); + } + + @Override + public void setPropagating(boolean b) { + this.propagating.set(b); + } + + @Override + public List getScheduleList() { + return scheduleList; + } + + @Override + public void setScheduleList(List list) { + this.scheduleList = list; + } + + @Override + public Deque getActivity() { + return activity; + } + + @Override + public void setActivityInt(int i) { + this.activityInt = i; + } + + @Override + public int getActivityInt() { + return activityInt; + } + + @Override + public void scheduleQueryUpdate(Runnable r) { + if (ge == null) + return; + if (ge.isDisposed()) + return; + if (currentQueryUpdater.compareAndSet(null, r)) { + ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER); + } + } + + Runnable QUERY_UPDATE_SCHEDULER = new Runnable() { + @Override + public void run() { + Runnable r = currentQueryUpdater.getAndSet(null); + if (r != null) { + r.run(); + } + } + }; + + @Override + public void dispose() { + cache.dispose(); + cache = new DummyCache(); + scheduleList.clear(); + autoExpanded.clear(); + autoExpanded = null; + ge = null; + + } + + } + + + + + private static class GeViewerContentProvider implements ITreeContentProvider { + @Override + public Object[] getElements(Object inputElement) { + return getChildren(inputElement); + } + + @Override + public Object[] getChildren(Object element) { + TreeNode node = (TreeNode)element; + return node.getChildren().toArray(); + + } + + @Override + public Object getParent(Object element) { + TreeNode node = (TreeNode)element; + return node.getParent(); + } + + @Override + public boolean hasChildren(Object element) { + return getChildren(element).length > 0; + } + + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + + } + + @Override + public void dispose() { + + } + } + + private class GeViewerLabelProvider extends CellLabelProvider { + private TreeNode node; + + private Labeler labeler; + private Imager imager; + Collection labelDecorators; + Collection imageDecorators; + + Map labels; + Map runtimeLabels; + @Override + public void update(ViewerCell cell) { + TreeNode node = (TreeNode)cell.getElement(); + NodeContext ctx = node.getContext(); + int columnIndex = cell.getColumnIndex(); + String columnKey = columns[columnIndex].getKey(); + + // using columnIndex 0 to refresh data. + // Note that this does not work if ViewerCellReferences are used. (At the moment there is no code that would use them). + if (node != this.node || columnIndex == 0) { + this.node = node; + GENodeQueryManager manager = node.getManager(); + labeler = manager.query(ctx, BuiltinKeys.SELECTED_LABELER); + imager = manager.query(ctx, BuiltinKeys.SELECTED_IMAGER); + labelDecorators = manager.query(ctx, BuiltinKeys.LABEL_DECORATORS); + imageDecorators = manager.query(ctx, BuiltinKeys.IMAGE_DECORATORS); + + if (labeler != null) { + labels = labeler.getLabels(); + runtimeLabels = labeler.getRuntimeLabels(); + } else { + labels = null; + runtimeLabels = null; + } + } + + //if (DEBUG) System.out.println("GeViewerLabelProvider.update " + context + " " + columnIndex); + + setText(cell, columnKey); + setImage(cell, columnKey); + } + + void setImage(ViewerCell cell, String columnKey) { + if (imager != null) { + Object descOrImage = null; + boolean hasUncachedImages = false; + + ImageDescriptor desc = imager.getImage(columnKey); + if (desc != null) { + int index = 0; + // Attempt to decorate the label + if (!imageDecorators.isEmpty()) { + for (ImageDecorator id : imageDecorators) { + ImageDescriptor ds = id.decorateImage(desc, columnKey, index); + if (ds != null) + desc = ds; + } + } + + // Try resolving only cached images here and now + Object img = localResourceManager.find(desc); + if (img == null) + img = resourceManager.find(desc); + + descOrImage = img != null ? img : desc; + hasUncachedImages |= img == null; + } + + if (!hasUncachedImages) { + cell.setImage((Image) descOrImage); + } else { + // Schedule loading to another thread to refrain from + // blocking + // the UI with database operations. + queueImageTask(node, new ImageTask(node, descOrImage)); + } + } else { + cell.setImage(null); + } + } + + private void queueImageTask(TreeNode node, ImageTask task) { + synchronized (imageTasks) { + imageTasks.put(node, task); + } + imageLoaderJob.scheduleIfNecessary(100); + } + + void setText(ViewerCell cell, String key) { + if (labeler != null) { + String s = null; + if (runtimeLabels != null) + s = runtimeLabels.get(key); + if (s == null) + s = labels.get(key); + //if (DEBUG) System.out.println(cell.getElement() + " " + cell.getColumnIndex() + " label:" + s + " key:" + key + " " + labels.size() + " " + (runtimeLabels == null ? "-1" : runtimeLabels.size())); + if (s != null) { + FontDescriptor font = originalFont; + ColorDescriptor bg = originalBackground; + ColorDescriptor fg = originalForeground; + + // Attempt to decorate the label + if (!labelDecorators.isEmpty()) { + int index = 0; + for (LabelDecorator ld : labelDecorators) { + String ds = ld.decorateLabel(s, key, index); + if (ds != null) + s = ds; + + FontDescriptor dfont = ld.decorateFont(font, key, index); + if (dfont != null) + font = dfont; + + ColorDescriptor dbg = ld.decorateBackground(bg, key, index); + if (dbg != null) + bg = dbg; + + ColorDescriptor dfg = ld.decorateForeground(fg, key, index); + if (dfg != null) + fg = dfg; + } + } + + if (font != originalFont) { + // System.out.println("set font: " + index + ": " + + // font); + cell.setFont((Font) localResourceManager.get(font)); + } else { + cell.setFont((Font) (originalFont != null ? localResourceManager.get(originalFont) : null)); + } + if (bg != originalBackground) + cell.setBackground((Color) localResourceManager.get(bg)); + else + cell.setBackground((Color) (originalBackground != null ? localResourceManager.get(originalBackground) : null)); + if (fg != originalForeground) + cell.setForeground((Color) localResourceManager.get(fg)); + else + cell.setForeground((Color) (originalForeground != null ? localResourceManager.get(originalForeground) : null)); + + cell.setText(s); + } + } else { + cell.setText(Labeler.NO_LABEL); + } + + } + } + + + + private class GeEditingSupport extends EditingSupport { + private Object lastElement; + + private Modifier lastModifier; + + private int columnIndex; + public GeEditingSupport(ColumnViewer viewer, int columnIndex) { + super(viewer); + this.columnIndex = columnIndex; + } + + @Override + protected boolean canEdit(Object element) { + if (filterSelectionEdit && !selectedNodes.contains(element)) { + // When item is clicked, canEdit is called before the selection is updated. + // This allows filtering edit attempts when the item is selected. + return false; + } + lastElement = null; // clear cached element + modifier. + Modifier modifier = getModifier((TreeNode)element); + if (modifier == null) + return false; + return true; + } + + @Override + protected CellEditor getCellEditor(Object element) { + TreeNode node = (TreeNode) element; + Modifier modifier = getModifier((TreeNode)element); + NodeContext context = node.getContext(); + if (modifier instanceof DialogModifier) { + return performDialogEditing(context, (DialogModifier) modifier); + } else if (modifier instanceof CustomModifier) { + return startCustomEditing(node, (CustomModifier) modifier); + } else if (modifier instanceof EnumerationModifier) { + return startEnumerationEditing((EnumerationModifier) modifier); + } else { + return startTextEditing(modifier); + } + + } + + @Override + protected Object getValue(Object element) { + Modifier modifier = getModifier((TreeNode)element); + return modifier.getValue(); + } + @Override + protected void setValue(Object element, Object value) { + Modifier modifier = getModifier((TreeNode)element); + // CustomModifiers have internal set value mechanism. + if (!(modifier instanceof CustomModifier)) + modifier.modify((String)value); + + } + + CellEditor startTextEditing( Modifier modifier) { + TextCellEditor editor = new ValidatedTextEditor(viewer.getTree());//new TextCellEditor(viewer.getTree()); + editor.setValidator(new ModifierValidator(modifier)); + return editor; + } + + CellEditor startEnumerationEditing(EnumerationModifier modifier) { + if (SimanticsUI.isLinuxGTK()) { + // ComboBoxCellEditor2 does not work when GEImpl2 is embedded into dialog (It does work in SelectionView) + // CBCE2 does not work because it receives a focus lost event when the combo/popup is opened. + return new EnumModifierEditor(viewer.getTree(),modifier); + } else { + return new EnumModifierEditor2(viewer.getTree(),modifier); + } + } + + CellEditor performDialogEditing(final NodeContext context, final DialogModifier modifier) { + DialogCellEditor editor = new DialogCellEditor(viewer.getTree()) { + String res = null; + @Override + protected Object openDialogBox(Control cellEditorWindow) { + + final Semaphore sem = new Semaphore(1); + Consumer callback = result -> { + res = result; + sem.release(); + }; + String status = modifier.query(cellEditorWindow, null, columnIndex, context, callback); + if (status != null) + return null; + try { + sem.acquire(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return res; + } + + + }; + editor.setValidator(new ModifierValidator(modifier)); + return editor; + } + + CellEditor startCustomEditing(TreeNode node, CustomModifier modifier) { + CustomModifierEditor editor = new CustomModifierEditor(viewer.getTree(), modifier, node, columnIndex); + return editor; + } + + private Modifier getModifier(TreeNode element) { + if (element == lastElement) + return lastModifier; + lastModifier = GraphExplorerImpl2.this.getModifier(element, columnIndex); + lastElement = element; + return lastModifier; + } + + + } + + private Modifier getModifier(TreeNode element, int columnIndex) { + GENodeQueryManager manager = element.getManager(); + final NodeContext context = element.getContext(); + Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER); + if (labeler == null) + return null; + Column column = columns[columnIndex]; + + return labeler.getModifier(modificationContext, column.getKey()); + + } + + static class ImageTask { + TreeNode node; + Object descsOrImage; + public ImageTask(TreeNode node, Object descsOrImage) { + this.node = node; + this.descsOrImage = descsOrImage; + } + } + + /** + * The job that is used for off-loading image loading tasks (see + * {@link ImageTask} to a worker thread from the main UI thread. + */ + ImageLoaderJob imageLoaderJob; + + // Map imageTasks = new THashMap(); + Map imageTasks = new THashMap(); + + /** + * Invoked in a job worker thread. + * + * @param monitor + */ + @Override + protected IStatus setPendingImages(IProgressMonitor monitor) { + ImageTask[] tasks = null; + synchronized (imageTasks) { + tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]); + imageTasks.clear(); + } + + MultiStatus status = null; + + // Load missing images + for (ImageTask task : tasks) { + Object desc = task.descsOrImage; + if (desc instanceof ImageDescriptor) { + try { + desc = resourceManager.get((ImageDescriptor) desc); + task.descsOrImage = desc; + } catch (DeviceResourceException e) { + if (status == null) + status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null); + status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e)); + } + } + + } + + // Perform final UI updates in the UI thread. + final ImageTask[] _tasks = tasks; + thread.asyncExec(new Runnable() { + @Override + public void run() { + setImages(_tasks); + } + }); + + return status != null ? status : Status.OK_STATUS; + } + + + void setImages(ImageTask[] tasks) { + for (ImageTask task : tasks) + if (task != null) + setImage(task); + } + + void setImage(ImageTask task) { + if (!task.node.isDisposed()) + update(task.node, 0); + } + + private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider { + + private GraphExplorerImpl2 ge; + + GraphExplorerPostSelectionProvider(GraphExplorerImpl2 ge) { + this.ge = ge; + } + + void dispose() { + ge = null; + } + + @Override + public void setSelection(final ISelection selection) { + if(ge == null) return; + ge.setSelection(selection, false); + + } + + + @Override + public void removeSelectionChangedListener(ISelectionChangedListener listener) { + if(ge == null) return; + if(ge.isDisposed()) { + if (DEBUG_SELECTION_LISTENERS) + System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener); + return; + } + ge.selectionProvider.removeSelectionChangedListener(listener); + } + + @Override + public void addPostSelectionChangedListener(ISelectionChangedListener listener) { + if(ge == null) return; + if (!ge.thread.currentThreadAccess()) + throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread()); + if(ge.isDisposed()) { + System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener); + return; + } + ge.selectionProvider.addPostSelectionChangedListener(listener); + } + + @Override + public void removePostSelectionChangedListener(ISelectionChangedListener listener) { + if(ge == null) return; + if(ge.isDisposed()) { + if (DEBUG_SELECTION_LISTENERS) + System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener); + return; + } + ge.selectionProvider.removePostSelectionChangedListener(listener); + } + + + @Override + public void addSelectionChangedListener(ISelectionChangedListener listener) { + if(ge == null) return; + if (!ge.thread.currentThreadAccess()) + throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread()); + if (ge.viewer.getTree().isDisposed() || ge.selectionProvider == null) { + System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener); + return; + } + + ge.selectionProvider.addSelectionChangedListener(listener); + } + + + @Override + public ISelection getSelection() { + if(ge == null) return StructuredSelection.EMPTY; + if (!ge.thread.currentThreadAccess()) + throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread()); + if (ge.viewer.getTree().isDisposed() || ge.selectionProvider == null) + return StructuredSelection.EMPTY; + return ge.selectionProvider.getSelection(); + } + + } + + static class ModifierValidator implements ICellEditorValidator { + private Modifier modifier; + public ModifierValidator(Modifier modifier) { + this.modifier = modifier; + } + + @Override + public String isValid(Object value) { + return modifier.isValid((String)value); + } + } + + static class UpdateRunner implements Runnable { + + final GraphExplorerImpl2 ge; + + UpdateRunner(GraphExplorerImpl2 ge, IGraphExplorerContext geContext) { + this.ge = ge; + } + + public void run() { + try { + doRun(); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + public void doRun() { + + if (ge.isDisposed()) + return; + + HashSet items; + + ScrollBar verticalBar = ge.viewer.getTree().getVerticalBar(); + + + synchronized (ge.pendingItems) { + items = ge.pendingItems; + ge.pendingItems = new HashSet(); + } + if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size()); + + ge.viewer.getTree().setRedraw(false); + for (UpdateItem item : items) { + item.update(ge.viewer); + } + + // check if vertical scroll bar has become visible and refresh layout. + boolean currentlyVerticalBarVisible = verticalBar.isVisible(); + if (ge.verticalBarVisible != currentlyVerticalBarVisible) { + ge.verticalBarVisible = currentlyVerticalBarVisible; + ge.viewer.getTree().getParent().layout(); + } + + ge.viewer.getTree().setRedraw(true); + + synchronized (ge.pendingItems) { + if (!ge.scheduleUpdater()) { + ge.updating = false; + } + } + if (DEBUG) { + if (!ge.updating) { + ge.printTree(ge.rootNode, 0); + } + } + } + + } + + private class ValidatedTextEditor extends TextCellEditor { + + + public ValidatedTextEditor(Composite parent) { + super(parent); + } + + protected void editOccured(org.eclipse.swt.events.ModifyEvent e) { + String value = text.getText(); + if (value == null) { + value = "";//$NON-NLS-1$ + } + Object typedValue = value; + boolean oldValidState = isValueValid(); + boolean newValidState = isCorrect(typedValue); + if (!newValidState) { + text.setBackground(invalidModificationColor); + text.setToolTipText(getErrorMessage()); + } else { + text.setBackground(null); + text.setToolTipText(null); + } + valueChanged(oldValidState, newValidState); + }; + } + + private class EnumModifierEditor2 extends ComboBoxCellEditor2 { + + List values; + public EnumModifierEditor2(Composite parent, EnumerationModifier modifier) { + super(parent,modifier.getValues().toArray(new String[modifier.getValues().size()]),SWT.READ_ONLY); + values = modifier.getValues(); + setValidator(new ModifierValidator(modifier)); + } + @Override + protected void doSetValue(Object value) { + super.doSetValue((Integer)values.indexOf(value)); + } + + @Override + protected Object doGetValue() { + return values.get((Integer)super.doGetValue()); + } + }; + + private class EnumModifierEditor extends ComboBoxCellEditor { + + List values; + public EnumModifierEditor(Composite parent, EnumerationModifier modifier) { + super(parent,modifier.getValues().toArray(new String[modifier.getValues().size()]),SWT.READ_ONLY); + values = modifier.getValues(); + setValidator(new ModifierValidator(modifier)); + } + @Override + protected void doSetValue(Object value) { + super.doSetValue((Integer)values.indexOf(value)); + } + + @Override + protected Object doGetValue() { + return values.get((Integer)super.doGetValue()); + } + }; + + + private class CustomModifierEditor extends CellEditor implements ICellEditorValidator, DisposeListener { + private CustomModifier modifier; + private TreeNode node; + private NodeContext context; + private int columnIndex; + private Composite control; + private Control origControl; + + public CustomModifierEditor(Composite parent, CustomModifier modifier, TreeNode node, int columnIndex) { + this.modifier = modifier; + this.node = node; + this.context = node.getContext(); + this.columnIndex = columnIndex; + setValidator(this); + create(parent); + } + + @Override + protected Control createControl(Composite parent) { + control = new Composite(parent, SWT.NONE); + control.setLayout(new FillLayout()); + origControl = (Control) modifier.createControl(control, null, columnIndex, context); + return control; + } + + + + @Override + protected Object doGetValue() { + return modifier.getValue(); + } + + @Override + protected void doSetValue(Object value) { + //CustomModifier handles value setting internally. + } + + + private void reCreate() { + modifier = (CustomModifier)getModifier(node, columnIndex); + if (control != null && !control.isDisposed()) { + if (!origControl.isDisposed()) + origControl.dispose(); + origControl = (Control)modifier.createControl(control, null, columnIndex, context); + origControl.addDisposeListener(this); + } + } + protected void doSetFocus() { + if (control != null && !control.isDisposed()) + control.setFocus(); + }; + + @Override + public void widgetDisposed(DisposeEvent e) { + if (e.widget == origControl) { + reCreate(); + } + + } + + @Override + public String isValid(Object value) { + return modifier.isValid((String)value); + } + + } + + private class TreeNode implements IAdaptable { + private NodeContext context; + + private TreeNode parent; + private List children = new ArrayList(); + private GENodeQueryManager manager; + + private TreeNode(NodeContext context) { + if (context == null) + throw new NullPointerException(); + this.context = context; + contextToNodeMap.add(context, this); + manager = new GENodeQueryManager(explorerContext, null, null, ViewerRowReference.create(this)); + } + + public List getChildren() { + synchronized (children) { + return children; + } + } + + public TreeNode getParent() { + return parent; + } + + public NodeContext getContext() { + return context; + } + + public GENodeQueryManager getManager() { + return manager; + } + + public TreeNode addChild(NodeContext context) { + TreeNode child = new TreeNode(context); + child.parent = this; + children.add(child); + if (DEBUG) System.out.println("Add " + this + " -> " + child); + return child; + } + + public TreeNode addChild(int index, NodeContext context) { + + TreeNode child = new TreeNode(context); + child.parent = this; + children.add(index,child); + if (DEBUG) System.out.println("Add " + this + " -> " + child + " at " + index); + return child; + } + + public TreeNode setChild(int index, NodeContext context) { + + TreeNode child = new TreeNode(context); + child.parent = this; + children.set(index,child); + if (DEBUG) System.out.println("Set " + this + " -> " + child + " at " + index); + return child; + } + + public int distanceToRoot() { + int i = 0; + TreeNode n = getParent(); + while (n != null) { + n = n.getParent(); + i++; + } + return i; + + } + + public void dispose() { + if (parent != null) + parent.children.remove(this); + dispose2(); + } + + public void dispose2() { + if (DEBUG) System.out.println("dispose " + this); + parent = null; + for (TreeNode n : children) { + n.dispose2(); + } + clearCache(); + children.clear(); + contextToNodeMap.remove(context, this); + context = null; + manager.dispose(); + manager = null; + } + + private void clearCache() { + if (explorerContext != null) { + GECache2 cache = explorerContext.cache; + + if (cache != null) { + cache.dispose(context); + } + } + } + + public boolean updateChildren() { + if (context == null) + throw new IllegalStateException("Node is disposed."); + + NodeContext[] childContexts = manager.query(context, BuiltinKeys.FINAL_CHILDREN); + + if (DEBUG) System.out.println("updateChildren " + childContexts.length + " " + this); + + + boolean modified = false; + synchronized (children) { + + int oldCount = children.size(); + BijectionMap indexes = new BijectionMap(); + Set mapped = new HashSet(); + boolean reorder = false; + // locate matching pairs form old and new children + for (int i = 0; i < oldCount; i++) { + NodeContext oldCtx = children.get(i).context; + for (int j = 0; j oldChildren = new ArrayList(oldCount); + oldChildren.addAll(children); + if (childContexts.length >= oldCount) { + for (int i = 0; i < oldCount; i++) { + Integer oldIndex = indexes.getLeft(i); + if (oldIndex == null) { + setChild(i, childContexts[i]); + } else { + TreeNode n = oldChildren.get(oldIndex); + children.set(i, n); + } + + } + for (int i = oldCount; i < childContexts.length; i++) { + addChild(childContexts[i]); + } + } else { + for (int i = 0; i < childContexts.length; i++) { + Integer oldIndex = indexes.getLeft(i); + if (oldIndex == null) { + setChild(i, childContexts[i]); + } else { + TreeNode n = oldChildren.get(oldIndex); + children.set(i, n); + } + } + for (int i = oldCount -1; i >= childContexts.length; i--) { + children.remove(i); + } + } + for (int i = 0; i < oldChildren.size(); i++) { + if (!indexes.containsLeft(i)) { + oldChildren.get(i).dispose2(); + } + } + + } + + } + return modified; + } + + public boolean isDisposed() { + return context == null; + } + + @SuppressWarnings("unchecked") + @Override + public T getAdapter(Class adapter) { + if (adapter == NodeContext.class) + return (T) context; + return context.getAdapter(adapter); + } + +// @Override +// public String toString() { +// String s = ""; +// if (manager != null) { +// +// s+= super.toString() + " "; +// try { +// Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER); +// Map labels = labeler.getLabels(); +// for (Entry l : labels.entrySet()) { +// s+= l.getKey() + " : " + l.getValue() + " "; +// } +// } catch (Exception e) { +// +// } +// } else { +// s = super.toString(); +// } +// if (context != null) +// s += " context " + context.hashCode(); +// return s; +// +// } + + } + + private static class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor implements + IsExpandedProcessor, ProcessorLifecycle { + /** + * The set of currently expanded node contexts. + */ + private final HashSet expanded = new HashSet(); + private final HashMap expandedQueries = new HashMap(); + + private Tree tree; + + public TreeNodeIsExpandedProcessor() { + } + + @Override + public Object getIdentifier() { + return BuiltinKeys.IS_EXPANDED; + } + + @Override + public String toString() { + return "IsExpandedProcessor"; + } + + @Override + public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey key) { + boolean isExpanded = expanded.contains(context); + expandedQueries.put(context, updater); + return Boolean.valueOf(isExpanded); + } + + @Override + public Collection getExpanded() { + return new HashSet(expanded); + } + + @Override + public boolean getExpanded(NodeContext context) { + return this.expanded.contains(context); + } + + @Override + public boolean setExpanded(NodeContext context, boolean expanded) { + return _setExpanded(context, expanded); + } + + @Override + public boolean replaceExpanded(NodeContext context, boolean expanded) { + return nodeStatusChanged(context, expanded); + } + + private boolean _setExpanded(NodeContext context, boolean expanded) { + if (expanded) { + return this.expanded.add(context); + } else { + return this.expanded.remove(context); + } + } + + Listener treeListener = new Listener() { + @Override + public void handleEvent(Event event) { + TreeNode node = (TreeNode) event.item.getData(); + NodeContext context = node.getContext(); + switch (event.type) { + case SWT.Expand: + nodeStatusChanged(context, true); + break; + case SWT.Collapse: + nodeStatusChanged(context, false); + break; + } + } + }; + + protected boolean nodeStatusChanged(NodeContext context, boolean expanded) { + boolean result = _setExpanded(context, expanded); + PrimitiveQueryUpdater updater = expandedQueries.get(context); + if (updater != null) + updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded); + return result; + } + + @Override + public void attached(GraphExplorer explorer) { + Object control = explorer.getControl(); + if (control instanceof Tree) { + this.tree = (Tree) control; + tree.addListener(SWT.Expand, treeListener); + tree.addListener(SWT.Collapse, treeListener); + } else { + System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control); + } + } + + @Override + public void clear() { + expanded.clear(); + expandedQueries.clear(); + } + + @Override + public void detached(GraphExplorer explorer) { + clear(); + if (tree != null) { + tree.removeListener(SWT.Expand, treeListener); + tree.removeListener(SWT.Collapse, treeListener); + tree = null; + } + } + } + + private void printTree(TreeNode node, int depth) { + String s = ""; + for (int i = 0; i < depth; i++) { + s += " "; + } + s += node; + System.out.println(s); + int d = depth+1; + for (TreeNode n : node.getChildren()) { + printTree(n, d); + } + + } + + /** + * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used) + */ + final private static class GECacheKey { + + private NodeContext context; + private CacheKey key; + + GECacheKey(NodeContext context, CacheKey key) { + this.context = context; + this.key = key; + if (context == null || key == null) + throw new IllegalArgumentException("Null context or key is not accepted"); + } + + GECacheKey(GECacheKey other) { + this.context = other.context; + this.key = other.key; + if (context == null || key == null) + throw new IllegalArgumentException("Null context or key is not accepted"); + } + + void setValues(NodeContext context, CacheKey key) { + this.context = context; + this.key = key; + if (context == null || key == null) + throw new IllegalArgumentException("Null context or key is not accepted"); + } + + @Override + public int hashCode() { + return context.hashCode() | key.hashCode(); + } + + @Override + public boolean equals(Object object) { + + if (this == object) + return true; + else if (object == null) + return false; + + GECacheKey i = (GECacheKey) object; + + return key.equals(i.key) && context.equals(i.context); + + } + + }; + + /** + * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data. + */ + private static class GECache2 implements IGECache { + + final HashMap entries = new HashMap(); + final HashMap> treeReferences = new HashMap>(); + final HashMap> keyRefs = new HashMap>(); + + /** + * This single instance is used for all get operations from the cache. This + * should work since the GE cache is meant to be single-threaded within the + * current UI thread, what ever that thread is. For put operations which + * store the key, this is not used. + */ + NodeContext getNC = new NodeContext() { + @Override + public T getAdapter(Class adapter) { + return null; + } + + @Override + public T getConstant(ConstantKey key) { + return null; + } + + @Override + public Set> getKeys() { + return Collections.emptySet(); + } + }; + CacheKey getCK = new CacheKey() { + @Override + public Object processorIdenfitier() { + return this; + } + }; + GECacheKey getKey = new GECacheKey(getNC, getCK); + + + private void addKey(GECacheKey key) { + Set refs = keyRefs.get(key.context); + if (refs != null) { + refs.add(key); + } else { + refs = new HashSet(); + refs.add(key); + keyRefs.put(key.context, refs); + } + } + + private void removeKey(GECacheKey key) { + Set refs = keyRefs.get(key.context); + if (refs != null) { + refs.remove(key); + } + } + + public IGECacheEntry put(NodeContext context, CacheKey key, T value) { +// if (DEBUG) System.out.println("Add entry " + context + " " + key); + IGECacheEntry entry = new GECacheEntry(context, key, value); + GECacheKey gekey = new GECacheKey(context, key); + entries.put(gekey, entry); + addKey(gekey); + return entry; + } + + @SuppressWarnings("unchecked") + public T get(NodeContext context, CacheKey key) { + getKey.setValues(context, key); + IGECacheEntry entry = entries.get(getKey); + if (entry == null) + return null; + return (T) entry.getValue(); + } + + @Override + public IGECacheEntry getEntry(NodeContext context, CacheKey key) { + assert(context != null); + assert(key != null); + getKey.setValues(context, key); + return entries.get(getKey); + } + + @Override + public void remove(NodeContext context, CacheKey key) { +// if (DEBUG) System.out.println("Remove entry " + context + " " + key); + getKey.setValues(context, key); + entries.remove(getKey); + removeKey(getKey); + } + + @Override + public Set getTreeReference(NodeContext context, CacheKey key) { + assert(context != null); + assert(key != null); + getKey.setValues(context, key); + return treeReferences.get(getKey); + } + + @Override + public void putTreeReference(NodeContext context, CacheKey key, UIElementReference reference) { + assert(context != null); + assert(key != null); + //if (DEBUG) System.out.println("Add tree reference " + context + " " + key); + getKey.setValues(context, key); + Set refs = treeReferences.get(getKey); + if (refs != null) { + refs.add(reference); + } else { + refs = new HashSet(4); + refs.add(reference); + GECacheKey gekey = new GECacheKey(getKey); + treeReferences.put(gekey, refs); + addKey(gekey); + } + } + + @Override + public Set removeTreeReference(NodeContext context, CacheKey key) { + assert(context != null); + assert(key != null); + //if (DEBUG) System.out.println("Remove tree reference " + context + " " + key); + getKey.setValues(context, key); + removeKey(getKey); + return treeReferences.remove(getKey); + } + + @Override + public boolean isShown(NodeContext context) { + return references.get(context) > 0; + } + + private TObjectIntHashMap references = new TObjectIntHashMap(); + + @Override + public void incRef(NodeContext context) { + int exist = references.get(context); + references.put(context, exist+1); + } + + @Override + public void decRef(NodeContext context) { + int exist = references.get(context); + references.put(context, exist-1); + if(exist == 1) { + references.remove(context); + } + } + + public void dispose() { + references.clear(); + entries.clear(); + treeReferences.clear(); + keyRefs.clear(); + } + + public void dispose(NodeContext context) { + Set keys = keyRefs.remove(context); + if (keys != null) { + for (GECacheKey key : keys) { + entries.remove(key); + treeReferences.remove(key); + } + } + } + } + + + /** + * Non-functional cache to replace actual cache when GEContext is disposed. + * + * @author mlmarko + * + */ + private static class DummyCache extends GECache2 { + + @Override + public IGECacheEntry getEntry(NodeContext context, CacheKey key) { + return null; + } + + @Override + public IGECacheEntry put(NodeContext context, CacheKey key, + T value) { + return null; + } + + @Override + public void putTreeReference(NodeContext context, CacheKey key, + UIElementReference reference) { + } + + @Override + public T get(NodeContext context, CacheKey key) { + return null; + } + + @Override + public Set getTreeReference( + NodeContext context, CacheKey key) { + return null; + } + + @Override + public void remove(NodeContext context, CacheKey key) { + + } + + @Override + public Set removeTreeReference( + NodeContext context, CacheKey key) { + return null; + } + + @Override + public boolean isShown(NodeContext context) { + return false; + } + + @Override + public void incRef(NodeContext context) { + + } + + @Override + public void decRef(NodeContext context) { + + } + + @Override + public void dispose() { + super.dispose(); + } + } + + @Override + public Object getClicked(Object event) { + MouseEvent e = (MouseEvent)event; + final Tree tree = (Tree) e.getSource(); + Point point = new Point(e.x, e.y); + TreeItem item = tree.getItem(point); + + // No selectable item at point? + if (item == null) + return null; + + Object data = item.getData(); + return data; + } +}