X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.browsing.ui.nattable%2Fsrc%2Forg%2Fsimantics%2Fbrowsing%2Fui%2Fnattable%2FNatTableGraphExplorer.java;fp=bundles%2Forg.simantics.browsing.ui.nattable%2Fsrc%2Forg%2Fsimantics%2Fbrowsing%2Fui%2Fnattable%2FNatTableGraphExplorer.java;h=aa2c5b21e568485fdfdd709534a2619cf252b711;hp=0000000000000000000000000000000000000000;hb=96bb7ef9cbe42d82eb58306d8f9b62392cc29ba8;hpb=ae5bb63c5c88f6569518fed2a24df86fbd0570ff diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableGraphExplorer.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableGraphExplorer.java new file mode 100644 index 000000000..aa2c5b21e --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableGraphExplorer.java @@ -0,0 +1,2762 @@ +package org.simantics.browsing.ui.nattable; + +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.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.ColumnWeightData; +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.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.window.Window; +import org.eclipse.nebula.widgets.nattable.NatTable; +import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration; +import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes; +import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; +import org.eclipse.nebula.widgets.nattable.config.IEditableRule; +import org.eclipse.nebula.widgets.nattable.coordinate.Range; +import org.eclipse.nebula.widgets.nattable.data.IDataProvider; +import org.eclipse.nebula.widgets.nattable.data.ListDataProvider; +import org.eclipse.nebula.widgets.nattable.data.convert.DefaultDisplayConverter; +import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes; +import org.eclipse.nebula.widgets.nattable.edit.EditConfigHelper; +import org.eclipse.nebula.widgets.nattable.edit.ICellEditHandler; +import org.eclipse.nebula.widgets.nattable.edit.config.DefaultEditBindings; +import org.eclipse.nebula.widgets.nattable.edit.config.DefaultEditConfiguration; +import org.eclipse.nebula.widgets.nattable.edit.editor.AbstractCellEditor; +import org.eclipse.nebula.widgets.nattable.edit.editor.ComboBoxCellEditor; +import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor; +import org.eclipse.nebula.widgets.nattable.edit.editor.IEditErrorHandler; +import org.eclipse.nebula.widgets.nattable.edit.editor.TextCellEditor; +import org.eclipse.nebula.widgets.nattable.edit.gui.AbstractDialogCellEditor; +import org.eclipse.nebula.widgets.nattable.grid.GridRegion; +import org.eclipse.nebula.widgets.nattable.grid.cell.AlternatingRowConfigLabelAccumulator; +import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider; +import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider; +import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer; +import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer; +import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultColumnHeaderDataLayer; +import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer; +import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer; +import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer; +import org.eclipse.nebula.widgets.nattable.hideshow.ColumnHideShowLayer; +import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent; +import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent; +import org.eclipse.nebula.widgets.nattable.layer.DataLayer; +import org.eclipse.nebula.widgets.nattable.layer.ILayerListener; +import org.eclipse.nebula.widgets.nattable.layer.LabelStack; +import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator; +import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; +import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent; +import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter; +import org.eclipse.nebula.widgets.nattable.reorder.ColumnReorderLayer; +import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; +import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum; +import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration; +import org.eclipse.nebula.widgets.nattable.style.DisplayMode; +import org.eclipse.nebula.widgets.nattable.ui.menu.AbstractHeaderMenuConfiguration; +import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder; +import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer; +import org.eclipse.nebula.widgets.nattable.widget.EditModeEnum; +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.GC; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.graphics.Rectangle; +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.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.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.nattable.override.DefaultTreeLayerConfiguration2; +import org.simantics.browsing.ui.swt.Activator; +import org.simantics.browsing.ui.swt.AdaptableHintContext; +import org.simantics.browsing.ui.swt.DefaultImageDecoratorsProcessor; +import org.simantics.browsing.ui.swt.DefaultIsExpandedProcessor; +import org.simantics.browsing.ui.swt.DefaultLabelDecoratorsProcessor; +import org.simantics.browsing.ui.swt.DefaultSelectedImagerProcessor; +import org.simantics.browsing.ui.swt.DefaultShowMaxChildrenProcessor; +import org.simantics.browsing.ui.swt.GraphExplorerImplBase; +import org.simantics.browsing.ui.swt.ImageLoaderJob; +import org.simantics.browsing.ui.swt.ViewerCellReference; +import org.simantics.browsing.ui.swt.ViewerRowReference; +import org.simantics.browsing.ui.swt.internal.Threads; +import org.simantics.db.layer0.SelectionHints; +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; + +/** + * NatTable base GraphExplorer + * + * + * FIXME : asynchronous node loading does not work properly + check expanded/collapsed sate handling + * TODO: InputValidators + input errors + * TODO: ability to hide headers + * TODO: code cleanup (copied from GraphExplorerImpl2) + * + * @author Marko Luukkainen + * + */ +public class NatTableGraphExplorer extends GraphExplorerImplBase implements GraphExplorer{ + public static final int DEFAULT_MAX_CHILDREN = 1000; + private static final boolean DEBUG_SELECTION_LISTENERS = false; + private static final boolean DEBUG = false; + + private Composite composite; + private NatTable natTable; + + private GETreeLayer treeLayer; + private DataLayer dataLayer; + private ViewportLayer viewportLayer; + private SelectionLayer selectionLayer; + private GEColumnHeaderDataProvider columnHeaderDataProvider; + private GEColumnAccessor columnAccessor; + private DefaultRowHeaderDataLayer rowHeaderDataLayer; + private DataLayer columnHeaderDataLayer; + private DataLayer cornerDataLayer; + + private List list = new ArrayList<>(); + + private NatTableSelectionAdaptor selectionAdaptor; + private NatTableColumnLayout layout; + + LocalResourceManager localResourceManager; + 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(); + + 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 MapList contextToNodeMap; + + private ModificationContext modificationContext = null; + + private boolean filterSelectionEdit = true; + + 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 NatTableGraphExplorer(Composite parent) { + this(parent, SWT.BORDER | SWT.MULTI ); + } + + public NatTableGraphExplorer(Composite parent, int style) { + this.composite = parent; + + + this.localResourceManager = new LocalResourceManager(JFaceResources.getResources()); + this.resourceManager = new DeviceResourceManager(parent.getDisplay()); + + this.imageLoaderJob = new ImageLoaderJob(this); + this.imageLoaderJob.setPriority(Job.DECORATE); + contextToNodeMap = new MapList(); + + 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); + + originalFont = JFaceResources.getDefaultFontDescriptor(); + + columns = new Column[0]; + createNatTable(); + layout = new NatTableColumnLayout(natTable, columnHeaderDataProvider, rowHeaderDataLayer); + this.composite.setLayout(layout); + + setBasicListeners(); + setDefaultProcessors(); + + natTable.addDisposeListener(new DisposeListener() { + + @Override + public void widgetDisposed(DisposeEvent e) { + doDispose(); + + } + }); + + 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; + } + } + }; + + natTable.addListener(SWT.Activate, listener); + natTable.addListener(SWT.Deactivate, listener); + natTable.addListener(SWT.Show, listener); + natTable.addListener(SWT.Hide, listener); + natTable.addListener(SWT.Paint,listener); + + setColumns( new Column[] { new Column(ColumnKeys.SINGLE) }); + + } + + private long focusGainedAt = 0L; + private boolean visible = false; + private Collection selectedNodes = new ArrayList(); + + protected void setBasicListeners() { + + natTable.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); + } + }); + natTable.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); + } + } + }); + natTable.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); + } + } + }); + + selectionAdaptor.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()]))); + } + }); + + selectionAdaptor.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 = composite.getDisplay(); + if (display.getThread() != Thread.currentThread()) { + throw new RuntimeException("Invoke from SWT thread only"); + } +// System.out.println("doSetRoot " + root); + if (isDisposed()) + return; + if (natTable.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, explorerContext); + 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(); + listReIndex(); + natTable.refresh(true); + } + } + }); + + } + + private synchronized void listReIndex() { + list.clear(); + for (TreeNode c : rootNode.getChildren()) + _insertToList(c); + } + + private void _insertToList(TreeNode n) { + n.setListIndex(list.size()); + list.add(n); + for (TreeNode c : n.getChildren()) { + _insertToList(c); + } + } + + 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; + //FIXME if(natTable != null) this.columnHeaderDataLayer.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 = composite.getDisplay(); + if (d.getThread() == Thread.currentThread()) { + doSetColumns(columns, callback); + natTable.refresh(true); + }else + d.asyncExec(new Runnable() { + @Override + public void run() { + if (natTable == null) + return; + if (natTable.isDisposed()) + return; + doSetColumns(columns, callback); + natTable.refresh(true); + natTable.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 void doSetColumns(Column[] cols, Consumer> callback) { + + 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; + + columnHeaderDataProvider.updateColumnSizes(); + + Map map = new HashMap(); + + // FIXME : temporary workaround for ModelBrowser. +// natTable.setHeaderVisible(columns.length == 1 ? false : columnsAreVisible); + + int columnIndex = 0; + + for (Column column : columns) { + int width = column.getWidth(); + if(column.hasGrab()) { + if (width < 0) + width = 1; + layout.setColumnData(columnIndex, new ColumnWeightData(column.getWeight(), width)); + + } else { + if (width < 0) + width = 50; + layout.setColumnData(columnIndex, new ColumnWeightData(columns.length > 1 ? 0 : 1, width)); + + } + 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 selectionAdaptor.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) { + selectionAdaptor.addSelectionListener(listener); + } + + public void removeSelectionListener(SelectionListener listener) { + selectionAdaptor.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 (natTable!= null && !natTable.isDisposed()) + natTable.redraw(); + } + }); + return; + } + doSetRoot(context); + } + + private void setRootContext0(final NodeContext context) { + Assert.isNotNull(context, "root must not be null"); + if (isDisposed() || natTable.isDisposed()) + return; + Display display = natTable.getDisplay(); + if (display.getThread() == Thread.currentThread()) { + setRoot(context); + } else { + display.asyncExec(new Runnable() { + @Override + public void run() { + setRoot(context); + } + }); + } + } + + @Override + public void setFocus() { + natTable.setFocus(); + } + + @SuppressWarnings("unchecked") + @Override + public T getControl() { + return (T)natTable; + } + + + @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 = natTable.getDisplay(); + natTable.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. + if (rootNode != null) { + rootNode.dispose(); + rootNode = null; + } + 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; + + 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; +// if (natTable != null) { +// natTable.dispose(); +// natTable = null; +// } + treeLayer = null; + dataLayer = null; + viewportLayer = null; + selectionLayer = null; + columnHeaderDataProvider = null; + columnAccessor = null; + rowHeaderDataLayer = null; + columnHeaderDataLayer = null; + cornerDataLayer = null; + + } + + @Override + public boolean select(NodeContext context) { + + assertNotDisposed(); + + if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) { + StructuredSelection s = new StructuredSelection(); + selectionAdaptor.setSelection(s); + selectionProvider.setAndFireNonEqualSelection(s); + return true; + } + + selectionAdaptor.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; + + return true; //FIXME +// 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) { + for (TreeNode n : contextToNodeMap.getValues(context)) { + if (expanded) + treeLayer.expandTreeRow(n.getListIndex()); + else + treeLayer.collapseTreeRow(n.getListIndex()); + } + //viewer.setExpandedState(context, expanded); + + } + + @Override + public void setAutoExpandLevel(int level) { + this.autoExpandLevel = level; + treeLayer.expandAllToLevel(level); + //viewer.setAutoExpandLevel(level); + } + + int maxChildren = 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(NatTable natTable) { + if (element != null) { + + if (element.isDisposed()) { + return; + } + if (((TreeNode)element).updateChildren()) { + listReIndex(); + natTable.refresh(true); + //viewer.refresh(element,true); + } else { + if (columnIndex >= 0) { + natTable.redraw(); + //viewer.update(element, new String[]{columns[columnIndex].getKey()}); + } else { + natTable.redraw(); + //viewer.refresh(element,true); + } + } + + if (!element.isDisposed() && autoExpandLevel > 1 && !element.isExpanded() && element.getDepth() <= autoExpandLevel) { + expand = true; + treeLayer.expandTreeRow(element.getListIndex()); + //viewer.setExpandedState(element, true); + expand = false; + } + } else { + if (rootNode.updateChildren()) { + listReIndex(); + natTable.refresh(true); + //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 (natTable.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 (natTable.isDisposed()) + return; + if (element != null && element.isDisposed()) + return; + synchronized (pendingItems) { + + pendingItems.add(new UpdateItem(element)); + if (updating) return; + updateCounter++; + scheduleUpdater(); + } + } + + boolean scheduleUpdater() { + + if (natTable.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 (natTable == null || natTable.isDisposed()) + return; + + if (updateCounter > 0) { + updateCounter = 0; + uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS); + } else { + natTable.getDisplay().asyncExec(new UpdateRunner(NatTableGraphExplorer.this, NatTableGraphExplorer.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"; +// FIXME: +// 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 (natTable.isDisposed()) + return; + Display d = natTable.getDisplay(); + if (d.getThread() == Thread.currentThread()) { + selectionAdaptor.setSelection(sel); + } else { + d.asyncExec(new Runnable() { + @Override + public void run() { + if (natTable.isDisposed()) + return; + selectionAdaptor.setSelection(sel); + } + }); + } + } + } + + @Override + public void setModificationContext(ModificationContext modificationContext) { + this.modificationContext = modificationContext; + + } + + final ExecutorService queryUpdateScheduler = Threads.getExecutor(); + + + private double getDisplayScale() { + Point dpi = Display.getCurrent().getDPI(); + return (double)dpi.x/96.0; + } + + private void createNatTable() { + GETreeData treeData = new GETreeData(list); + GETreeRowModel treeRowModel = new GETreeRowModel(treeData); + columnAccessor = new GEColumnAccessor(this); + + IDataProvider dataProvider = new ListDataProvider(list, columnAccessor); + + int defaultFontSize = 12; + int height = (int)Math.ceil(((double)(defaultFontSize))*getDisplayScale()) + DataLayer.DEFAULT_ROW_HEIGHT-defaultFontSize; + dataLayer = new DataLayer(dataProvider, DataLayer.DEFAULT_COLUMN_WIDTH, height); + + // resizable rows are unnecessary in Sulca report. + dataLayer.setRowsResizableByDefault(false); + + // Row header layer + DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(dataProvider); + rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider); + + // adjust row header column width so that row numbers fit into the column. + //adjustRowHeaderWidth(list.size()); + + // Column header layer + columnHeaderDataProvider = new GEColumnHeaderDataProvider(this, dataLayer); + columnHeaderDataLayer = new DefaultColumnHeaderDataLayer(columnHeaderDataProvider); + columnHeaderDataLayer.setDefaultRowHeight(height); + columnHeaderDataProvider.updateColumnSizes(); + + //ISortModel sortModel = new EcoSortModel(this, generator,dataLayer); + + // Column re-order + hide + ColumnReorderLayer columnReorderLayer = new ColumnReorderLayer(dataLayer); + ColumnHideShowLayer columnHideShowLayer = new ColumnHideShowLayer(columnReorderLayer); + + + treeLayer = new GETreeLayer(columnHideShowLayer, treeRowModel, false); + + selectionLayer = new SelectionLayer(treeLayer); + + viewportLayer = new ViewportLayer(selectionLayer); + + ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, viewportLayer, selectionLayer); + // Note: The column header layer is wrapped in a filter row composite. + // This plugs in the filter row functionality + + ColumnOverrideLabelAccumulator labelAccumulator = new ColumnOverrideLabelAccumulator(columnHeaderDataLayer); + columnHeaderDataLayer.setConfigLabelAccumulator(labelAccumulator); + + // Register labels + //SortHeaderLayer sortHeaderLayer = new SortHeaderLayer(columnHeaderLayer, sortModel, false); + + RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, viewportLayer, selectionLayer); + + // Corner layer + DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider); + cornerDataLayer = new DataLayer(cornerDataProvider); + //CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, sortHeaderLayer); + CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, columnHeaderLayer); + + // Grid + //GridLayer gridLayer = new GridLayer(viewportLayer,sortHeaderLayer,rowHeaderLayer, cornerLayer); + GridLayer gridLayer = new GridLayer(viewportLayer, columnHeaderLayer,rowHeaderLayer, cornerLayer, false); + + /* Since 1.4.0, alternative row rendering uses row indexes in the original data list. + When combined with collapsed tree rows, rows with odd or even index may end up next to each other, + which defeats the purpose of alternating colors. This overrides that and returns the functionality + that we had with 1.0.1. */ + gridLayer.setConfigLabelAccumulatorForRegion(GridRegion.BODY, new RelativeAlternatingRowConfigLabelAccumulator()); + gridLayer.addConfiguration(new DefaultEditConfiguration()); + //gridLayer.addConfiguration(new DefaultEditBindings()); + gridLayer.addConfiguration(new GEEditBindings()); + + natTable = new NatTable(composite,gridLayer,false); + + //selectionLayer.registerCommandHandler(new EcoCopyDataCommandHandler(selectionLayer,columnHeaderDataLayer,columnAccessor, columnHeaderDataProvider)); + + natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable)); + natTable.addConfiguration(new DefaultTreeLayerConfiguration2(treeLayer)); + natTable.addConfiguration(new SingleClickSortConfiguration()); + //natTable.addLayerListener(this); + + natTable.addConfiguration(new GENatTableThemeConfiguration(treeData)); + natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable)); + + natTable.addConfiguration(new AbstractRegistryConfiguration() { + + @Override + public void configureRegistry(IConfigRegistry configRegistry) { + configRegistry.registerConfigAttribute( + EditConfigAttributes.CELL_EDITABLE_RULE, + new IEditableRule() { + + @Override + public boolean isEditable(ILayerCell cell, + IConfigRegistry configRegistry) { + int col = cell.getColumnIndex(); + int row = cell.getRowIndex(); + TreeNode node = list.get(row); + Modifier modifier = getModifier(node,col); + return modifier != null; + + } + + @Override + public boolean isEditable(int columnIndex, int rowIndex) { + // there are no callers? + return false; + } + + }); + configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, new AdaptableCellEditor()); + configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, new DefaultDisplayConverter(),DisplayMode.EDIT); + // configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_PAINTER, new GECellPainter(),DisplayMode.NORMAL); + + + } + }); + + natTable.configure(); + +// natTable.addListener(SWT.MenuDetect, new NatTableMenuListener()); + +// DefaultToolTip toolTip = new EcoCellToolTip(natTable, columnAccessor); +// toolTip.setBackgroundColor(natTable.getDisplay().getSystemColor(SWT.COLOR_WHITE)); +// toolTip.setPopupDelay(500); +// toolTip.activate(); +// toolTip.setShift(new Point(10, 10)); + + +// menuManager.createContextMenu(composite); +// natTable.setMenu(getMenuManager().getMenu()); + + selectionAdaptor = new NatTableSelectionAdaptor(natTable, selectionLayer, treeData); + } + + 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()); + + } + + private class AdaptableCellEditor implements ICellEditor { + ICellEditor editor; + + @Override + public Control activateCell(Composite parent, Object originalCanonicalValue, EditModeEnum editMode, + ICellEditHandler editHandler, ILayerCell cell, IConfigRegistry configRegistry) { + int col = cell.getColumnIndex(); + int row = cell.getRowIndex(); + TreeNode node = list.get(row); + Modifier modifier = getModifier(node, col); + if (modifier == null) + return null; + + editor = null; + if (modifier instanceof DialogModifier) { + DialogModifier mod = (DialogModifier)modifier; + editor = new DialogCellEditor(node, col, mod); + } else if (modifier instanceof CustomModifier) { + CustomModifier mod = (CustomModifier)modifier; + editor = new CustomCellEditor(node, col, mod); + } else if (modifier instanceof EnumerationModifier) { + EnumerationModifier mod = (EnumerationModifier)modifier; + editor = new ComboBoxCellEditor(mod.getValues()); + } else { + editor = new TextCellEditor(); + } + + return editor.activateCell(parent, originalCanonicalValue, editMode, editHandler, cell, configRegistry); + } + + @Override + public int getColumnIndex() { + return editor.getColumnIndex(); + } + + @Override + public int getRowIndex() { + return editor.getRowIndex(); + } + + @Override + public int getColumnPosition() { + return editor.getColumnPosition(); + } + + @Override + public int getRowPosition() { + return editor.getRowPosition(); + } + + @Override + public Object getEditorValue() { + return editor.getEditorValue(); + } + + @Override + public void setEditorValue(Object value) { + editor.setEditorValue(value); + + } + + @Override + public Object getCanonicalValue() { + return editor.getCanonicalValue(); + } + + @Override + public Object getCanonicalValue(IEditErrorHandler conversionErrorHandler) { + return editor.getCanonicalValue(); + } + + @Override + public void setCanonicalValue(Object canonicalValue) { + editor.setCanonicalValue(canonicalValue); + + } + + @Override + public boolean validateCanonicalValue(Object canonicalValue) { + return editor.validateCanonicalValue(canonicalValue); + } + + @Override + public boolean validateCanonicalValue(Object canonicalValue, IEditErrorHandler validationErrorHandler) { + return editor.validateCanonicalValue(canonicalValue, validationErrorHandler); + } + + @Override + public boolean commit(MoveDirectionEnum direction) { + return editor.commit(direction); + } + + @Override + public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit) { + return editor.commit(direction, closeAfterCommit); + } + + @Override + public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit, boolean skipValidation) { + return editor.commit(direction, closeAfterCommit, skipValidation); + } + + @Override + public void close() { + editor.close(); + + } + + @Override + public boolean isClosed() { + return editor.isClosed(); + } + + @Override + public Control getEditorControl() { + return editor.getEditorControl(); + } + + @Override + public Control createEditorControl(Composite parent) { + return editor.createEditorControl(parent); + } + + @Override + public boolean openInline(IConfigRegistry configRegistry, List configLabels) { + return EditConfigHelper.openInline(configRegistry, configLabels); + } + + @Override + public boolean supportMultiEdit(IConfigRegistry configRegistry, List configLabels) { + return editor.supportMultiEdit(configRegistry, configLabels); + } + + @Override + public boolean openMultiEditDialog() { + return editor.openMultiEditDialog(); + } + + @Override + public boolean openAdjacentEditor() { + return editor.openAdjacentEditor(); + } + + @Override + public boolean activateAtAnyPosition() { + return true; + } + + @Override + public boolean activateOnTraversal(IConfigRegistry configRegistry, List configLabels) { + return editor.activateOnTraversal(configRegistry, configLabels); + } + + @Override + public void addEditorControlListeners() { + editor.addEditorControlListeners(); + + } + + @Override + public void removeEditorControlListeners() { + editor.removeEditorControlListeners(); + + } + + @Override + public Rectangle calculateControlBounds(Rectangle cellBounds) { + return editor.calculateControlBounds(cellBounds); + } + + + } + + private class CustomCellEditor extends AbstractCellEditor { + TreeNode node; + CustomModifier customModifier; + Control control; + int column; + + public CustomCellEditor(TreeNode node, int column, CustomModifier customModifier) { + this.customModifier = customModifier; + this.node = node; + this.column = column; + } + + @Override + public Object getEditorValue() { + return customModifier.getValue(); + } + + @Override + public void setEditorValue(Object value) { + customModifier.modify(value.toString()); + + } + + @Override + public Control getEditorControl() { + return control; + } + + @Override + public Control createEditorControl(Composite parent) { + return (Control)customModifier.createControl(parent, null, column, node.getContext()); + + } + + @Override + protected Control activateCell(Composite parent, Object originalCanonicalValue) { + this.control = createEditorControl(parent); + return control; + } + + + } + + private class DialogCellEditor extends AbstractDialogCellEditor { + TreeNode node; + DialogModifier dialogModifier; + int column; + + String res = null; + Semaphore sem; + + boolean closed = false; + + public DialogCellEditor(TreeNode node, int column, DialogModifier dialogModifier) { + this.dialogModifier = dialogModifier; + this.node = node; + this.column = column; + } + + @Override + public int open() { + sem = new Semaphore(1); + Consumer callback = result -> { + res = result; + sem.release(); + }; + String status = dialogModifier.query(this.parent.getShell(), null, column, node.getContext(), callback); + if (status != null) { + closed = true; + return Window.CANCEL; + } + + try { + sem.acquire(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + closed = true; + return Window.OK; + } + + @Override + public DialogModifier createDialogInstance() { + closed = false; + return dialogModifier; + } + + @Override + public Object getDialogInstance() { + return (DialogModifier)this.dialog; + } + + @Override + public void close() { + + } + + @Override + public Object getEditorValue() { + return null; + } + + @Override + public void setEditorValue(Object value) { + // dialog modifier handles this internally + } + + @Override + public boolean isClosed() { + return closed; + } + + } + + + /** + * 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(); + + void queueImageTask(TreeNode node, ImageTask task) { + synchronized (imageTasks) { + imageTasks.put(node, task); + } + imageLoaderJob.scheduleIfNecessary(100); + } + + /** + * 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 NatTableGraphExplorer ge; + + GraphExplorerPostSelectionProvider(NatTableGraphExplorer 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.natTable.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.natTable.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 NatTableGraphExplorer ge; + + UpdateRunner(NatTableGraphExplorer 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.natTable.getVerticalBar(); + + + synchronized (ge.pendingItems) { + items = ge.pendingItems; + ge.pendingItems = new HashSet(); + } + if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size()); + + ge.natTable.setRedraw(false); + for (UpdateItem item : items) { + item.update(ge.natTable); + } + + // check if vertical scroll bar has become visible and refresh layout. + boolean currentlyVerticalBarVisible = verticalBar.isVisible(); + if (ge.verticalBarVisible != currentlyVerticalBarVisible) { + ge.verticalBarVisible = currentlyVerticalBarVisible; + ge.natTable.getParent().layout(); + } + + ge.natTable.setRedraw(true); + + synchronized (ge.pendingItems) { + if (!ge.scheduleUpdater()) { + ge.updating = false; + } + } + if (DEBUG) { + if (!ge.updating) { + ge.printTree(ge.rootNode, 0); + } + } + } + + } + + + + public static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext { + // This is for query debugging only. + + private NatTableGraphExplorer 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(NatTableGraphExplorer ge) { + this.ge = ge; + } + + public MapList getContextToNodeMap() { + if (ge == null) + return null; + return ge.contextToNodeMap; + } + + public NatTableGraphExplorer getGe() { + return 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 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 NatTable natTable; + private List list; + + 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); + } + } + + ILayerListener treeListener = new ILayerListener() { + + @Override + public void handleLayerEvent(ILayerEvent event) { + // TODO Auto-generated method stub + if (event instanceof ShowRowPositionsEvent) { + ShowRowPositionsEvent e = (ShowRowPositionsEvent)event; + for (Range r : e.getRowPositionRanges()) { + int expanded = viewportLayer.getRowIndexByPosition(r.start-2)+1; + //System.out.println("ex " + expanded); + if (expanded < 0) { + return; + } + nodeStatusChanged(list.get(expanded).getContext(), false); + } + } else if (event instanceof HideRowPositionsEvent) { + HideRowPositionsEvent e = (HideRowPositionsEvent)event; + for (Range r : e.getRowPositionRanges()) { + int collapsed = viewportLayer.getRowIndexByPosition(r.start-2)+1; + //System.out.println("col " + collapsed); + if (collapsed < 0) { + return; + } + nodeStatusChanged(list.get(collapsed).getContext(), false); + } + } + + } + }; + + 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 NatTable) { + this.natTable = (NatTable) control; + this.list = ((NatTableGraphExplorer)explorer).list; + natTable.addLayerListener(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 (natTable != null) { + natTable.removeLayerListener(treeListener); +// natTable.removeListener(SWT.Expand, treeListener); +// natTable.removeListener(SWT.Collapse, treeListener); + natTable = 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. + */ + public 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(); + } + } + +private class NatTableHeaderMenuConfiguration extends AbstractHeaderMenuConfiguration { + + + public NatTableHeaderMenuConfiguration(NatTable natTable) { + super(natTable); + } + + @Override + protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) { + return super.createColumnHeaderMenu(natTable) + .withHideColumnMenuItem() + .withShowAllColumnsMenuItem() + .withAutoResizeSelectedColumnsMenuItem(); + } + + @Override + protected PopupMenuBuilder createCornerMenu(NatTable natTable) { + return super.createCornerMenu(natTable) + .withShowAllColumnsMenuItem(); + } + @Override + protected PopupMenuBuilder createRowHeaderMenu(NatTable natTable) { + return super.createRowHeaderMenu(natTable); + } + } + + private static class RelativeAlternatingRowConfigLabelAccumulator extends AlternatingRowConfigLabelAccumulator { + + @Override + public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) { + configLabels.addLabel((rowPosition % 2 == 0 ? EVEN_ROW_CONFIG_TYPE : ODD_ROW_CONFIG_TYPE)); + } + } + + @Override + public Object getClicked(Object event) { + MouseEvent e = (MouseEvent)event; + final NatTable tree = (NatTable) e.getSource(); + Point point = new Point(e.x, e.y); + int y = natTable.getRowPositionByY(point.y); + int x = natTable.getColumnPositionByX(point.x); + if (x < 0 | y < 0) + return null; + return list.get(y); + } +}