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;h=6765f65ead2e0a87877ee52c73f969b452bac0d6;hp=f38f5fd9350bae2d3b5f4ecf338eb396afb2527b;hb=bbfae087089fa3126eefb18207fde0cfaa7315a3;hpb=145a2884933f2ffdd48d6835729e58f1152d274e 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 index f38f5fd93..6765f65ea 100644 --- 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 @@ -1,2879 +1,2882 @@ -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.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.data.validate.IDataValidator; -import org.eclipse.nebula.widgets.nattable.data.validate.ValidationFailedException; -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.DefaultEditConfiguration; -import org.eclipse.nebula.widgets.nattable.edit.config.DialogErrorHandling; -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.NatTableBorderOverlayPainter; -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.selection.command.SelectCellCommand; -import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration; -import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes; -import org.eclipse.nebula.widgets.nattable.style.DisplayMode; -import org.eclipse.nebula.widgets.nattable.style.Style; -import org.eclipse.nebula.widgets.nattable.ui.menu.AbstractHeaderMenuConfiguration; -import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder; -import org.eclipse.nebula.widgets.nattable.util.GUIHelper; -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.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 based GraphExplorer - * - * This GraphExplorer is not fully compatible with the other implementations, since it is not based on SWT.Tree. - * - * This implementation is useful in scenarios, where there are a lot of data to be displayed, the performance of NatTable is much better to SWT.Tree based implementations. - * - * - * 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 = 10000; - 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(style); - 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); - - // 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(); - rootNode.setExpanded(true); - listReIndex(); - } - } - }); - - } - - private synchronized void listReIndex() { - for (TreeNode n : list) { - n.setListIndex(-2); - } - list.clear(); - for (TreeNode c : rootNode.getChildren()) - _insertToList(c); - natTable.refresh(); - } - - private void _insertToList(TreeNode n) { - n.setListIndex(list.size()); - list.add(n); - for (TreeNode c : n.getChildren()) { - _insertToList(c); - } - } - - public List getItems() { - return Collections.unmodifiableList(list); - } - - 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(); - 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; - - } - - public boolean select(TreeNode node) { - assertNotDisposed(); - - if (!list.contains(node)) { - StructuredSelection s = new StructuredSelection(); - selectionAdaptor.setSelection(s); - selectionProvider.setAndFireNonEqualSelection(s); - return true; - } - selectionAdaptor.setSelection(new StructuredSelection(node)); - return false; - } - - public void show(TreeNode node) { - int index = node.getListIndex(); - - int position = treeLayer.getRowPositionByIndex(index); - if (position < 0) { - treeLayer.expandToTreeRow(index); - position = treeLayer.getRowPositionByIndex(index); - } - viewportLayer.moveRowPositionIntoViewport(position); - } - - @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()); - } - - } - - @Override - public void setAutoExpandLevel(int level) { - this.autoExpandLevel = level; - treeLayer.expandAllToLevel(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 (element.updateChildren()) { - if (DEBUG) { - System.out.println("Update Item updateChildren " + element.listIndex + " " + element); - printDebug(NatTableGraphExplorer.this); - } - listReIndex(); - if (!element.isHidden()) { - if (!element.isExpanded()) { - if (element.listIndex >= 0) - treeLayer.collapseTreeRow(element.listIndex); - if (DEBUG) { - System.out.println("Update Item collapse " + element.listIndex); - printDebug(NatTableGraphExplorer.this); - } - } else { - for (TreeNode c : element.getChildren()) - c.initData(); - } - } else { - TreeNode p = element.getCollapsedAncestor(); - if (p != null) { - if (element.listIndex >= 0) - treeLayer.collapseTreeRow(element.listIndex); - if (p.listIndex >= 0) - treeLayer.collapseTreeRow(p.listIndex); - if (DEBUG) { - System.out.println("Update Item ancetor collapse " + p.listIndex); - printDebug(NatTableGraphExplorer.this); - } - } - } - } else { -// if (columnIndex >= 0) { -// viewer.update(element, new String[]{columns[columnIndex].getKey()}); -// } else { -// viewer.refresh(element,true); -// } - natTable.redraw(); - } - - if (!element.autoExpanded && !element.isDisposed() && autoExpandLevel > 1 && !element.isExpanded() && element.getDepth() <= autoExpandLevel) { - expand = true; - element.autoExpanded = true; - element.initData(); - if (DEBUG) System.out.println("Update Item expand " + element.listIndex); - treeLayer.expandTreeRow(element.getListIndex()); - //viewer.setExpandedState(element, true); - expand = false; - } - } else { - if (rootNode.updateChildren()) { - listReIndex(); - } - } - } - - @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 (natTable.isDisposed()) - return; - if (element != null && element.isDisposed()) - return; - if (DEBUG) System.out.println("update " + element + " " + columnIndex); - synchronized (pendingItems) { - pendingItems.add(new UpdateItem(element, columnIndex)); - if (updating) return; - updateCounter++; - scheduleUpdater(); - } - } - - private void update(final TreeNode element) { - - if (natTable.isDisposed()) - return; - if (element != null && element.isDisposed()) - return; - if (DEBUG) System.out.println("update " + element); - 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(int style) { - 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, style)); - 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(EditConfigAttributes.CONVERSION_ERROR_HANDLER, new DialogErrorHandling(), DisplayMode.EDIT); - configRegistry.registerConfigAttribute(EditConfigAttributes.VALIDATION_ERROR_HANDLER, new DialogErrorHandling(), DisplayMode.EDIT); - configRegistry.registerConfigAttribute(EditConfigAttributes.DATA_VALIDATOR, new AdaptableDataValidator(),DisplayMode.EDIT); - - Style conversionErrorStyle = new Style(); - conversionErrorStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, GUIHelper.COLOR_RED); - conversionErrorStyle.setAttributeValue(CellStyleAttributes.FOREGROUND_COLOR, GUIHelper.COLOR_WHITE); - configRegistry.registerConfigAttribute(EditConfigAttributes.CONVERSION_ERROR_STYLE, conversionErrorStyle, DisplayMode.EDIT); - - configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, new DefaultDisplayConverter(),DisplayMode.EDIT); - - - } - }); - - if ((style & SWT.BORDER) > 0) { - natTable.addOverlayPainter(new NatTableBorderOverlayPainter()); - } - - 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 AdaptableDataValidator implements IDataValidator { - @Override - public boolean validate(ILayerCell cell, IConfigRegistry configRegistry, Object newValue) { - int col = cell.getColumnIndex(); - int row = cell.getRowIndex(); - return validate(col, row, newValue); - } - - @Override - public boolean validate(int col, int row, Object newValue) { - TreeNode node = list.get(row); - Modifier modifier = getModifier(node, col); - if (modifier == null) - return false; - - String err = modifier.isValid(newValue.toString()); - if (err == null) - return true; - modifier.isValid(newValue.toString()); - throw new ValidationFailedException(err); - } - } - - 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) { - printDebug(ge); - } - } - } - - } - - private static void printDebug(NatTableGraphExplorer ge) { - ge.printTree(ge.rootNode, 0); - System.out.println("Expanded"); - for (TreeNode n : ge.treeLayer.expanded) - System.out.println(n); - System.out.println("Expanded end"); - System.out.println("Hidden "); - for (int i : ge.treeLayer.getHiddenRowIndexes()) { - System.out.print(i + " "); - } - System.out.println(); -// Display.getCurrent().timerExec(1000, new Runnable() { -// -// @Override -// public void run() { -// System.out.println("Hidden delayed "); -// for (int i : ge.treeLayer.getHiddenRowIndexes()) { -// System.out.print(i + " "); -// } -// System.out.println(); -// } -// }); - } - - - 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) { - if (event instanceof ShowRowPositionsEvent) { - ShowRowPositionsEvent e = (ShowRowPositionsEvent)event; - for (Range r : e.getRowPositionRanges()) { - int expanded = viewportLayer.getRowIndexByPosition(r.start-2)+1; - if (DEBUG)System.out.println("IsExpandedProcessor expand " + expanded); - if (expanded < 0 || expanded >= list.size()) { - return; - } - nodeStatusChanged(list.get(expanded).getContext(), true); - } - } else if (event instanceof HideRowPositionsEvent) { - HideRowPositionsEvent e = (HideRowPositionsEvent)event; - for (Range r : e.getRowPositionRanges()) { - int collapsed = viewportLayer.getRowIndexByPosition(r.start-2); - if (DEBUG)System.out.println("IsExpandedProcessor collapse " + collapsed); - if (collapsed < 0 || collapsed >= list.size()) { - 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-1); - } -} +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.BiFunction; +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.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.data.validate.IDataValidator; +import org.eclipse.nebula.widgets.nattable.data.validate.ValidationFailedException; +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.DefaultEditConfiguration; +import org.eclipse.nebula.widgets.nattable.edit.config.DialogErrorHandling; +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.NatTableBorderOverlayPainter; +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.CellStyleAttributes; +import org.eclipse.nebula.widgets.nattable.style.DisplayMode; +import org.eclipse.nebula.widgets.nattable.style.Style; +import org.eclipse.nebula.widgets.nattable.ui.menu.AbstractHeaderMenuConfiguration; +import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder; +import org.eclipse.nebula.widgets.nattable.util.GUIHelper; +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.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.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 based GraphExplorer + * + * This GraphExplorer is not fully compatible with the other implementations, since it is not based on SWT.Tree. + * + * This implementation is useful in scenarios, where there are a lot of data to be displayed, the performance of NatTable is much better to SWT.Tree based implementations. + * + * + * 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 = 10000; + 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 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 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(style); + 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); + + // 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(); + rootNode.setExpanded(true); + listReIndex(); + } + } + }); + + } + + private synchronized void listReIndex() { + for (TreeNode n : list) { + n.setListIndex(-2); + } + list.clear(); + for (TreeNode c : rootNode.getChildren()) + _insertToList(c); + natTable.refresh(); + } + + private void _insertToList(TreeNode n) { + n.setListIndex(list.size()); + list.add(n); + for (TreeNode c : n.getChildren()) { + _insertToList(c); + } + } + + public List getItems() { + return Collections.unmodifiableList(list); + } + + 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(); + 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.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 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; + + } + + public boolean select(TreeNode node) { + assertNotDisposed(); + + if (!list.contains(node)) { + StructuredSelection s = new StructuredSelection(); + selectionAdaptor.setSelection(s); + selectionProvider.setAndFireNonEqualSelection(s); + return true; + } + selectionAdaptor.setSelection(new StructuredSelection(node)); + return false; + } + + public void show(TreeNode node) { + int index = node.getListIndex(); + + int position = treeLayer.getRowPositionByIndex(index); + if (position < 0) { + treeLayer.expandToTreeRow(index); + position = treeLayer.getRowPositionByIndex(index); + } + viewportLayer.moveRowPositionIntoViewport(position); + } + + @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()); + } + + } + + @Override + public void setAutoExpandLevel(int level) { + this.autoExpandLevel = level; + treeLayer.expandAllToLevel(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 (element.updateChildren()) { + if (DEBUG) { + System.out.println("Update Item updateChildren " + element.listIndex + " " + element); + printDebug(NatTableGraphExplorer.this); + } + listReIndex(); + if (!element.isHidden()) { + if (!element.isExpanded()) { + if (element.listIndex >= 0) + treeLayer.collapseTreeRow(element.listIndex); + if (DEBUG) { + System.out.println("Update Item collapse " + element.listIndex); + printDebug(NatTableGraphExplorer.this); + } + } else { + for (TreeNode c : element.getChildren()) + c.initData(); + } + } else { + TreeNode p = element.getCollapsedAncestor(); + if (p != null) { + if (element.listIndex >= 0) + treeLayer.collapseTreeRow(element.listIndex); + if (p.listIndex >= 0) + treeLayer.collapseTreeRow(p.listIndex); + if (DEBUG) { + System.out.println("Update Item ancetor collapse " + p.listIndex); + printDebug(NatTableGraphExplorer.this); + } + } + } + } else { +// if (columnIndex >= 0) { +// viewer.update(element, new String[]{columns[columnIndex].getKey()}); +// } else { +// viewer.refresh(element,true); +// } + element.initData(); + natTable.redraw(); + } + + if (!element.autoExpanded && !element.isDisposed() && autoExpandLevel > 1 && !element.isExpanded() && element.getDepth() <= autoExpandLevel) { + expand = true; + element.autoExpanded = true; + element.initData(); + if (DEBUG) System.out.println("Update Item expand " + element.listIndex); + treeLayer.expandTreeRow(element.getListIndex()); + //viewer.setExpandedState(element, true); + expand = false; + } + } else { + if (rootNode.updateChildren()) { + listReIndex(); + } + } + } + + @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 (natTable.isDisposed()) + return; + if (element != null && element.isDisposed()) + return; + if (DEBUG) System.out.println("update " + element + " " + columnIndex); + synchronized (pendingItems) { + pendingItems.add(new UpdateItem(element, columnIndex)); + if (updating) return; + updateCounter++; + scheduleUpdater(); + } + } + + private void update(final TreeNode element) { + + if (natTable.isDisposed()) + return; + if (element != null && element.isDisposed()) + return; + if (DEBUG) System.out.println("update " + element); + 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(); + + + public static double getDisplayScale() { + Point dpi = Display.getCurrent().getDPI(); + return (double)dpi.x/96.0; + } + + private void createNatTable(int style) { + GETreeData treeData = new GETreeData(list); + GETreeRowModel treeRowModel = new GETreeRowModel(treeData); + columnAccessor = new GEColumnAccessor(this); + + IDataProvider dataProvider = new ListDataProvider(list, columnAccessor); + +// FIXME: NatTable 1.0 required help to work with custom display scaling (Windows 7 display scaling). +// It seems that NatTable 1.4 breaks with the same code in Windows 7, so now the code is disabled. +// More testing with different hardware is required... +// 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); + dataLayer = new DataLayer(dataProvider); + + // 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, style)); + 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(EditConfigAttributes.CONVERSION_ERROR_HANDLER, new DialogErrorHandling(), DisplayMode.EDIT); + configRegistry.registerConfigAttribute(EditConfigAttributes.VALIDATION_ERROR_HANDLER, new DialogErrorHandling(), DisplayMode.EDIT); + configRegistry.registerConfigAttribute(EditConfigAttributes.DATA_VALIDATOR, new AdaptableDataValidator(),DisplayMode.EDIT); + + Style conversionErrorStyle = new Style(); + conversionErrorStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, GUIHelper.COLOR_RED); + conversionErrorStyle.setAttributeValue(CellStyleAttributes.FOREGROUND_COLOR, GUIHelper.COLOR_WHITE); + configRegistry.registerConfigAttribute(EditConfigAttributes.CONVERSION_ERROR_STYLE, conversionErrorStyle, DisplayMode.EDIT); + + configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, new DefaultDisplayConverter(),DisplayMode.EDIT); + + + } + }); + + if ((style & SWT.BORDER) > 0) { + natTable.addOverlayPainter(new NatTableBorderOverlayPainter()); + } + + 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 AdaptableDataValidator implements IDataValidator { + @Override + public boolean validate(ILayerCell cell, IConfigRegistry configRegistry, Object newValue) { + int col = cell.getColumnIndex(); + int row = cell.getRowIndex(); + return validate(col, row, newValue); + } + + @Override + public boolean validate(int col, int row, Object newValue) { + TreeNode node = list.get(row); + Modifier modifier = getModifier(node, col); + if (modifier == null) + return false; + + String err = modifier.isValid(newValue != null ? newValue.toString() : ""); + if (err == null) + return true; + throw new ValidationFailedException(err); + } + } + + 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) { + printDebug(ge); + } + } + } + + } + + private static void printDebug(NatTableGraphExplorer ge) { + ge.printTree(ge.rootNode, 0); + System.out.println("Expanded"); + for (TreeNode n : ge.treeLayer.expanded) + System.out.println(n); + System.out.println("Expanded end"); + System.out.println("Hidden "); + for (int i : ge.treeLayer.getHiddenRowIndexes()) { + System.out.print(i + " "); + } + System.out.println(); +// Display.getCurrent().timerExec(1000, new Runnable() { +// +// @Override +// public void run() { +// System.out.println("Hidden delayed "); +// for (int i : ge.treeLayer.getHiddenRowIndexes()) { +// System.out.print(i + " "); +// } +// System.out.println(); +// } +// }); + } + + + 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) { + if (event instanceof ShowRowPositionsEvent) { + ShowRowPositionsEvent e = (ShowRowPositionsEvent)event; + for (Range r : e.getRowPositionRanges()) { + int expanded = viewportLayer.getRowIndexByPosition(r.start-2)+1; + if (DEBUG)System.out.println("IsExpandedProcessor expand " + expanded); + if (expanded < 0 || expanded >= list.size()) { + return; + } + nodeStatusChanged(list.get(expanded).getContext(), true); + } + } else if (event instanceof HideRowPositionsEvent) { + HideRowPositionsEvent e = (HideRowPositionsEvent)event; + for (Range r : e.getRowPositionRanges()) { + int collapsed = viewportLayer.getRowIndexByPosition(r.start-2); + if (DEBUG)System.out.println("IsExpandedProcessor collapse " + collapsed); + if (collapsed < 0 || collapsed >= list.size()) { + 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-1); + } +}