-package org.simantics.browsing.ui.nattable;\r
-\r
-import java.util.ArrayList;\r
-import java.util.Arrays;\r
-import java.util.Collection;\r
-import java.util.Collections;\r
-import java.util.Deque;\r
-import java.util.HashMap;\r
-import java.util.HashSet;\r
-import java.util.LinkedList;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Set;\r
-import java.util.WeakHashMap;\r
-import java.util.concurrent.CopyOnWriteArrayList;\r
-import java.util.concurrent.ExecutorService;\r
-import java.util.concurrent.ScheduledExecutorService;\r
-import java.util.concurrent.Semaphore;\r
-import java.util.concurrent.TimeUnit;\r
-import java.util.concurrent.atomic.AtomicBoolean;\r
-import java.util.concurrent.atomic.AtomicReference;\r
-import java.util.function.Consumer;\r
-\r
-import org.eclipse.core.runtime.Assert;\r
-import org.eclipse.core.runtime.IProgressMonitor;\r
-import org.eclipse.core.runtime.IStatus;\r
-import org.eclipse.core.runtime.MultiStatus;\r
-import org.eclipse.core.runtime.Platform;\r
-import org.eclipse.core.runtime.Status;\r
-import org.eclipse.core.runtime.jobs.Job;\r
-import org.eclipse.jface.resource.ColorDescriptor;\r
-import org.eclipse.jface.resource.DeviceResourceException;\r
-import org.eclipse.jface.resource.DeviceResourceManager;\r
-import org.eclipse.jface.resource.FontDescriptor;\r
-import org.eclipse.jface.resource.ImageDescriptor;\r
-import org.eclipse.jface.resource.JFaceResources;\r
-import org.eclipse.jface.resource.LocalResourceManager;\r
-import org.eclipse.jface.viewers.ColumnWeightData;\r
-import org.eclipse.jface.viewers.ICellEditorValidator;\r
-import org.eclipse.jface.viewers.IPostSelectionProvider;\r
-import org.eclipse.jface.viewers.ISelection;\r
-import org.eclipse.jface.viewers.ISelectionChangedListener;\r
-import org.eclipse.jface.viewers.ISelectionProvider;\r
-import org.eclipse.jface.viewers.SelectionChangedEvent;\r
-import org.eclipse.jface.viewers.StructuredSelection;\r
-import org.eclipse.jface.window.Window;\r
-import org.eclipse.nebula.widgets.nattable.NatTable;\r
-import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;\r
-import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;\r
-import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;\r
-import org.eclipse.nebula.widgets.nattable.config.IEditableRule;\r
-import org.eclipse.nebula.widgets.nattable.coordinate.Range;\r
-import org.eclipse.nebula.widgets.nattable.data.IDataProvider;\r
-import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;\r
-import org.eclipse.nebula.widgets.nattable.data.convert.DefaultDisplayConverter;\r
-import org.eclipse.nebula.widgets.nattable.data.validate.IDataValidator;\r
-import org.eclipse.nebula.widgets.nattable.data.validate.ValidationFailedException;\r
-import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;\r
-import org.eclipse.nebula.widgets.nattable.edit.EditConfigHelper;\r
-import org.eclipse.nebula.widgets.nattable.edit.ICellEditHandler;\r
-import org.eclipse.nebula.widgets.nattable.edit.config.DefaultEditConfiguration;\r
-import org.eclipse.nebula.widgets.nattable.edit.config.DialogErrorHandling;\r
-import org.eclipse.nebula.widgets.nattable.edit.editor.AbstractCellEditor;\r
-import org.eclipse.nebula.widgets.nattable.edit.editor.ComboBoxCellEditor;\r
-import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor;\r
-import org.eclipse.nebula.widgets.nattable.edit.editor.IEditErrorHandler;\r
-import org.eclipse.nebula.widgets.nattable.edit.editor.TextCellEditor;\r
-import org.eclipse.nebula.widgets.nattable.edit.gui.AbstractDialogCellEditor;\r
-import org.eclipse.nebula.widgets.nattable.grid.GridRegion;\r
-import org.eclipse.nebula.widgets.nattable.grid.cell.AlternatingRowConfigLabelAccumulator;\r
-import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;\r
-import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;\r
-import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;\r
-import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;\r
-import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultColumnHeaderDataLayer;\r
-import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;\r
-import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;\r
-import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;\r
-import org.eclipse.nebula.widgets.nattable.hideshow.ColumnHideShowLayer;\r
-import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent;\r
-import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent;\r
-import org.eclipse.nebula.widgets.nattable.layer.DataLayer;\r
-import org.eclipse.nebula.widgets.nattable.layer.ILayerListener;\r
-import org.eclipse.nebula.widgets.nattable.layer.LabelStack;\r
-import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator;\r
-import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;\r
-import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;\r
-import org.eclipse.nebula.widgets.nattable.painter.NatTableBorderOverlayPainter;\r
-import org.eclipse.nebula.widgets.nattable.reorder.ColumnReorderLayer;\r
-import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;\r
-import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum;\r
-import org.eclipse.nebula.widgets.nattable.selection.command.SelectCellCommand;\r
-import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration;\r
-import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;\r
-import org.eclipse.nebula.widgets.nattable.style.DisplayMode;\r
-import org.eclipse.nebula.widgets.nattable.style.Style;\r
-import org.eclipse.nebula.widgets.nattable.ui.menu.AbstractHeaderMenuConfiguration;\r
-import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder;\r
-import org.eclipse.nebula.widgets.nattable.util.GUIHelper;\r
-import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;\r
-import org.eclipse.nebula.widgets.nattable.widget.EditModeEnum;\r
-import org.eclipse.swt.SWT;\r
-import org.eclipse.swt.events.DisposeEvent;\r
-import org.eclipse.swt.events.DisposeListener;\r
-import org.eclipse.swt.events.FocusEvent;\r
-import org.eclipse.swt.events.FocusListener;\r
-import org.eclipse.swt.events.KeyEvent;\r
-import org.eclipse.swt.events.KeyListener;\r
-import org.eclipse.swt.events.MouseEvent;\r
-import org.eclipse.swt.events.MouseListener;\r
-import org.eclipse.swt.events.SelectionListener;\r
-import org.eclipse.swt.graphics.Color;\r
-import org.eclipse.swt.graphics.Point;\r
-import org.eclipse.swt.graphics.RGB;\r
-import org.eclipse.swt.graphics.Rectangle;\r
-import org.eclipse.swt.widgets.Composite;\r
-import org.eclipse.swt.widgets.Control;\r
-import org.eclipse.swt.widgets.Display;\r
-import org.eclipse.swt.widgets.Event;\r
-import org.eclipse.swt.widgets.Listener;\r
-import org.eclipse.swt.widgets.ScrollBar;\r
-import org.eclipse.ui.PlatformUI;\r
-import org.eclipse.ui.contexts.IContextActivation;\r
-import org.eclipse.ui.contexts.IContextService;\r
-import org.eclipse.ui.services.IServiceLocator;\r
-import org.eclipse.ui.swt.IFocusService;\r
-import org.simantics.browsing.ui.BuiltinKeys;\r
-import org.simantics.browsing.ui.Column;\r
-import org.simantics.browsing.ui.Column.Align;\r
-import org.simantics.browsing.ui.DataSource;\r
-import org.simantics.browsing.ui.ExplorerState;\r
-import org.simantics.browsing.ui.GraphExplorer;\r
-import org.simantics.browsing.ui.NodeContext;\r
-import org.simantics.browsing.ui.NodeContext.CacheKey;\r
-import org.simantics.browsing.ui.NodeContext.PrimitiveQueryKey;\r
-import org.simantics.browsing.ui.NodeContext.QueryKey;\r
-import org.simantics.browsing.ui.NodeQueryManager;\r
-import org.simantics.browsing.ui.NodeQueryProcessor;\r
-import org.simantics.browsing.ui.PrimitiveQueryProcessor;\r
-import org.simantics.browsing.ui.PrimitiveQueryUpdater;\r
-import org.simantics.browsing.ui.SelectionDataResolver;\r
-import org.simantics.browsing.ui.SelectionFilter;\r
-import org.simantics.browsing.ui.StatePersistor;\r
-import org.simantics.browsing.ui.common.ColumnKeys;\r
-import org.simantics.browsing.ui.common.ErrorLogger;\r
-import org.simantics.browsing.ui.common.NodeContextBuilder;\r
-import org.simantics.browsing.ui.common.NodeContextUtil;\r
-import org.simantics.browsing.ui.common.internal.GENodeQueryManager;\r
-import org.simantics.browsing.ui.common.internal.IGECache;\r
-import org.simantics.browsing.ui.common.internal.IGraphExplorerContext;\r
-import org.simantics.browsing.ui.common.internal.UIElementReference;\r
-import org.simantics.browsing.ui.common.processors.AbstractPrimitiveQueryProcessor;\r
-import org.simantics.browsing.ui.common.processors.DefaultCheckedStateProcessor;\r
-import org.simantics.browsing.ui.common.processors.DefaultComparableChildrenProcessor;\r
-import org.simantics.browsing.ui.common.processors.DefaultFinalChildrenProcessor;\r
-import org.simantics.browsing.ui.common.processors.DefaultImageDecoratorProcessor;\r
-import org.simantics.browsing.ui.common.processors.DefaultImagerFactoriesProcessor;\r
-import org.simantics.browsing.ui.common.processors.DefaultImagerProcessor;\r
-import org.simantics.browsing.ui.common.processors.DefaultLabelDecoratorProcessor;\r
-import org.simantics.browsing.ui.common.processors.DefaultLabelerFactoriesProcessor;\r
-import org.simantics.browsing.ui.common.processors.DefaultLabelerProcessor;\r
-import org.simantics.browsing.ui.common.processors.DefaultPrunedChildrenProcessor;\r
-import org.simantics.browsing.ui.common.processors.DefaultSelectedImageDecoratorFactoriesProcessor;\r
-import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelDecoratorFactoriesProcessor;\r
-import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelerProcessor;\r
-import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointFactoryProcessor;\r
-import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointProcessor;\r
-import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionProcessor;\r
-import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionsProcessor;\r
-import org.simantics.browsing.ui.common.processors.DefaultViewpointProcessor;\r
-import org.simantics.browsing.ui.common.processors.IsExpandedProcessor;\r
-import org.simantics.browsing.ui.common.processors.NoSelectionRequestProcessor;\r
-import org.simantics.browsing.ui.common.processors.ProcessorLifecycle;\r
-import org.simantics.browsing.ui.content.Labeler;\r
-import org.simantics.browsing.ui.content.Labeler.CustomModifier;\r
-import org.simantics.browsing.ui.content.Labeler.DialogModifier;\r
-import org.simantics.browsing.ui.content.Labeler.EnumerationModifier;\r
-import org.simantics.browsing.ui.content.Labeler.Modifier;\r
-import org.simantics.browsing.ui.nattable.override.DefaultTreeLayerConfiguration2;\r
-import org.simantics.browsing.ui.swt.Activator;\r
-import org.simantics.browsing.ui.swt.AdaptableHintContext;\r
-import org.simantics.browsing.ui.swt.DefaultImageDecoratorsProcessor;\r
-import org.simantics.browsing.ui.swt.DefaultIsExpandedProcessor;\r
-import org.simantics.browsing.ui.swt.DefaultLabelDecoratorsProcessor;\r
-import org.simantics.browsing.ui.swt.DefaultSelectedImagerProcessor;\r
-import org.simantics.browsing.ui.swt.DefaultShowMaxChildrenProcessor;\r
-import org.simantics.browsing.ui.swt.GraphExplorerImplBase;\r
-import org.simantics.browsing.ui.swt.ImageLoaderJob;\r
-import org.simantics.browsing.ui.swt.ViewerCellReference;\r
-import org.simantics.browsing.ui.swt.ViewerRowReference;\r
-import org.simantics.browsing.ui.swt.internal.Threads;\r
-import org.simantics.db.layer0.SelectionHints;\r
-import org.simantics.utils.datastructures.BinaryFunction;\r
-import org.simantics.utils.datastructures.MapList;\r
-import org.simantics.utils.datastructures.disposable.AbstractDisposable;\r
-import org.simantics.utils.datastructures.hints.IHintContext;\r
-import org.simantics.utils.threads.IThreadWorkQueue;\r
-import org.simantics.utils.threads.SWTThread;\r
-import org.simantics.utils.threads.ThreadUtils;\r
-import org.simantics.utils.ui.AdaptionUtils;\r
-import org.simantics.utils.ui.ISelectionUtils;\r
-import org.simantics.utils.ui.jface.BasePostSelectionProvider;\r
-\r
-import gnu.trove.map.hash.THashMap;\r
-import gnu.trove.map.hash.TObjectIntHashMap;\r
-\r
-/**\r
- * NatTable based GraphExplorer\r
- * \r
- * This GraphExplorer is not fully compatible with the other implementations, since it is not based on SWT.Tree. \r
- * \r
- * 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.\r
- * \r
- * \r
- * TODO: ability to hide headers\r
- * TODO: code cleanup (copied from GraphExplorerImpl2) \r
- * \r
- * @author Marko Luukkainen <marko.luukkainen@vtt.fi>\r
- *\r
- */\r
-public class NatTableGraphExplorer extends GraphExplorerImplBase implements GraphExplorer{\r
- public static final int DEFAULT_MAX_CHILDREN = 10000;\r
- private static final boolean DEBUG_SELECTION_LISTENERS = false;\r
- private static final boolean DEBUG = false;\r
- \r
- private Composite composite;\r
- private NatTable natTable;\r
- \r
- private GETreeLayer treeLayer;\r
- private DataLayer dataLayer;\r
- private ViewportLayer viewportLayer;\r
- private SelectionLayer selectionLayer;\r
- private GEColumnHeaderDataProvider columnHeaderDataProvider;\r
- private GEColumnAccessor columnAccessor;\r
- private DefaultRowHeaderDataLayer rowHeaderDataLayer;\r
- private DataLayer columnHeaderDataLayer;\r
- private DataLayer cornerDataLayer;\r
- \r
- private List<TreeNode> list = new ArrayList<>();\r
- \r
- private NatTableSelectionAdaptor selectionAdaptor;\r
- private NatTableColumnLayout layout;\r
- \r
- LocalResourceManager localResourceManager;\r
- DeviceResourceManager resourceManager;\r
- \r
- \r
- private IThreadWorkQueue thread;\r
- \r
- @SuppressWarnings({ "rawtypes" })\r
- final HashMap<CacheKey<?>, NodeQueryProcessor> processors = new HashMap<CacheKey<?>, NodeQueryProcessor>();\r
- @SuppressWarnings({ "rawtypes" })\r
- final HashMap<Object, PrimitiveQueryProcessor> primitiveProcessors = new HashMap<Object, PrimitiveQueryProcessor>();\r
- @SuppressWarnings({ "rawtypes" })\r
- final HashMap<Class, DataSource> dataSources = new HashMap<Class, DataSource>();\r
-\r
- FontDescriptor originalFont;\r
- protected ColorDescriptor originalForeground;\r
- protected ColorDescriptor originalBackground;\r
- private Color invalidModificationColor;\r
- \r
- private Column[] columns;\r
- private Map<String,Integer> columnKeyToIndex;\r
- private boolean columnsAreVisible = true;\r
- \r
- private NodeContext rootContext;\r
- private TreeNode rootNode;\r
- private StatePersistor persistor = null;\r
-\r
- private boolean editable = true;\r
- \r
- private boolean disposed = false;\r
- \r
- private final CopyOnWriteArrayList<FocusListener> focusListeners = new CopyOnWriteArrayList<FocusListener>();\r
- private final CopyOnWriteArrayList<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();\r
- private final CopyOnWriteArrayList<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>();\r
- \r
- private int autoExpandLevel = 0;\r
- private IServiceLocator serviceLocator;\r
- private IContextService contextService = null;\r
- private IFocusService focusService = null;\r
- private IContextActivation editingContext = null;\r
- \r
- GeViewerContext explorerContext = new GeViewerContext(this);\r
- \r
- private GraphExplorerPostSelectionProvider postSelectionProvider = new GraphExplorerPostSelectionProvider(this);\r
- private BasePostSelectionProvider selectionProvider = new BasePostSelectionProvider();\r
- private SelectionDataResolver selectionDataResolver;\r
- private SelectionFilter selectionFilter;\r
- \r
- private MapList<NodeContext, TreeNode> contextToNodeMap;\r
- \r
- private ModificationContext modificationContext = null;\r
- \r
- private boolean filterSelectionEdit = true;\r
- \r
- private boolean expand;\r
- private boolean verticalBarVisible = false;\r
- \r
- private BinaryFunction<Object[], GraphExplorer, Object[]> selectionTransformation = new BinaryFunction<Object[], GraphExplorer, Object[]>() {\r
-\r
- @Override\r
- public Object[] call(GraphExplorer explorer, Object[] objects) {\r
- Object[] result = new Object[objects.length];\r
- for (int i = 0; i < objects.length; i++) {\r
- IHintContext context = new AdaptableHintContext(SelectionHints.KEY_MAIN);\r
- context.setHint(SelectionHints.KEY_MAIN, objects[i]);\r
- result[i] = context;\r
- }\r
- return result;\r
- }\r
-\r
- };\r
- \r
- static class TransientStateImpl implements TransientExplorerState {\r
-\r
- private Integer activeColumn = null;\r
- \r
- @Override\r
- public synchronized Integer getActiveColumn() {\r
- return activeColumn;\r
- }\r
- \r
- public synchronized void setActiveColumn(Integer column) {\r
- activeColumn = column;\r
- }\r
- \r
- }\r
- \r
- private TransientStateImpl transientState = new TransientStateImpl();\r
- \r
- public NatTableGraphExplorer(Composite parent) {\r
- this(parent, SWT.BORDER | SWT.MULTI );\r
- }\r
- \r
- public NatTableGraphExplorer(Composite parent, int style) {\r
- this.composite = parent;\r
- \r
- \r
- this.localResourceManager = new LocalResourceManager(JFaceResources.getResources());\r
- this.resourceManager = new DeviceResourceManager(parent.getDisplay());\r
-\r
- this.imageLoaderJob = new ImageLoaderJob(this);\r
- this.imageLoaderJob.setPriority(Job.DECORATE);\r
- contextToNodeMap = new MapList<NodeContext, TreeNode>();\r
- \r
- invalidModificationColor = (Color) localResourceManager.get(ColorDescriptor.createFrom(new RGB(255, 128, 128)));\r
-\r
- this.thread = SWTThread.getThreadAccess(parent);\r
-\r
- for (int i = 0; i < 10; i++)\r
- explorerContext.activity.push(0);\r
- \r
- originalFont = JFaceResources.getDefaultFontDescriptor();\r
-\r
- columns = new Column[0];\r
- createNatTable(style);\r
- layout = new NatTableColumnLayout(natTable, columnHeaderDataProvider, rowHeaderDataLayer);\r
- this.composite.setLayout(layout);\r
- \r
- setBasicListeners();\r
- setDefaultProcessors();\r
- \r
- natTable.addDisposeListener(new DisposeListener() {\r
- \r
- @Override\r
- public void widgetDisposed(DisposeEvent e) {\r
- doDispose();\r
- \r
- }\r
- });\r
- \r
- Listener listener = new Listener() {\r
- \r
- @Override\r
- public void handleEvent(Event event) {\r
- \r
- switch (event.type) {\r
- case SWT.Activate:\r
- case SWT.Show:\r
- case SWT.Paint:\r
- {\r
- visible = true;\r
- activate();\r
- break;\r
- }\r
- case SWT.Deactivate:\r
- case SWT.Hide:\r
- visible = false;\r
- }\r
- }\r
- };\r
- \r
- natTable.addListener(SWT.Activate, listener);\r
- natTable.addListener(SWT.Deactivate, listener);\r
- natTable.addListener(SWT.Show, listener);\r
- natTable.addListener(SWT.Hide, listener);\r
- natTable.addListener(SWT.Paint,listener);\r
- \r
- setColumns( new Column[] { new Column(ColumnKeys.SINGLE) });\r
- \r
- }\r
- \r
- private long focusGainedAt = 0L;\r
- private boolean visible = false;\r
- private Collection<TreeNode> selectedNodes = new ArrayList<TreeNode>();\r
- \r
- protected void setBasicListeners() {\r
- \r
- natTable.addFocusListener(new FocusListener() {\r
- @Override\r
- public void focusGained(FocusEvent e) {\r
- focusGainedAt = ((long) e.time) & 0xFFFFFFFFL;\r
- for (FocusListener listener : focusListeners)\r
- listener.focusGained(e);\r
- }\r
- @Override\r
- public void focusLost(FocusEvent e) {\r
- for (FocusListener listener : focusListeners)\r
- listener.focusLost(e);\r
- }\r
- });\r
- natTable.addMouseListener(new MouseListener() {\r
- @Override\r
- public void mouseDoubleClick(MouseEvent e) {\r
- for (MouseListener listener : mouseListeners) {\r
- listener.mouseDoubleClick(e);\r
- }\r
- }\r
- @Override\r
- public void mouseDown(MouseEvent e) {\r
- for (MouseListener listener : mouseListeners) {\r
- listener.mouseDown(e);\r
- }\r
- }\r
- @Override\r
- public void mouseUp(MouseEvent e) {\r
- for (MouseListener listener : mouseListeners) {\r
- listener.mouseUp(e);\r
- }\r
- }\r
- });\r
- natTable.addKeyListener(new KeyListener() {\r
- @Override\r
- public void keyPressed(KeyEvent e) {\r
- for (KeyListener listener : keyListeners) {\r
- listener.keyPressed(e);\r
- }\r
- }\r
- @Override\r
- public void keyReleased(KeyEvent e) {\r
- for (KeyListener listener : keyListeners) {\r
- listener.keyReleased(e);\r
- }\r
- }\r
- });\r
- \r
- selectionAdaptor.addSelectionChangedListener(new ISelectionChangedListener() {\r
- \r
- @Override\r
- public void selectionChanged(SelectionChangedEvent event) {\r
- //System.out.println("GraphExplorerImpl2.fireSelection");\r
- selectedNodes = AdaptionUtils.adaptToCollection(event.getSelection(), TreeNode.class);\r
- Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);\r
- selectionProvider.setAndFireSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));\r
- }\r
- });\r
- \r
- selectionAdaptor.addPostSelectionChangedListener(new ISelectionChangedListener() {\r
- \r
- @Override\r
- public void selectionChanged(SelectionChangedEvent event) {\r
- //System.out.println("GraphExplorerImpl2.firePostSelection");\r
- Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);\r
- selectionProvider.firePostSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));\r
- \r
- }\r
- });\r
-\r
- }\r
- \r
- private NodeContext pendingRoot;\r
- \r
- private void activate() {\r
- if (pendingRoot != null && !expand) {\r
- doSetRoot(pendingRoot);\r
- pendingRoot = null;\r
- }\r
- }\r
- \r
- /**\r
- * Invoke only from SWT thread to reset the root of the graph explorer tree.\r
- * \r
- * @param root\r
- */\r
- private void doSetRoot(NodeContext root) {\r
- Display display = composite.getDisplay();\r
- if (display.getThread() != Thread.currentThread()) {\r
- throw new RuntimeException("Invoke from SWT thread only");\r
- }\r
-// System.out.println("doSetRoot " + root);\r
- if (isDisposed())\r
- return;\r
- if (natTable.isDisposed())\r
- return;\r
- if (root.getConstant(BuiltinKeys.INPUT) == null) {\r
- ErrorLogger.defaultLogError("root node context does not contain BuiltinKeys.INPUT key. Node = " + root, new Exception("trace"));\r
- return;\r
- }\r
- \r
- \r
-\r
- // Empty caches, release queries.\r
- if (rootNode != null) {\r
- rootNode.dispose();\r
- } \r
- GeViewerContext oldContext = explorerContext;\r
- GeViewerContext newContext = new GeViewerContext(this);\r
- this.explorerContext = newContext;\r
- oldContext.safeDispose();\r
-\r
- // Need to empty these or otherwise they won't be emptied until the\r
- // explorer is disposed which would mean that many unwanted references\r
- // will be held by this map.\r
- clearPrimitiveProcessors();\r
-\r
- this.rootContext = root.getConstant(BuiltinKeys.IS_ROOT) != null ? root\r
- : NodeContextUtil.withConstant(root, BuiltinKeys.IS_ROOT, Boolean.TRUE);\r
-\r
- explorerContext.getCache().incRef(this.rootContext);\r
-\r
- initializeState();\r
- \r
- \r
- select(rootContext);\r
- //refreshColumnSizes();\r
- rootNode = new TreeNode(rootContext, explorerContext);\r
- if (DEBUG) System.out.println("setRoot " + rootNode);\r
- \r
- // Delay content reading.\r
- \r
- // This is required for cases when GEImpl2 is attached to selection view. Reading content\r
- // instantly could stagnate SWT thread under rapid changes in selection. By delaying the \r
- // content reading we give the system a change to dispose the GEImpl2 before the content is read.\r
- display.asyncExec(new Runnable() {\r
- \r
- @Override\r
- public void run() {\r
- if (rootNode != null) {\r
- rootNode.updateChildren();\r
- rootNode.setExpanded(true);\r
- listReIndex();\r
- }\r
- }\r
- });\r
- \r
- }\r
- \r
- private synchronized void listReIndex() {\r
- for (TreeNode n : list) {\r
- n.setListIndex(-2);\r
- }\r
- list.clear();\r
- for (TreeNode c : rootNode.getChildren())\r
- _insertToList(c);\r
- natTable.refresh();\r
- }\r
- \r
- private void _insertToList(TreeNode n) {\r
- n.setListIndex(list.size());\r
- list.add(n);\r
- for (TreeNode c : n.getChildren()) {\r
- _insertToList(c);\r
- }\r
- }\r
- \r
- public List<TreeNode> getItems() {\r
- return Collections.unmodifiableList(list);\r
- }\r
- \r
- private void initializeState() {\r
- if (persistor == null)\r
- return;\r
-\r
- ExplorerState state = persistor.deserialize(\r
- Platform.getStateLocation(Activator.getDefault().getBundle()).toFile(),\r
- getRoot());\r
-\r
-\r
- Object processor = getPrimitiveProcessor(BuiltinKeys.IS_EXPANDED);\r
- if (processor instanceof DefaultIsExpandedProcessor) {\r
- DefaultIsExpandedProcessor isExpandedProcessor = (DefaultIsExpandedProcessor)processor;\r
- for(NodeContext expanded : state.expandedNodes) {\r
- isExpandedProcessor.setExpanded(expanded, true);\r
- }\r
- }\r
- }\r
-\r
- @Override\r
- public NodeContext getRoot() {\r
- return rootContext;\r
- }\r
- \r
- @Override\r
- public IThreadWorkQueue getThread() {\r
- return thread;\r
- }\r
-\r
- @Override\r
- public NodeContext getParentContext(NodeContext context) {\r
- if (disposed)\r
- throw new IllegalStateException("disposed");\r
- if (!thread.currentThreadAccess())\r
- throw new IllegalStateException("not in SWT display thread " + thread.getThread());\r
-\r
- List<TreeNode> nodes = contextToNodeMap.getValuesUnsafe(context);\r
- for (int i = 0; i < nodes.size(); i++) {\r
- if (nodes.get(i).getParent() != null)\r
- return nodes.get(i).getParent().getContext();\r
- }\r
- return null;\r
- \r
- }\r
- \r
- \r
- @SuppressWarnings("unchecked")\r
- @Override\r
- public <T> T getAdapter(Class<T> adapter) {\r
- if(ISelectionProvider.class == adapter) return (T) postSelectionProvider;\r
- else if(IPostSelectionProvider.class == adapter) return (T) postSelectionProvider;\r
- return null;\r
- }\r
-\r
- \r
- protected void setDefaultProcessors() {\r
- // Add a simple IMAGER query processor that always returns null.\r
- // With this processor no images will ever be shown.\r
- // setPrimitiveProcessor(new StaticImagerProcessor(null));\r
-\r
- setProcessor(new DefaultComparableChildrenProcessor());\r
- setProcessor(new DefaultLabelDecoratorsProcessor());\r
- setProcessor(new DefaultImageDecoratorsProcessor());\r
- setProcessor(new DefaultSelectedLabelerProcessor());\r
- setProcessor(new DefaultLabelerFactoriesProcessor());\r
- setProcessor(new DefaultSelectedImagerProcessor());\r
- setProcessor(new DefaultImagerFactoriesProcessor());\r
- setPrimitiveProcessor(new DefaultLabelerProcessor());\r
- setPrimitiveProcessor(new DefaultCheckedStateProcessor());\r
- setPrimitiveProcessor(new DefaultImagerProcessor());\r
- setPrimitiveProcessor(new DefaultLabelDecoratorProcessor());\r
- setPrimitiveProcessor(new DefaultImageDecoratorProcessor());\r
- setPrimitiveProcessor(new NoSelectionRequestProcessor());\r
-\r
- setProcessor(new DefaultFinalChildrenProcessor(this));\r
-\r
- setProcessor(new DefaultPrunedChildrenProcessor());\r
- setProcessor(new DefaultSelectedViewpointProcessor());\r
- setProcessor(new DefaultSelectedLabelDecoratorFactoriesProcessor());\r
- setProcessor(new DefaultSelectedImageDecoratorFactoriesProcessor());\r
- setProcessor(new DefaultViewpointContributionsProcessor());\r
-\r
- setPrimitiveProcessor(new DefaultViewpointProcessor());\r
- setPrimitiveProcessor(new DefaultViewpointContributionProcessor());\r
- setPrimitiveProcessor(new DefaultSelectedViewpointFactoryProcessor());\r
- setPrimitiveProcessor(new TreeNodeIsExpandedProcessor());\r
- setPrimitiveProcessor(new DefaultShowMaxChildrenProcessor());\r
- }\r
- \r
- @Override\r
- public Column[] getColumns() {\r
- return Arrays.copyOf(columns, columns.length);\r
- }\r
- \r
- @Override\r
- public void setColumnsVisible(boolean visible) {\r
- columnsAreVisible = visible;\r
- //FIXME if(natTable != null) this.columnHeaderDataLayer.setHeaderVisible(columnsAreVisible);\r
- }\r
-\r
- @Override\r
- public void setColumns(final Column[] columns) {\r
- setColumns(columns, null);\r
- }\r
-\r
- @Override\r
- public void setColumns(final Column[] columns, Consumer<Map<Column, Object>> callback) {\r
- assertNotDisposed();\r
- checkUniqueColumnKeys(columns);\r
-\r
- Display d = composite.getDisplay();\r
- if (d.getThread() == Thread.currentThread()) {\r
- doSetColumns(columns, callback);\r
- natTable.refresh(true);\r
- }else\r
- d.asyncExec(new Runnable() {\r
- @Override\r
- public void run() {\r
- if (natTable == null)\r
- return;\r
- if (natTable.isDisposed())\r
- return;\r
- doSetColumns(columns, callback);\r
- natTable.refresh();\r
- natTable.getParent().layout();\r
- }\r
- });\r
- }\r
- \r
- private void checkUniqueColumnKeys(Column[] cols) {\r
- Set<String> usedColumnKeys = new HashSet<String>();\r
- List<Column> duplicateColumns = new ArrayList<Column>();\r
- for (Column c : cols) {\r
- if (!usedColumnKeys.add(c.getKey()))\r
- duplicateColumns.add(c);\r
- }\r
- if (!duplicateColumns.isEmpty()) {\r
- throw new IllegalArgumentException("All columns do not have unique keys: " + cols + ", overlapping: " + duplicateColumns);\r
- }\r
- }\r
- \r
- private void doSetColumns(Column[] cols, Consumer<Map<Column, Object>> callback) {\r
-\r
- HashMap<String, Integer> keyToIndex = new HashMap<String, Integer>();\r
- for (int i = 0; i < cols.length; ++i) {\r
- keyToIndex.put(cols[i].getKey(), i);\r
- }\r
-\r
- this.columns = Arrays.copyOf(cols, cols.length);\r
- //this.columns[cols.length] = FILLER_COLUMN;\r
- this.columnKeyToIndex = keyToIndex;\r
- \r
- columnHeaderDataProvider.updateColumnSizes();\r
-\r
- Map<Column, Object> map = new HashMap<Column, Object>();\r
-\r
- // FIXME : temporary workaround for ModelBrowser.\r
-// natTable.setHeaderVisible(columns.length == 1 ? false : columnsAreVisible);\r
- \r
- int columnIndex = 0;\r
-\r
- for (Column column : columns) {\r
- int width = column.getWidth();\r
- if(column.hasGrab()) {\r
- if (width < 0)\r
- width = 1;\r
- layout.setColumnData(columnIndex, new ColumnWeightData(column.getWeight(), width));\r
-\r
- } else {\r
- if (width < 0)\r
- width = 50;\r
- layout.setColumnData(columnIndex, new ColumnWeightData(columns.length > 1 ? 0 : 1, width));\r
-\r
- }\r
- columnIndex++;\r
- }\r
- \r
- \r
-\r
- if(callback != null) callback.accept(map);\r
- }\r
- \r
- int toSWT(Align alignment) {\r
- switch (alignment) {\r
- case LEFT: return SWT.LEFT;\r
- case CENTER: return SWT.CENTER;\r
- case RIGHT: return SWT.RIGHT;\r
- default: throw new Error("unhandled alignment: " + alignment);\r
- }\r
- }\r
-\r
- @Override\r
- public <T> void setProcessor(NodeQueryProcessor<T> processor) {\r
- assertNotDisposed();\r
- if (processor == null)\r
- throw new IllegalArgumentException("null processor");\r
-\r
- processors.put(processor.getIdentifier(), processor);\r
- }\r
-\r
- @Override\r
- public <T> void setPrimitiveProcessor(PrimitiveQueryProcessor<T> processor) {\r
- assertNotDisposed();\r
- if (processor == null)\r
- throw new IllegalArgumentException("null processor");\r
-\r
- PrimitiveQueryProcessor<?> oldProcessor = primitiveProcessors.put(\r
- processor.getIdentifier(), processor);\r
-\r
- if (oldProcessor instanceof ProcessorLifecycle)\r
- ((ProcessorLifecycle) oldProcessor).detached(this);\r
- if (processor instanceof ProcessorLifecycle)\r
- ((ProcessorLifecycle) processor).attached(this);\r
- }\r
-\r
- @Override\r
- public <T> void setDataSource(DataSource<T> provider) {\r
- assertNotDisposed();\r
- if (provider == null)\r
- throw new IllegalArgumentException("null provider");\r
- dataSources.put(provider.getProvidedClass(), provider);\r
- }\r
-\r
- @SuppressWarnings("unchecked")\r
- @Override\r
- public <T> DataSource<T> removeDataSource(Class<T> forProvidedClass) {\r
- assertNotDisposed();\r
- if (forProvidedClass == null)\r
- throw new IllegalArgumentException("null class");\r
- return dataSources.remove(forProvidedClass);\r
- }\r
-\r
- @Override\r
- public void setPersistor(StatePersistor persistor) {\r
- this.persistor = persistor;\r
- }\r
-\r
- @Override\r
- public SelectionDataResolver getSelectionDataResolver() {\r
- return selectionDataResolver;\r
- }\r
-\r
- @Override\r
- public void setSelectionDataResolver(SelectionDataResolver r) {\r
- this.selectionDataResolver = r;\r
- }\r
-\r
- @Override\r
- public SelectionFilter getSelectionFilter() {\r
- return selectionFilter;\r
- }\r
-\r
- @Override\r
- public void setSelectionFilter(SelectionFilter f) {\r
- this.selectionFilter = f;\r
- // TODO: re-filter current selection?\r
- }\r
- \r
- protected ISelection constructSelection(NodeContext... contexts) {\r
- if (contexts == null)\r
- throw new IllegalArgumentException("null contexts");\r
- if (contexts.length == 0)\r
- return StructuredSelection.EMPTY;\r
- if (selectionFilter == null)\r
- return new StructuredSelection(transformSelection(contexts));\r
- return new StructuredSelection( transformSelection(filter(selectionFilter, contexts)) );\r
- }\r
- \r
- protected Object[] transformSelection(Object[] objects) {\r
- return selectionTransformation.call(this, objects);\r
- }\r
- \r
- protected static Object[] filter(SelectionFilter filter, NodeContext[] contexts) {\r
- int len = contexts.length;\r
- Object[] objects = new Object[len];\r
- for (int i = 0; i < len; ++i)\r
- objects[i] = filter.filter(contexts[i]);\r
- return objects;\r
- }\r
-\r
- @Override\r
- public void setSelectionTransformation(\r
- BinaryFunction<Object[], GraphExplorer, Object[]> f) {\r
- this.selectionTransformation = f;\r
- }\r
- \r
- public ISelection getWidgetSelection() {\r
- return selectionAdaptor.getSelection();\r
- }\r
-\r
- @Override\r
- public <T> void addListener(T listener) {\r
- if (listener instanceof FocusListener) {\r
- focusListeners.add((FocusListener) listener);\r
- } else if (listener instanceof MouseListener) {\r
- mouseListeners.add((MouseListener) listener);\r
- } else if (listener instanceof KeyListener) {\r
- keyListeners.add((KeyListener) listener);\r
- }\r
- }\r
-\r
- @Override\r
- public <T> void removeListener(T listener) {\r
- if (listener instanceof FocusListener) {\r
- focusListeners.remove(listener);\r
- } else if (listener instanceof MouseListener) {\r
- mouseListeners.remove(listener);\r
- } else if (listener instanceof KeyListener) {\r
- keyListeners.remove(listener);\r
- }\r
- }\r
-\r
- public void addSelectionListener(SelectionListener listener) {\r
- selectionAdaptor.addSelectionListener(listener);\r
- }\r
-\r
- public void removeSelectionListener(SelectionListener listener) {\r
- selectionAdaptor.removeSelectionListener(listener);\r
- }\r
-\r
- private Set<String> uiContexts;\r
- \r
- @Override\r
- public void setUIContexts(Set<String> contexts) {\r
- this.uiContexts = contexts;\r
- }\r
- \r
- @Override\r
- public void setRoot(final Object root) {\r
- if(uiContexts != null && uiContexts.size() == 1)\r
- setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root, BuiltinKeys.UI_CONTEXT, uiContexts.iterator().next()));\r
- else\r
- setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root));\r
- }\r
-\r
- @Override\r
- public void setRootContext(final NodeContext context) {\r
- setRootContext0(context);\r
- }\r
- \r
- private void setRoot(NodeContext context) {\r
- if (!visible) {\r
- pendingRoot = context;\r
- Display.getDefault().asyncExec(new Runnable() {\r
- @Override\r
- public void run() {\r
- if (natTable!= null && !natTable.isDisposed())\r
- natTable.redraw();\r
- }\r
- });\r
- return;\r
- }\r
- doSetRoot(context);\r
- }\r
-\r
- private void setRootContext0(final NodeContext context) {\r
- Assert.isNotNull(context, "root must not be null");\r
- if (isDisposed() || natTable.isDisposed())\r
- return;\r
- Display display = natTable.getDisplay();\r
- if (display.getThread() == Thread.currentThread()) {\r
- setRoot(context);\r
- } else {\r
- display.asyncExec(new Runnable() {\r
- @Override\r
- public void run() {\r
- setRoot(context);\r
- }\r
- });\r
- }\r
- }\r
- \r
- @Override\r
- public void setFocus() {\r
- natTable.setFocus();\r
- }\r
- \r
- @SuppressWarnings("unchecked")\r
- @Override\r
- public <T> T getControl() {\r
- return (T)natTable;\r
- }\r
- \r
- \r
- @Override\r
- public boolean isDisposed() {\r
- return disposed;\r
- }\r
-\r
- protected void assertNotDisposed() {\r
- if (isDisposed())\r
- throw new IllegalStateException("disposed");\r
- }\r
- \r
- @Override\r
- public boolean isEditable() {\r
- return editable;\r
- }\r
-\r
- @Override\r
- public void setEditable(boolean editable) {\r
- if (!thread.currentThreadAccess())\r
- throw new IllegalStateException("not in SWT display thread " + thread.getThread());\r
-\r
- this.editable = editable;\r
- Display display = natTable.getDisplay();\r
- natTable.setBackground(editable ? null : display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));\r
- }\r
- \r
- private void doDispose() {\r
- if (disposed)\r
- return;\r
- disposed = true;\r
- // TODO: Since GENodeQueryManager is cached in QueryChache and it refers to this class\r
- // we have to remove all references here to reduce memory consumption.\r
- // \r
- // Proper fix would be to remove references between QueryCache and GENodeQueryManagers.\r
- if (rootNode != null) {\r
- rootNode.dispose();\r
- rootNode = null; \r
- } \r
- explorerContext.dispose();\r
- explorerContext = null;\r
- processors.clear();\r
- detachPrimitiveProcessors();\r
- primitiveProcessors.clear();\r
- dataSources.clear(); \r
- pendingItems.clear();\r
- rootContext = null;\r
- mouseListeners.clear();\r
- selectionProvider.clearListeners();\r
- selectionProvider = null;\r
- selectionDataResolver = null;\r
- selectedNodes.clear();\r
- selectedNodes = null;\r
- selectionTransformation = null;\r
- originalFont = null;\r
- localResourceManager.dispose();\r
- localResourceManager = null;\r
- // Must shutdown image loader job before disposing its ResourceManager\r
- imageLoaderJob.dispose();\r
- imageLoaderJob.cancel();\r
- try {\r
- imageLoaderJob.join();\r
- imageLoaderJob = null;\r
- } catch (InterruptedException e) {\r
- ErrorLogger.defaultLogError(e);\r
- }\r
- resourceManager.dispose();\r
- resourceManager = null;\r
- \r
- contextToNodeMap.clear(); // should be empty at this point.\r
- contextToNodeMap = null;\r
- if (postSelectionProvider != null) {\r
- postSelectionProvider.dispose();\r
- postSelectionProvider = null;\r
- }\r
- imageTasks = null;\r
- modificationContext = null;\r
- focusService = null;\r
- contextService = null;\r
- serviceLocator = null;\r
- columns = null;\r
- columnKeyToIndex.clear();\r
- columnKeyToIndex = null;\r
-// if (natTable != null) {\r
-// natTable.dispose();\r
-// natTable = null;\r
-// }\r
- treeLayer = null;\r
- dataLayer = null;\r
- viewportLayer = null;\r
- selectionLayer = null;\r
- columnHeaderDataProvider = null;\r
- columnAccessor = null;\r
- rowHeaderDataLayer = null;\r
- columnHeaderDataLayer = null;\r
- cornerDataLayer = null;\r
-\r
- }\r
- \r
- @Override\r
- public boolean select(NodeContext context) {\r
-\r
- assertNotDisposed();\r
-\r
- if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) {\r
- StructuredSelection s = new StructuredSelection();\r
- selectionAdaptor.setSelection(s);\r
- selectionProvider.setAndFireNonEqualSelection(s);\r
- return true;\r
- }\r
-\r
- selectionAdaptor.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0)));\r
- \r
- return false;\r
- \r
- }\r
- \r
- public boolean select(TreeNode node) {\r
- assertNotDisposed();\r
-\r
- if (!list.contains(node)) {\r
- StructuredSelection s = new StructuredSelection();\r
- selectionAdaptor.setSelection(s);\r
- selectionProvider.setAndFireNonEqualSelection(s);\r
- return true;\r
- }\r
- selectionAdaptor.setSelection(new StructuredSelection(node));\r
- return false;\r
- }\r
- \r
- public void show(TreeNode node) {\r
- int index = node.getListIndex();\r
- \r
- int position = treeLayer.getRowPositionByIndex(index);\r
- if (position < 0) {\r
- treeLayer.expandToTreeRow(index);\r
- position = treeLayer.getRowPositionByIndex(index);\r
- }\r
- viewportLayer.moveRowPositionIntoViewport(position);\r
- }\r
- \r
- @Override\r
- public boolean selectPath(Collection<NodeContext> contexts) {\r
- \r
- if(contexts == null) throw new IllegalArgumentException("Null list is not allowed");\r
- if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed");\r
- \r
- return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0);\r
- \r
- }\r
- \r
- private boolean selectPathInternal(NodeContext[] contexts, int position) {\r
-\r
- NodeContext head = contexts[position];\r
-\r
- if(position == contexts.length-1) {\r
- return select(head);\r
- \r
- }\r
-\r
- setExpanded(head, true);\r
- if(!waitVisible(contexts[position+1])) return false;\r
- \r
- return selectPathInternal(contexts, position+1);\r
- \r
- }\r
- \r
- private boolean waitVisible(NodeContext context) {\r
- long start = System.nanoTime();\r
- while(!isVisible(context)) {\r
- Display.getCurrent().readAndDispatch();\r
- long duration = System.nanoTime() - start;\r
- if(duration > 10e9) return false;\r
- }\r
- return true;\r
- }\r
- \r
- @Override\r
- public boolean isVisible(NodeContext context) {\r
- if (contextToNodeMap.getValuesUnsafe(context).size() == 0)\r
- return false;\r
- \r
- return true; //FIXME\r
-// Object elements[] = viewer.getVisibleExpandedElements();\r
-// return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0));\r
- \r
- \r
- }\r
- \r
- @Override\r
- public TransientExplorerState getTransientState() {\r
- if (!thread.currentThreadAccess())\r
- throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread());\r
- return transientState;\r
- }\r
- \r
- @Override\r
- public <T> T query(NodeContext context, CacheKey<T> key) {\r
- return this.explorerContext.cache.get(context, key);\r
- }\r
- \r
- /**\r
- * For setting a more local service locator for the explorer than the global\r
- * workbench service locator. Sometimes required to give this implementation\r
- * access to local workbench services like IFocusService.\r
- * \r
- * <p>\r
- * Must be invoked during right after construction.\r
- * \r
- * @param serviceLocator\r
- * a specific service locator or <code>null</code> to use the\r
- * workbench global service locator\r
- */\r
- public void setServiceLocator(IServiceLocator serviceLocator) {\r
- if (serviceLocator == null && PlatformUI.isWorkbenchRunning())\r
- serviceLocator = PlatformUI.getWorkbench();\r
- this.serviceLocator = serviceLocator;\r
- if (serviceLocator != null) {\r
- this.contextService = (IContextService) serviceLocator.getService(IContextService.class);\r
- this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class);\r
- }\r
- }\r
- \r
- private void detachPrimitiveProcessors() {\r
- for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {\r
- if (p instanceof ProcessorLifecycle) {\r
- ((ProcessorLifecycle) p).detached(this);\r
- }\r
- }\r
- }\r
-\r
- private void clearPrimitiveProcessors() {\r
- for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {\r
- if (p instanceof ProcessorLifecycle) {\r
- ((ProcessorLifecycle) p).clear();\r
- }\r
- }\r
- }\r
- \r
- @Override\r
- public void setExpanded(NodeContext context, boolean expanded) {\r
- for (TreeNode n : contextToNodeMap.getValues(context)) {\r
- if (expanded)\r
- treeLayer.expandTreeRow(n.getListIndex());\r
- else\r
- treeLayer.collapseTreeRow(n.getListIndex());\r
- }\r
- \r
- }\r
- \r
- @Override\r
- public void setAutoExpandLevel(int level) {\r
- this.autoExpandLevel = level;\r
- treeLayer.expandAllToLevel(level);\r
- }\r
- \r
- int maxChildren = DEFAULT_MAX_CHILDREN;\r
- \r
- @Override\r
- public int getMaxChildren() {\r
- return maxChildren;\r
- }\r
- \r
- @Override\r
- public void setMaxChildren(int maxChildren) {\r
- this.maxChildren = maxChildren;\r
- \r
- }\r
- \r
- @Override\r
- public int getMaxChildren(NodeQueryManager manager, NodeContext context) {\r
- Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN);\r
- //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result);\r
- if (result != null) {\r
- if (result < 0)\r
- throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result);\r
- return result;\r
- }\r
- return maxChildren;\r
- }\r
- \r
- @Override\r
- public <T> NodeQueryProcessor<T> getProcessor(QueryKey<T> key) {\r
- return explorerContext.getProcessor(key);\r
- }\r
-\r
- @Override\r
- public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(PrimitiveQueryKey<T> key) {\r
- return explorerContext.getPrimitiveProcessor(key);\r
- }\r
- \r
- private HashSet<UpdateItem> pendingItems = new HashSet<UpdateItem>();\r
- private boolean updating = false;\r
- private int updateCounter = 0;\r
- final ScheduledExecutorService uiUpdateScheduler = ThreadUtils.getNonBlockingWorkExecutor();\r
- \r
- private class UpdateItem {\r
- TreeNode element;\r
- int columnIndex;\r
- \r
- public UpdateItem(TreeNode element) {\r
- this(element,-1);\r
- }\r
- \r
- public UpdateItem(TreeNode element, int columnIndex) {\r
- this.element = element;\r
- this.columnIndex = columnIndex;\r
- if (element != null && element.isDisposed()) {\r
- throw new IllegalArgumentException("Node is disposed. " + element);\r
- }\r
- }\r
- \r
- public void update(NatTable natTable) {\r
- if (element != null) {\r
-\r
- if (element.isDisposed()) {\r
- return;\r
- }\r
- if (element.updateChildren()) {\r
- if (DEBUG) {\r
- System.out.println("Update Item updateChildren " + element.listIndex + " " + element);\r
- printDebug(NatTableGraphExplorer.this);\r
- }\r
- listReIndex();\r
- if (!element.isHidden()) { \r
- if (!element.isExpanded()) {\r
- if (element.listIndex >= 0)\r
- treeLayer.collapseTreeRow(element.listIndex);\r
- if (DEBUG) {\r
- System.out.println("Update Item collapse " + element.listIndex);\r
- printDebug(NatTableGraphExplorer.this);\r
- }\r
- } else {\r
- for (TreeNode c : element.getChildren())\r
- c.initData();\r
- }\r
- } else {\r
- TreeNode p = element.getCollapsedAncestor();\r
- if (p != null) {\r
- if (element.listIndex >= 0)\r
- treeLayer.collapseTreeRow(element.listIndex);\r
- if (p.listIndex >= 0) \r
- treeLayer.collapseTreeRow(p.listIndex);\r
- if (DEBUG) {\r
- System.out.println("Update Item ancetor collapse " + p.listIndex);\r
- printDebug(NatTableGraphExplorer.this);\r
- }\r
- }\r
- }\r
- } else {\r
-// if (columnIndex >= 0) {\r
-// viewer.update(element, new String[]{columns[columnIndex].getKey()});\r
-// } else {\r
-// viewer.refresh(element,true);\r
-// }\r
- natTable.redraw();\r
- }\r
- \r
- if (!element.autoExpanded && !element.isDisposed() && autoExpandLevel > 1 && !element.isExpanded() && element.getDepth() <= autoExpandLevel) {\r
- expand = true;\r
- element.autoExpanded = true;\r
- element.initData();\r
- if (DEBUG) System.out.println("Update Item expand " + element.listIndex);\r
- treeLayer.expandTreeRow(element.getListIndex());\r
- //viewer.setExpandedState(element, true);\r
- expand = false;\r
- }\r
- } else {\r
- if (rootNode.updateChildren()) {\r
- listReIndex();\r
- }\r
- }\r
- }\r
- \r
- @Override\r
- public boolean equals(Object obj) {\r
- if (obj == null)\r
- return false;\r
- if (obj.getClass() != getClass())\r
- return false;\r
- UpdateItem other = (UpdateItem)obj;\r
- if (columnIndex != other.columnIndex)\r
- return false;\r
- if (element != null)\r
- return element.equals(other.element);\r
- return other.element == null;\r
- }\r
- \r
- @Override\r
- public int hashCode() {\r
- if (element != null)\r
- return element.hashCode() + columnIndex;\r
- return 0;\r
- }\r
- }\r
- \r
- private void update(final TreeNode element, final int columnIndex) {\r
- if (natTable.isDisposed())\r
- return;\r
- if (element != null && element.isDisposed())\r
- return;\r
- if (DEBUG) System.out.println("update " + element + " " + columnIndex);\r
- synchronized (pendingItems) {\r
- pendingItems.add(new UpdateItem(element, columnIndex));\r
- if (updating) return;\r
- updateCounter++;\r
- scheduleUpdater();\r
- }\r
- }\r
-\r
- private void update(final TreeNode element) {\r
- \r
- if (natTable.isDisposed())\r
- return;\r
- if (element != null && element.isDisposed())\r
- return;\r
- if (DEBUG) System.out.println("update " + element);\r
- synchronized (pendingItems) {\r
- pendingItems.add(new UpdateItem(element));\r
- if (updating) return;\r
- updateCounter++;\r
- scheduleUpdater();\r
- }\r
- }\r
- \r
- boolean scheduleUpdater() {\r
-\r
- if (natTable.isDisposed())\r
- return false;\r
-\r
- if (!pendingItems.isEmpty()) {\r
- \r
- int activity = explorerContext.activityInt;\r
- long delay = 30;\r
- if (activity < 100) {\r
- //System.out.println("Scheduling update immediately.");\r
- } else if (activity < 1000) {\r
- //System.out.println("Scheduling update after 500ms.");\r
- delay = 500;\r
- } else {\r
- //System.out.println("Scheduling update after 3000ms.");\r
- delay = 3000;\r
- }\r
-\r
- updateCounter = 0;\r
- \r
- //System.out.println("Scheduling UI update after " + delay + " ms.");\r
- uiUpdateScheduler.schedule(new Runnable() {\r
- @Override\r
- public void run() {\r
- \r
- if (natTable == null || natTable.isDisposed())\r
- return;\r
- \r
- if (updateCounter > 0) {\r
- updateCounter = 0;\r
- uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS);\r
- } else {\r
- natTable.getDisplay().asyncExec(new UpdateRunner(NatTableGraphExplorer.this, NatTableGraphExplorer.this.explorerContext));\r
- }\r
- \r
- }\r
- }, delay, TimeUnit.MILLISECONDS);\r
-\r
- updating = true;\r
- return true;\r
- }\r
-\r
- return false;\r
- }\r
- \r
- @Override\r
- public String startEditing(NodeContext context, String columnKey) {\r
- assertNotDisposed();\r
- if (!thread.currentThreadAccess())\r
- throw new IllegalStateException("not in SWT display thread " + thread.getThread());\r
-\r
- if(columnKey.startsWith("#")) {\r
- columnKey = columnKey.substring(1);\r
- }\r
-\r
- Integer columnIndex = columnKeyToIndex.get(columnKey);\r
- if (columnIndex == null)\r
- return "Rename not supported for selection";\r
-// FIXME:\r
-// viewer.editElement(context, columnIndex);\r
-// if(viewer.isCellEditorActive()) return null;\r
- return "Rename not supported for selection";\r
- }\r
-\r
- @Override\r
- public String startEditing(String columnKey) {\r
- ISelection selection = postSelectionProvider.getSelection();\r
- if(selection == null) return "Rename not supported for selection";\r
- NodeContext context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class);\r
- if(context == null) return "Rename not supported for selection";\r
-\r
- return startEditing(context, columnKey);\r
-\r
- }\r
- \r
- public void setSelection(final ISelection selection, boolean forceControlUpdate) {\r
- assertNotDisposed();\r
- boolean equalsOld = selectionProvider.selectionEquals(selection);\r
- if (equalsOld && !forceControlUpdate) {\r
- // Just set the selection object instance, fire no events nor update\r
- // the viewer selection.\r
- selectionProvider.setSelection(selection);\r
- } else {\r
- Collection<NodeContext> coll = AdaptionUtils.adaptToCollection(selection, NodeContext.class);\r
- Collection<TreeNode> nodes = new ArrayList<TreeNode>();\r
- for (NodeContext c : coll) {\r
- List<TreeNode> match = contextToNodeMap.getValuesUnsafe(c);\r
- if(match.size() > 0)\r
- nodes.add(match.get(0));\r
- }\r
- final ISelection sel = new StructuredSelection(nodes.toArray());\r
- if (coll.size() == 0)\r
- return;\r
- // Schedule viewer and selection update if necessary.\r
- if (natTable.isDisposed())\r
- return;\r
- Display d = natTable.getDisplay();\r
- if (d.getThread() == Thread.currentThread()) {\r
- selectionAdaptor.setSelection(sel);\r
- } else {\r
- d.asyncExec(new Runnable() {\r
- @Override\r
- public void run() {\r
- if (natTable.isDisposed())\r
- return;\r
- selectionAdaptor.setSelection(sel);\r
- }\r
- });\r
- }\r
- }\r
- }\r
- \r
- @Override\r
- public void setModificationContext(ModificationContext modificationContext) {\r
- this.modificationContext = modificationContext;\r
- \r
- }\r
- \r
- final ExecutorService queryUpdateScheduler = Threads.getExecutor();\r
- \r
- \r
- private double getDisplayScale() {\r
- Point dpi = Display.getCurrent().getDPI();\r
- return (double)dpi.x/96.0;\r
- }\r
- \r
- private void createNatTable(int style) {\r
- GETreeData treeData = new GETreeData(list);\r
- GETreeRowModel<TreeNode> treeRowModel = new GETreeRowModel<TreeNode>(treeData);\r
- columnAccessor = new GEColumnAccessor(this);\r
- \r
- IDataProvider dataProvider = new ListDataProvider<TreeNode>(list, columnAccessor);\r
- \r
- int defaultFontSize = 12;\r
- int height = (int)Math.ceil(((double)(defaultFontSize))*getDisplayScale()) + DataLayer.DEFAULT_ROW_HEIGHT-defaultFontSize;\r
- dataLayer = new DataLayer(dataProvider, DataLayer.DEFAULT_COLUMN_WIDTH, height);\r
- \r
- // resizable rows are unnecessary in Sulca report.\r
- dataLayer.setRowsResizableByDefault(false);\r
- \r
- // Row header layer\r
- DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(dataProvider);\r
- rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider);\r
- \r
- // adjust row header column width so that row numbers fit into the column. \r
- //adjustRowHeaderWidth(list.size());\r
- \r
- // Column header layer\r
- columnHeaderDataProvider = new GEColumnHeaderDataProvider(this, dataLayer); \r
- columnHeaderDataLayer = new DefaultColumnHeaderDataLayer(columnHeaderDataProvider);\r
- columnHeaderDataLayer.setDefaultRowHeight(height);\r
- columnHeaderDataProvider.updateColumnSizes();\r
- \r
- //ISortModel sortModel = new EcoSortModel(this, generator,dataLayer);\r
- \r
- // Column re-order + hide\r
- ColumnReorderLayer columnReorderLayer = new ColumnReorderLayer(dataLayer);\r
- ColumnHideShowLayer columnHideShowLayer = new ColumnHideShowLayer(columnReorderLayer);\r
- \r
- \r
- treeLayer = new GETreeLayer(columnHideShowLayer, treeRowModel, false);\r
- \r
- selectionLayer = new SelectionLayer(treeLayer);\r
- \r
- viewportLayer = new ViewportLayer(selectionLayer);\r
- \r
- ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, viewportLayer, selectionLayer);\r
- // Note: The column header layer is wrapped in a filter row composite.\r
- // This plugs in the filter row functionality\r
- \r
- ColumnOverrideLabelAccumulator labelAccumulator = new ColumnOverrideLabelAccumulator(columnHeaderDataLayer);\r
- columnHeaderDataLayer.setConfigLabelAccumulator(labelAccumulator);\r
- \r
- // Register labels\r
- //SortHeaderLayer<TreeNode> sortHeaderLayer = new SortHeaderLayer<TreeNode>(columnHeaderLayer, sortModel, false);\r
-\r
- RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, viewportLayer, selectionLayer);\r
-\r
- // Corner layer\r
- DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider);\r
- cornerDataLayer = new DataLayer(cornerDataProvider);\r
- //CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, sortHeaderLayer);\r
- CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, columnHeaderLayer);\r
-\r
- // Grid\r
- //GridLayer gridLayer = new GridLayer(viewportLayer,sortHeaderLayer,rowHeaderLayer, cornerLayer);\r
- GridLayer gridLayer = new GridLayer(viewportLayer, columnHeaderLayer,rowHeaderLayer, cornerLayer, false);\r
- \r
- /* Since 1.4.0, alternative row rendering uses row indexes in the original data list. \r
- When combined with collapsed tree rows, rows with odd or even index may end up next to each other,\r
- which defeats the purpose of alternating colors. This overrides that and returns the functionality\r
- that we had with 1.0.1. */\r
- gridLayer.setConfigLabelAccumulatorForRegion(GridRegion.BODY, new RelativeAlternatingRowConfigLabelAccumulator());\r
- gridLayer.addConfiguration(new DefaultEditConfiguration());\r
- //gridLayer.addConfiguration(new DefaultEditBindings());\r
- gridLayer.addConfiguration(new GEEditBindings());\r
- \r
- natTable = new NatTable(composite,gridLayer,false);\r
- \r
- //selectionLayer.registerCommandHandler(new EcoCopyDataCommandHandler(selectionLayer,columnHeaderDataLayer,columnAccessor, columnHeaderDataProvider));\r
- \r
- natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable));\r
- natTable.addConfiguration(new DefaultTreeLayerConfiguration2(treeLayer));\r
- natTable.addConfiguration(new SingleClickSortConfiguration());\r
- //natTable.addLayerListener(this);\r
- \r
- natTable.addConfiguration(new GENatTableThemeConfiguration(treeData, style));\r
- natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable));\r
- \r
- natTable.addConfiguration(new AbstractRegistryConfiguration() {\r
- \r
- @Override\r
- public void configureRegistry(IConfigRegistry configRegistry) {\r
- configRegistry.registerConfigAttribute(\r
- EditConfigAttributes.CELL_EDITABLE_RULE,\r
- new IEditableRule() {\r
-\r
- @Override\r
- public boolean isEditable(ILayerCell cell,\r
- IConfigRegistry configRegistry) {\r
- int col = cell.getColumnIndex();\r
- int row = cell.getRowIndex();\r
- TreeNode node = list.get(row);\r
- Modifier modifier = getModifier(node,col);\r
- return modifier != null;\r
- \r
- }\r
-\r
- @Override\r
- public boolean isEditable(int columnIndex, int rowIndex) {\r
- // there are no callers?\r
- return false;\r
- }\r
-\r
- });\r
- configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, new AdaptableCellEditor());\r
- configRegistry.registerConfigAttribute(EditConfigAttributes.CONVERSION_ERROR_HANDLER, new DialogErrorHandling(), DisplayMode.EDIT);\r
- configRegistry.registerConfigAttribute(EditConfigAttributes.VALIDATION_ERROR_HANDLER, new DialogErrorHandling(), DisplayMode.EDIT);\r
- configRegistry.registerConfigAttribute(EditConfigAttributes.DATA_VALIDATOR, new AdaptableDataValidator(),DisplayMode.EDIT);\r
- \r
- Style conversionErrorStyle = new Style();\r
- conversionErrorStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, GUIHelper.COLOR_RED);\r
- conversionErrorStyle.setAttributeValue(CellStyleAttributes.FOREGROUND_COLOR, GUIHelper.COLOR_WHITE);\r
- configRegistry.registerConfigAttribute(EditConfigAttributes.CONVERSION_ERROR_STYLE, conversionErrorStyle, DisplayMode.EDIT);\r
- \r
- configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, new DefaultDisplayConverter(),DisplayMode.EDIT);\r
- \r
- \r
- }\r
- });\r
- \r
- if ((style & SWT.BORDER) > 0) {\r
- natTable.addOverlayPainter(new NatTableBorderOverlayPainter());\r
- }\r
- \r
- natTable.configure();\r
- \r
-// natTable.addListener(SWT.MenuDetect, new NatTableMenuListener());\r
- \r
-// DefaultToolTip toolTip = new EcoCellToolTip(natTable, columnAccessor);\r
-// toolTip.setBackgroundColor(natTable.getDisplay().getSystemColor(SWT.COLOR_WHITE));\r
-// toolTip.setPopupDelay(500);\r
-// toolTip.activate();\r
-// toolTip.setShift(new Point(10, 10));\r
-\r
- \r
-// menuManager.createContextMenu(composite);\r
-// natTable.setMenu(getMenuManager().getMenu());\r
- \r
- selectionAdaptor = new NatTableSelectionAdaptor(natTable, selectionLayer, treeData);\r
- }\r
- \r
- Modifier getModifier(TreeNode element, int columnIndex) {\r
- GENodeQueryManager manager = element.getManager();\r
- final NodeContext context = element.getContext();\r
- Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);\r
- if (labeler == null)\r
- return null;\r
- Column column = columns[columnIndex];\r
-\r
- return labeler.getModifier(modificationContext, column.getKey());\r
-\r
- }\r
- \r
- private class AdaptableCellEditor implements ICellEditor {\r
- ICellEditor editor;\r
-\r
- @Override\r
- public Control activateCell(Composite parent, Object originalCanonicalValue, EditModeEnum editMode,\r
- ICellEditHandler editHandler, ILayerCell cell, IConfigRegistry configRegistry) {\r
- int col = cell.getColumnIndex();\r
- int row = cell.getRowIndex();\r
- TreeNode node = list.get(row);\r
- Modifier modifier = getModifier(node, col);\r
- if (modifier == null)\r
- return null;\r
- \r
- editor = null;\r
- if (modifier instanceof DialogModifier) {\r
- DialogModifier mod = (DialogModifier)modifier;\r
- editor = new DialogCellEditor(node, col, mod);\r
- } else if (modifier instanceof CustomModifier) {\r
- CustomModifier mod = (CustomModifier)modifier;\r
- editor = new CustomCellEditor(node, col, mod);\r
- } else if (modifier instanceof EnumerationModifier) {\r
- EnumerationModifier mod = (EnumerationModifier)modifier;\r
- editor = new ComboBoxCellEditor(mod.getValues());\r
- } else {\r
- editor = new TextCellEditor();\r
- }\r
- \r
- return editor.activateCell(parent, originalCanonicalValue, editMode, editHandler, cell, configRegistry);\r
- }\r
-\r
- @Override\r
- public int getColumnIndex() {\r
- return editor.getColumnIndex();\r
- }\r
-\r
- @Override\r
- public int getRowIndex() {\r
- return editor.getRowIndex();\r
- }\r
-\r
- @Override\r
- public int getColumnPosition() {\r
- return editor.getColumnPosition();\r
- }\r
-\r
- @Override\r
- public int getRowPosition() {\r
- return editor.getRowPosition();\r
- }\r
-\r
- @Override\r
- public Object getEditorValue() {\r
- return editor.getEditorValue();\r
- }\r
-\r
- @Override\r
- public void setEditorValue(Object value) {\r
- editor.setEditorValue(value);\r
- \r
- }\r
-\r
- @Override\r
- public Object getCanonicalValue() {\r
- return editor.getCanonicalValue();\r
- }\r
-\r
- @Override\r
- public Object getCanonicalValue(IEditErrorHandler conversionErrorHandler) {\r
- return editor.getCanonicalValue();\r
- }\r
-\r
- @Override\r
- public void setCanonicalValue(Object canonicalValue) {\r
- editor.setCanonicalValue(canonicalValue);\r
- \r
- }\r
-\r
- @Override\r
- public boolean validateCanonicalValue(Object canonicalValue) {\r
- return editor.validateCanonicalValue(canonicalValue);\r
- }\r
-\r
- @Override\r
- public boolean validateCanonicalValue(Object canonicalValue, IEditErrorHandler validationErrorHandler) {\r
- return editor.validateCanonicalValue(canonicalValue, validationErrorHandler);\r
- }\r
-\r
- @Override\r
- public boolean commit(MoveDirectionEnum direction) {\r
- return editor.commit(direction);\r
- }\r
-\r
- @Override\r
- public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit) {\r
- return editor.commit(direction, closeAfterCommit);\r
- }\r
-\r
- @Override\r
- public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit, boolean skipValidation) {\r
- return editor.commit(direction, closeAfterCommit, skipValidation);\r
- }\r
-\r
- @Override\r
- public void close() {\r
- editor.close();\r
- \r
- }\r
-\r
- @Override\r
- public boolean isClosed() {\r
- return editor.isClosed();\r
- }\r
-\r
- @Override\r
- public Control getEditorControl() {\r
- return editor.getEditorControl();\r
- }\r
-\r
- @Override\r
- public Control createEditorControl(Composite parent) {\r
- return editor.createEditorControl(parent);\r
- }\r
-\r
- @Override\r
- public boolean openInline(IConfigRegistry configRegistry, List<String> configLabels) {\r
- return EditConfigHelper.openInline(configRegistry, configLabels);\r
- }\r
-\r
- @Override\r
- public boolean supportMultiEdit(IConfigRegistry configRegistry, List<String> configLabels) {\r
- return editor.supportMultiEdit(configRegistry, configLabels);\r
- }\r
-\r
- @Override\r
- public boolean openMultiEditDialog() {\r
- return editor.openMultiEditDialog();\r
- }\r
-\r
- @Override\r
- public boolean openAdjacentEditor() {\r
- return editor.openAdjacentEditor();\r
- }\r
-\r
- @Override\r
- public boolean activateAtAnyPosition() {\r
- return true;\r
- }\r
-\r
- @Override\r
- public boolean activateOnTraversal(IConfigRegistry configRegistry, List<String> configLabels) {\r
- return editor.activateOnTraversal(configRegistry, configLabels);\r
- }\r
-\r
- @Override\r
- public void addEditorControlListeners() {\r
- editor.addEditorControlListeners();\r
- \r
- }\r
-\r
- @Override\r
- public void removeEditorControlListeners() {\r
- editor.removeEditorControlListeners();\r
- \r
- }\r
-\r
- @Override\r
- public Rectangle calculateControlBounds(Rectangle cellBounds) {\r
- return editor.calculateControlBounds(cellBounds);\r
- }\r
- }\r
- \r
- private class AdaptableDataValidator implements IDataValidator {\r
- @Override\r
- public boolean validate(ILayerCell cell, IConfigRegistry configRegistry, Object newValue) {\r
- int col = cell.getColumnIndex();\r
- int row = cell.getRowIndex();\r
- return validate(col, row, newValue);\r
- }\r
- \r
- @Override\r
- public boolean validate(int col, int row, Object newValue) {\r
- TreeNode node = list.get(row);\r
- Modifier modifier = getModifier(node, col);\r
- if (modifier == null)\r
- return false;\r
- \r
- String err = modifier.isValid(newValue.toString());\r
- if (err == null)\r
- return true;\r
- modifier.isValid(newValue.toString());\r
- throw new ValidationFailedException(err);\r
- }\r
- }\r
- \r
- private class CustomCellEditor extends AbstractCellEditor {\r
- TreeNode node;\r
- CustomModifier customModifier;\r
- Control control;\r
- int column;\r
- \r
- public CustomCellEditor(TreeNode node, int column, CustomModifier customModifier) {\r
- this.customModifier = customModifier;\r
- this.node = node;\r
- this.column = column;\r
- }\r
-\r
- @Override\r
- public Object getEditorValue() {\r
- return customModifier.getValue();\r
- }\r
-\r
- @Override\r
- public void setEditorValue(Object value) {\r
- customModifier.modify(value.toString());\r
- \r
- }\r
-\r
- @Override\r
- public Control getEditorControl() {\r
- return control;\r
- }\r
-\r
- @Override\r
- public Control createEditorControl(Composite parent) {\r
- return (Control)customModifier.createControl(parent, null, column, node.getContext());\r
- \r
- }\r
-\r
- @Override\r
- protected Control activateCell(Composite parent, Object originalCanonicalValue) {\r
- this.control = createEditorControl(parent);\r
- return control;\r
- }\r
- \r
- \r
- }\r
- \r
- private class DialogCellEditor extends AbstractDialogCellEditor {\r
- TreeNode node;\r
- DialogModifier dialogModifier;\r
- int column;\r
- \r
- String res = null;\r
- Semaphore sem;\r
- \r
- boolean closed = false;\r
- \r
- public DialogCellEditor(TreeNode node, int column, DialogModifier dialogModifier) {\r
- this.dialogModifier = dialogModifier;\r
- this.node = node;\r
- this.column = column;\r
- }\r
- \r
- @Override\r
- public int open() {\r
- sem = new Semaphore(1);\r
- Consumer<String> callback = result -> {\r
- res = result;\r
- sem.release();\r
- };\r
- String status = dialogModifier.query(this.parent.getShell(), null, column, node.getContext(), callback);\r
- if (status != null) {\r
- closed = true;\r
- return Window.CANCEL;\r
- }\r
- \r
- try {\r
- sem.acquire();\r
- } catch (InterruptedException e) {\r
- e.printStackTrace();\r
- }\r
- closed = true;\r
- return Window.OK;\r
- }\r
- \r
- @Override\r
- public DialogModifier createDialogInstance() {\r
- closed = false;\r
- return dialogModifier;\r
- }\r
- \r
- @Override\r
- public Object getDialogInstance() {\r
- return (DialogModifier)this.dialog;\r
- }\r
- \r
- @Override\r
- public void close() {\r
- \r
- }\r
- \r
- @Override\r
- public Object getEditorValue() {\r
- return null;\r
- }\r
- \r
- @Override\r
- public void setEditorValue(Object value) {\r
- // dialog modifier handles this internally\r
- }\r
- \r
- @Override\r
- public boolean isClosed() {\r
- return closed;\r
- }\r
- \r
- }\r
- \r
-\r
- /**\r
- * The job that is used for off-loading image loading tasks (see\r
- * {@link ImageTask} to a worker thread from the main UI thread.\r
- */\r
- ImageLoaderJob imageLoaderJob;\r
- \r
- // Map<NodeContext, ImageTask> imageTasks = new THashMap<NodeContext, ImageTask>();\r
- Map<TreeNode, ImageTask> imageTasks = new THashMap<TreeNode, ImageTask>();\r
- \r
- void queueImageTask(TreeNode node, ImageTask task) {\r
- synchronized (imageTasks) {\r
- imageTasks.put(node, task);\r
- }\r
- imageLoaderJob.scheduleIfNecessary(100);\r
- }\r
- \r
- /**\r
- * Invoked in a job worker thread.\r
- * \r
- * @param monitor\r
- */\r
- @Override\r
- protected IStatus setPendingImages(IProgressMonitor monitor) {\r
- ImageTask[] tasks = null;\r
- synchronized (imageTasks) {\r
- tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]);\r
- imageTasks.clear();\r
- }\r
-\r
- MultiStatus status = null;\r
-\r
- // Load missing images\r
- for (ImageTask task : tasks) {\r
- Object desc = task.descsOrImage;\r
- if (desc instanceof ImageDescriptor) {\r
- try {\r
- desc = resourceManager.get((ImageDescriptor) desc);\r
- task.descsOrImage = desc;\r
- } catch (DeviceResourceException e) {\r
- if (status == null)\r
- status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null);\r
- status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e));\r
- }\r
- }\r
- \r
- }\r
-\r
- // Perform final UI updates in the UI thread.\r
- final ImageTask[] _tasks = tasks;\r
- thread.asyncExec(new Runnable() {\r
- @Override\r
- public void run() {\r
- setImages(_tasks);\r
- }\r
- });\r
-\r
- return status != null ? status : Status.OK_STATUS;\r
- }\r
- \r
-\r
- void setImages(ImageTask[] tasks) {\r
- for (ImageTask task : tasks)\r
- if (task != null)\r
- setImage(task);\r
- }\r
- \r
- void setImage(ImageTask task) {\r
- if (!task.node.isDisposed())\r
- update(task.node, 0);\r
- }\r
- \r
- private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider {\r
- \r
- private NatTableGraphExplorer ge;\r
- \r
- GraphExplorerPostSelectionProvider(NatTableGraphExplorer ge) {\r
- this.ge = ge;\r
- }\r
- \r
- void dispose() {\r
- ge = null;\r
- }\r
- \r
- @Override\r
- public void setSelection(final ISelection selection) {\r
- if(ge == null) return;\r
- ge.setSelection(selection, false);\r
- \r
- }\r
- \r
-\r
- @Override\r
- public void removeSelectionChangedListener(ISelectionChangedListener listener) {\r
- if(ge == null) return;\r
- if(ge.isDisposed()) {\r
- if (DEBUG_SELECTION_LISTENERS)\r
- System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener);\r
- return;\r
- }\r
- ge.selectionProvider.removeSelectionChangedListener(listener);\r
- }\r
- \r
- @Override\r
- public void addPostSelectionChangedListener(ISelectionChangedListener listener) {\r
- if(ge == null) return;\r
- if (!ge.thread.currentThreadAccess())\r
- throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());\r
- if(ge.isDisposed()) {\r
- System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener);\r
- return;\r
- }\r
- ge.selectionProvider.addPostSelectionChangedListener(listener);\r
- }\r
-\r
- @Override\r
- public void removePostSelectionChangedListener(ISelectionChangedListener listener) {\r
- if(ge == null) return;\r
- if(ge.isDisposed()) {\r
- if (DEBUG_SELECTION_LISTENERS)\r
- System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener);\r
- return;\r
- }\r
- ge.selectionProvider.removePostSelectionChangedListener(listener);\r
- }\r
- \r
-\r
- @Override\r
- public void addSelectionChangedListener(ISelectionChangedListener listener) {\r
- if(ge == null) return;\r
- if (!ge.thread.currentThreadAccess())\r
- throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());\r
- if (ge.natTable.isDisposed() || ge.selectionProvider == null) {\r
- System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener);\r
- return;\r
- }\r
-\r
- ge.selectionProvider.addSelectionChangedListener(listener);\r
- }\r
-\r
- \r
- @Override\r
- public ISelection getSelection() {\r
- if(ge == null) return StructuredSelection.EMPTY;\r
- if (!ge.thread.currentThreadAccess())\r
- throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread());\r
- if (ge.natTable.isDisposed() || ge.selectionProvider == null)\r
- return StructuredSelection.EMPTY;\r
- return ge.selectionProvider.getSelection();\r
- }\r
- \r
- }\r
- \r
- static class ModifierValidator implements ICellEditorValidator {\r
- private Modifier modifier;\r
- public ModifierValidator(Modifier modifier) {\r
- this.modifier = modifier;\r
- }\r
- \r
- @Override\r
- public String isValid(Object value) {\r
- return modifier.isValid((String)value);\r
- }\r
- }\r
- \r
- static class UpdateRunner implements Runnable {\r
-\r
- final NatTableGraphExplorer ge;\r
-\r
- UpdateRunner(NatTableGraphExplorer ge, IGraphExplorerContext geContext) {\r
- this.ge = ge;\r
- }\r
-\r
- public void run() {\r
- try {\r
- doRun();\r
- } catch (Throwable t) {\r
- t.printStackTrace();\r
- }\r
- }\r
-\r
- public void doRun() {\r
- \r
- if (ge.isDisposed())\r
- return;\r
-\r
- HashSet<UpdateItem> items;\r
-\r
- ScrollBar verticalBar = ge.natTable.getVerticalBar();\r
- \r
- \r
- synchronized (ge.pendingItems) {\r
- items = ge.pendingItems;\r
- ge.pendingItems = new HashSet<UpdateItem>();\r
- }\r
- if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size());\r
-\r
- //ge.natTable.setRedraw(false);\r
- for (UpdateItem item : items) {\r
- item.update(ge.natTable);\r
- }\r
- \r
- // check if vertical scroll bar has become visible and refresh layout.\r
- boolean currentlyVerticalBarVisible = verticalBar.isVisible();\r
- if (ge.verticalBarVisible != currentlyVerticalBarVisible) {\r
- ge.verticalBarVisible = currentlyVerticalBarVisible;\r
- ge.natTable.getParent().layout();\r
- }\r
- \r
- //ge.natTable.setRedraw(true);\r
- \r
- synchronized (ge.pendingItems) {\r
- if (!ge.scheduleUpdater()) {\r
- ge.updating = false;\r
- }\r
- }\r
- if (DEBUG) {\r
- if (!ge.updating) {\r
- printDebug(ge); \r
- }\r
- }\r
- }\r
-\r
- }\r
- \r
- private static void printDebug(NatTableGraphExplorer ge) {\r
- ge.printTree(ge.rootNode, 0);\r
- System.out.println("Expanded");\r
- for (TreeNode n : ge.treeLayer.expanded)\r
- System.out.println(n);\r
- System.out.println("Expanded end");\r
- System.out.println("Hidden ");\r
- for (int i : ge.treeLayer.getHiddenRowIndexes()) {\r
- System.out.print(i + " ");\r
- }\r
- System.out.println();\r
-// Display.getCurrent().timerExec(1000, new Runnable() {\r
-// \r
-// @Override\r
-// public void run() {\r
-// System.out.println("Hidden delayed ");\r
-// for (int i : ge.treeLayer.getHiddenRowIndexes()) {\r
-// System.out.print(i + " ");\r
-// }\r
-// System.out.println();\r
-// }\r
-// });\r
- }\r
- \r
- \r
- public static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {\r
- // This is for query debugging only.\r
- \r
- private NatTableGraphExplorer ge;\r
- int queryIndent = 0;\r
-\r
- GECache2 cache = new GECache2();\r
- AtomicBoolean propagating = new AtomicBoolean(false);\r
- Object propagateList = new Object();\r
- Object propagate = new Object();\r
- List<Runnable> scheduleList = new ArrayList<Runnable>();\r
- final Deque<Integer> activity = new LinkedList<Integer>();\r
- int activityInt = 0;\r
- \r
- AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();\r
-\r
- /**\r
- * Keeps track of nodes that have already been auto-expanded. After\r
- * being inserted into this set, nodes will not be forced to stay in an\r
- * expanded state after that. This makes it possible for the user to\r
- * close auto-expanded nodes.\r
- */\r
- Map<NodeContext, Boolean> autoExpanded = new WeakHashMap<NodeContext, Boolean>();\r
-\r
- public GeViewerContext(NatTableGraphExplorer ge) {\r
- this.ge = ge;\r
- }\r
- \r
- public MapList<NodeContext,TreeNode> getContextToNodeMap() {\r
- if (ge == null)\r
- return null;\r
- return ge.contextToNodeMap;\r
- }\r
- \r
- public NatTableGraphExplorer getGe() {\r
- return ge;\r
- }\r
- \r
- @Override\r
- protected void doDispose() {\r
- //saveState();\r
- autoExpanded.clear();\r
- }\r
-\r
- @Override\r
- public IGECache getCache() {\r
- return cache;\r
- }\r
-\r
- @Override\r
- public int queryIndent() {\r
- return queryIndent;\r
- }\r
-\r
- @Override\r
- public int queryIndent(int offset) {\r
- queryIndent += offset;\r
- return queryIndent;\r
- }\r
-\r
- @Override\r
- @SuppressWarnings("unchecked")\r
- public <T> NodeQueryProcessor<T> getProcessor(Object o) {\r
- if (ge == null)\r
- return null;\r
- return ge.processors.get(o);\r
- }\r
-\r
- @Override\r
- @SuppressWarnings("unchecked")\r
- public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {\r
- return ge.primitiveProcessors.get(o);\r
- }\r
-\r
- @SuppressWarnings("unchecked")\r
- @Override\r
- public <T> DataSource<T> getDataSource(Class<T> clazz) {\r
- return ge.dataSources.get(clazz);\r
- }\r
-\r
- @Override\r
- public void update(UIElementReference ref) {\r
- if (ref instanceof ViewerCellReference) {\r
- ViewerCellReference tiref = (ViewerCellReference) ref;\r
- Object element = tiref.getElement();\r
- int columnIndex = tiref.getColumn();\r
- // NOTE: must be called regardless of the the item value.\r
- // A null item is currently used to indicate a tree root update.\r
- ge.update((TreeNode)element,columnIndex);\r
- } else if (ref instanceof ViewerRowReference) {\r
- ViewerRowReference rref = (ViewerRowReference)ref;\r
- Object element = rref.getElement();\r
- ge.update((TreeNode)element);\r
- } else {\r
- throw new IllegalArgumentException("Ui Reference is unknkown " + ref);\r
- }\r
- }\r
-\r
- @Override\r
- public Object getPropagateLock() {\r
- return propagate;\r
- }\r
-\r
- @Override\r
- public Object getPropagateListLock() {\r
- return propagateList;\r
- }\r
-\r
- @Override\r
- public boolean isPropagating() {\r
- return propagating.get();\r
- }\r
-\r
- @Override\r
- public void setPropagating(boolean b) {\r
- this.propagating.set(b);\r
- }\r
-\r
- @Override\r
- public List<Runnable> getScheduleList() {\r
- return scheduleList;\r
- }\r
-\r
- @Override\r
- public void setScheduleList(List<Runnable> list) {\r
- this.scheduleList = list;\r
- }\r
-\r
- @Override\r
- public Deque<Integer> getActivity() {\r
- return activity;\r
- }\r
-\r
- @Override\r
- public void setActivityInt(int i) {\r
- this.activityInt = i;\r
- }\r
-\r
- @Override\r
- public int getActivityInt() {\r
- return activityInt;\r
- }\r
-\r
- @Override\r
- public void scheduleQueryUpdate(Runnable r) {\r
- if (ge == null)\r
- return;\r
- if (ge.isDisposed())\r
- return;\r
- if (currentQueryUpdater.compareAndSet(null, r)) {\r
- ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER);\r
- }\r
- }\r
-\r
- Runnable QUERY_UPDATE_SCHEDULER = new Runnable() {\r
- @Override\r
- public void run() {\r
- Runnable r = currentQueryUpdater.getAndSet(null);\r
- if (r != null) {\r
- r.run();\r
- }\r
- }\r
- };\r
- \r
- @Override\r
- public void dispose() {\r
- cache.dispose();\r
- cache = new DummyCache();\r
- scheduleList.clear();\r
- autoExpanded.clear();\r
- autoExpanded = null;\r
- ge = null;\r
- \r
- }\r
- }\r
- \r
- private class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor<Boolean> implements\r
- IsExpandedProcessor, ProcessorLifecycle {\r
- /**\r
- * The set of currently expanded node contexts.\r
- */\r
- private final HashSet<NodeContext> expanded = new HashSet<NodeContext>();\r
- private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();\r
-\r
- private NatTable natTable;\r
- private List<TreeNode> list;\r
-\r
- public TreeNodeIsExpandedProcessor() {\r
- }\r
-\r
- @Override\r
- public Object getIdentifier() {\r
- return BuiltinKeys.IS_EXPANDED;\r
- }\r
-\r
- @Override\r
- public String toString() {\r
- return "IsExpandedProcessor";\r
- }\r
-\r
- @Override\r
- public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Boolean> key) {\r
- boolean isExpanded = expanded.contains(context);\r
- expandedQueries.put(context, updater);\r
- return Boolean.valueOf(isExpanded);\r
- }\r
-\r
- @Override\r
- public Collection<NodeContext> getExpanded() {\r
- return new HashSet<NodeContext>(expanded);\r
- }\r
-\r
- @Override\r
- public boolean getExpanded(NodeContext context) {\r
- return this.expanded.contains(context);\r
- }\r
-\r
- @Override\r
- public boolean setExpanded(NodeContext context, boolean expanded) {\r
- return _setExpanded(context, expanded);\r
- }\r
-\r
- @Override\r
- public boolean replaceExpanded(NodeContext context, boolean expanded) {\r
- return nodeStatusChanged(context, expanded);\r
- }\r
-\r
- private boolean _setExpanded(NodeContext context, boolean expanded) {\r
- if (expanded) {\r
- return this.expanded.add(context);\r
- } else {\r
- return this.expanded.remove(context);\r
- }\r
- }\r
-\r
- ILayerListener treeListener = new ILayerListener() {\r
- \r
- @Override\r
- public void handleLayerEvent(ILayerEvent event) {\r
- if (event instanceof ShowRowPositionsEvent) {\r
- ShowRowPositionsEvent e = (ShowRowPositionsEvent)event;\r
- for (Range r : e.getRowPositionRanges()) {\r
- int expanded = viewportLayer.getRowIndexByPosition(r.start-2)+1;\r
- if (DEBUG)System.out.println("IsExpandedProcessor expand " + expanded);\r
- if (expanded < 0 || expanded >= list.size()) {\r
- return;\r
- }\r
- nodeStatusChanged(list.get(expanded).getContext(), true);\r
- }\r
- } else if (event instanceof HideRowPositionsEvent) {\r
- HideRowPositionsEvent e = (HideRowPositionsEvent)event;\r
- for (Range r : e.getRowPositionRanges()) {\r
- int collapsed = viewportLayer.getRowIndexByPosition(r.start-2);\r
- if (DEBUG)System.out.println("IsExpandedProcessor collapse " + collapsed);\r
- if (collapsed < 0 || collapsed >= list.size()) {\r
- return;\r
- }\r
- nodeStatusChanged(list.get(collapsed).getContext(), false);\r
- }\r
- }\r
- \r
- }\r
- };\r
-\r
- protected boolean nodeStatusChanged(NodeContext context, boolean expanded) {\r
- boolean result = _setExpanded(context, expanded);\r
- PrimitiveQueryUpdater updater = expandedQueries.get(context);\r
- if (updater != null)\r
- updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded);\r
- return result;\r
- }\r
-\r
- @Override\r
- public void attached(GraphExplorer explorer) {\r
- Object control = explorer.getControl();\r
- if (control instanceof NatTable) {\r
- this.natTable = (NatTable) control;\r
- this.list = ((NatTableGraphExplorer)explorer).list;\r
- natTable.addLayerListener(treeListener);\r
- \r
- } else {\r
- System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control);\r
- }\r
- }\r
-\r
- @Override\r
- public void clear() {\r
- expanded.clear();\r
- expandedQueries.clear();\r
- }\r
-\r
- @Override\r
- public void detached(GraphExplorer explorer) {\r
- clear();\r
- if (natTable != null) {\r
- natTable.removeLayerListener(treeListener);\r
-// natTable.removeListener(SWT.Expand, treeListener);\r
-// natTable.removeListener(SWT.Collapse, treeListener);\r
- natTable = null;\r
- }\r
- }\r
- }\r
- \r
- private void printTree(TreeNode node, int depth) {\r
- String s = "";\r
- for (int i = 0; i < depth; i++) {\r
- s += " ";\r
- }\r
- s += node;\r
- System.out.println(s);\r
- int d = depth+1;\r
- for (TreeNode n : node.getChildren()) {\r
- printTree(n, d);\r
- }\r
- \r
- }\r
- \r
- /**\r
- * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used)\r
- */\r
- final private static class GECacheKey {\r
-\r
- private NodeContext context;\r
- private CacheKey<?> key;\r
-\r
- GECacheKey(NodeContext context, CacheKey<?> key) {\r
- this.context = context;\r
- this.key = key;\r
- if (context == null || key == null)\r
- throw new IllegalArgumentException("Null context or key is not accepted");\r
- }\r
-\r
- GECacheKey(GECacheKey other) {\r
- this.context = other.context;\r
- this.key = other.key;\r
- if (context == null || key == null)\r
- throw new IllegalArgumentException("Null context or key is not accepted");\r
- }\r
-\r
- void setValues(NodeContext context, CacheKey<?> key) {\r
- this.context = context;\r
- this.key = key;\r
- if (context == null || key == null)\r
- throw new IllegalArgumentException("Null context or key is not accepted");\r
- }\r
-\r
- @Override\r
- public int hashCode() {\r
- return context.hashCode() | key.hashCode();\r
- }\r
-\r
- @Override\r
- public boolean equals(Object object) {\r
-\r
- if (this == object)\r
- return true;\r
- else if (object == null)\r
- return false;\r
-\r
- GECacheKey i = (GECacheKey) object;\r
-\r
- return key.equals(i.key) && context.equals(i.context);\r
-\r
- }\r
-\r
- };\r
- \r
- /**\r
- * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data.\r
- */\r
- public static class GECache2 implements IGECache {\r
- \r
- final HashMap<GECacheKey, IGECacheEntry> entries = new HashMap<GECacheKey, IGECacheEntry>();\r
- final HashMap<GECacheKey, Set<UIElementReference>> treeReferences = new HashMap<GECacheKey, Set<UIElementReference>>();\r
- final HashMap<NodeContext, Set<GECacheKey>> keyRefs = new HashMap<NodeContext, Set<GECacheKey>>();\r
- \r
- /**\r
- * This single instance is used for all get operations from the cache. This\r
- * should work since the GE cache is meant to be single-threaded within the\r
- * current UI thread, what ever that thread is. For put operations which\r
- * store the key, this is not used.\r
- */\r
- NodeContext getNC = new NodeContext() {\r
- @SuppressWarnings("rawtypes")\r
- @Override\r
- public Object getAdapter(Class adapter) {\r
- return null;\r
- }\r
- \r
- @Override\r
- public <T> T getConstant(ConstantKey<T> key) {\r
- return null;\r
- }\r
- \r
- @Override\r
- public Set<ConstantKey<?>> getKeys() {\r
- return Collections.emptySet();\r
- }\r
- };\r
- CacheKey<?> getCK = new CacheKey<Object>() {\r
- @Override\r
- public Object processorIdenfitier() {\r
- return this;\r
- }\r
- };\r
- GECacheKey getKey = new GECacheKey(getNC, getCK);\r
- \r
- \r
- private void addKey(GECacheKey key) {\r
- Set<GECacheKey> refs = keyRefs.get(key.context);\r
- if (refs != null) {\r
- refs.add(key);\r
- } else {\r
- refs = new HashSet<GECacheKey>();\r
- refs.add(key);\r
- keyRefs.put(key.context, refs);\r
- }\r
- }\r
- \r
- private void removeKey(GECacheKey key) {\r
- Set<GECacheKey> refs = keyRefs.get(key.context);\r
- if (refs != null) {\r
- refs.remove(key);\r
- } \r
- }\r
-\r
- public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key, T value) {\r
-// if (DEBUG) System.out.println("Add entry " + context + " " + key);\r
- IGECacheEntry entry = new GECacheEntry(context, key, value);\r
- GECacheKey gekey = new GECacheKey(context, key);\r
- entries.put(gekey, entry);\r
- addKey(gekey);\r
- return entry;\r
- }\r
-\r
- @SuppressWarnings("unchecked")\r
- public <T> T get(NodeContext context, CacheKey<T> key) {\r
- getKey.setValues(context, key);\r
- IGECacheEntry entry = entries.get(getKey);\r
- if (entry == null)\r
- return null;\r
- return (T) entry.getValue();\r
- }\r
-\r
- @Override\r
- public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {\r
- assert(context != null);\r
- assert(key != null);\r
- getKey.setValues(context, key);\r
- return entries.get(getKey);\r
- }\r
-\r
- @Override\r
- public <T> void remove(NodeContext context, CacheKey<T> key) {\r
-// if (DEBUG) System.out.println("Remove entry " + context + " " + key);\r
- getKey.setValues(context, key);\r
- entries.remove(getKey);\r
- removeKey(getKey);\r
- }\r
-\r
- @Override\r
- public <T> Set<UIElementReference> getTreeReference(NodeContext context, CacheKey<T> key) {\r
- assert(context != null);\r
- assert(key != null);\r
- getKey.setValues(context, key);\r
- return treeReferences.get(getKey);\r
- }\r
-\r
- @Override\r
- public <T> void putTreeReference(NodeContext context, CacheKey<T> key, UIElementReference reference) {\r
- assert(context != null);\r
- assert(key != null);\r
- //if (DEBUG) System.out.println("Add tree reference " + context + " " + key);\r
- getKey.setValues(context, key);\r
- Set<UIElementReference> refs = treeReferences.get(getKey);\r
- if (refs != null) {\r
- refs.add(reference);\r
- } else {\r
- refs = new HashSet<UIElementReference>(4);\r
- refs.add(reference);\r
- GECacheKey gekey = new GECacheKey(getKey);\r
- treeReferences.put(gekey, refs);\r
- addKey(gekey);\r
- }\r
- }\r
-\r
- @Override\r
- public <T> Set<UIElementReference> removeTreeReference(NodeContext context, CacheKey<T> key) {\r
- assert(context != null);\r
- assert(key != null);\r
- //if (DEBUG) System.out.println("Remove tree reference " + context + " " + key);\r
- getKey.setValues(context, key);\r
- removeKey(getKey);\r
- return treeReferences.remove(getKey);\r
- }\r
- \r
- @Override\r
- public boolean isShown(NodeContext context) {\r
- return references.get(context) > 0;\r
- }\r
-\r
- private TObjectIntHashMap<NodeContext> references = new TObjectIntHashMap<NodeContext>();\r
- \r
- @Override\r
- public void incRef(NodeContext context) {\r
- int exist = references.get(context);\r
- references.put(context, exist+1);\r
- }\r
- \r
- @Override\r
- public void decRef(NodeContext context) {\r
- int exist = references.get(context);\r
- references.put(context, exist-1);\r
- if(exist == 1) {\r
- references.remove(context);\r
- }\r
- }\r
- \r
- public void dispose() {\r
- references.clear();\r
- entries.clear();\r
- treeReferences.clear();\r
- keyRefs.clear();\r
- }\r
- \r
- public void dispose(NodeContext context) {\r
- Set<GECacheKey> keys = keyRefs.remove(context);\r
- if (keys != null) {\r
- for (GECacheKey key : keys) {\r
- entries.remove(key);\r
- treeReferences.remove(key);\r
- }\r
- }\r
- }\r
- }\r
- \r
- \r
- /**\r
- * Non-functional cache to replace actual cache when GEContext is disposed.\r
- * \r
- * @author mlmarko\r
- *\r
- */\r
- private static class DummyCache extends GECache2 {\r
-\r
- @Override\r
- public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {\r
- return null;\r
- }\r
-\r
- @Override\r
- public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key,\r
- T value) {\r
- return null;\r
- }\r
-\r
- @Override\r
- public <T> void putTreeReference(NodeContext context, CacheKey<T> key,\r
- UIElementReference reference) {\r
- }\r
-\r
- @Override\r
- public <T> T get(NodeContext context, CacheKey<T> key) {\r
- return null;\r
- }\r
-\r
- @Override\r
- public <T> Set<UIElementReference> getTreeReference(\r
- NodeContext context, CacheKey<T> key) {\r
- return null;\r
- }\r
-\r
- @Override\r
- public <T> void remove(NodeContext context, CacheKey<T> key) {\r
- \r
- }\r
-\r
- @Override\r
- public <T> Set<UIElementReference> removeTreeReference(\r
- NodeContext context, CacheKey<T> key) {\r
- return null;\r
- }\r
-\r
- @Override\r
- public boolean isShown(NodeContext context) {\r
- return false;\r
- }\r
-\r
- @Override\r
- public void incRef(NodeContext context) {\r
- \r
- }\r
-\r
- @Override\r
- public void decRef(NodeContext context) {\r
- \r
- }\r
- \r
- @Override\r
- public void dispose() {\r
- super.dispose();\r
- }\r
- }\r
- \r
- private class NatTableHeaderMenuConfiguration extends AbstractHeaderMenuConfiguration {\r
- \r
- \r
- public NatTableHeaderMenuConfiguration(NatTable natTable) {\r
- super(natTable);\r
- }\r
-\r
- @Override\r
- protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) {\r
- return super.createColumnHeaderMenu(natTable)\r
- .withHideColumnMenuItem()\r
- .withShowAllColumnsMenuItem()\r
- .withAutoResizeSelectedColumnsMenuItem();\r
- }\r
- \r
- @Override\r
- protected PopupMenuBuilder createCornerMenu(NatTable natTable) {\r
- return super.createCornerMenu(natTable)\r
- .withShowAllColumnsMenuItem();\r
- }\r
- @Override\r
- protected PopupMenuBuilder createRowHeaderMenu(NatTable natTable) {\r
- return super.createRowHeaderMenu(natTable);\r
- }\r
- }\r
- \r
- private static class RelativeAlternatingRowConfigLabelAccumulator extends AlternatingRowConfigLabelAccumulator {\r
- \r
- @Override\r
- public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) {\r
- configLabels.addLabel((rowPosition % 2 == 0 ? EVEN_ROW_CONFIG_TYPE : ODD_ROW_CONFIG_TYPE));\r
- }\r
- }\r
- \r
- @Override\r
- public Object getClicked(Object event) {\r
- MouseEvent e = (MouseEvent)event;\r
- final NatTable tree = (NatTable) e.getSource();\r
- Point point = new Point(e.x, e.y);\r
- int y = natTable.getRowPositionByY(point.y);\r
- int x = natTable.getColumnPositionByX(point.x);\r
- if (x < 0 | y <= 0)\r
- return null;\r
- return list.get(y-1); \r
- }\r
-}\r
+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 <marko.luukkainen@vtt.fi>
+ *
+ */
+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<TreeNode> list = new ArrayList<>();
+
+ private NatTableSelectionAdaptor selectionAdaptor;
+ private NatTableColumnLayout layout;
+
+ LocalResourceManager localResourceManager;
+ DeviceResourceManager resourceManager;
+
+
+ private IThreadWorkQueue thread;
+
+ @SuppressWarnings({ "rawtypes" })
+ final HashMap<CacheKey<?>, NodeQueryProcessor> processors = new HashMap<CacheKey<?>, NodeQueryProcessor>();
+ @SuppressWarnings({ "rawtypes" })
+ final HashMap<Object, PrimitiveQueryProcessor> primitiveProcessors = new HashMap<Object, PrimitiveQueryProcessor>();
+ @SuppressWarnings({ "rawtypes" })
+ final HashMap<Class, DataSource> dataSources = new HashMap<Class, DataSource>();
+
+ FontDescriptor originalFont;
+ protected ColorDescriptor originalForeground;
+ protected ColorDescriptor originalBackground;
+ private Color invalidModificationColor;
+
+ private Column[] columns;
+ private Map<String,Integer> 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<FocusListener> focusListeners = new CopyOnWriteArrayList<FocusListener>();
+ private final CopyOnWriteArrayList<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();
+ private final CopyOnWriteArrayList<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>();
+
+ 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<NodeContext, TreeNode> contextToNodeMap;
+
+ private ModificationContext modificationContext = null;
+
+ private boolean filterSelectionEdit = true;
+
+ private boolean expand;
+ private boolean verticalBarVisible = false;
+
+ private BinaryFunction<Object[], GraphExplorer, Object[]> selectionTransformation = new BinaryFunction<Object[], GraphExplorer, Object[]>() {
+
+ @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<NodeContext, TreeNode>();
+
+ 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<TreeNode> selectedNodes = new ArrayList<TreeNode>();
+
+ 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<NodeContext> 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<NodeContext> 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<TreeNode> 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<TreeNode> 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> T getAdapter(Class<T> 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<Map<Column, Object>> 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<String> usedColumnKeys = new HashSet<String>();
+ List<Column> duplicateColumns = new ArrayList<Column>();
+ 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<Map<Column, Object>> callback) {
+
+ HashMap<String, Integer> keyToIndex = new HashMap<String, Integer>();
+ 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<Column, Object> map = new HashMap<Column, Object>();
+
+ // 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 <T> void setProcessor(NodeQueryProcessor<T> processor) {
+ assertNotDisposed();
+ if (processor == null)
+ throw new IllegalArgumentException("null processor");
+
+ processors.put(processor.getIdentifier(), processor);
+ }
+
+ @Override
+ public <T> void setPrimitiveProcessor(PrimitiveQueryProcessor<T> 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 <T> void setDataSource(DataSource<T> provider) {
+ assertNotDisposed();
+ if (provider == null)
+ throw new IllegalArgumentException("null provider");
+ dataSources.put(provider.getProvidedClass(), provider);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> DataSource<T> removeDataSource(Class<T> 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<Object[], GraphExplorer, Object[]> f) {
+ this.selectionTransformation = f;
+ }
+
+ public ISelection getWidgetSelection() {
+ return selectionAdaptor.getSelection();
+ }
+
+ @Override
+ public <T> 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 <T> 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<String> uiContexts;
+
+ @Override
+ public void setUIContexts(Set<String> 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> 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<NodeContext> 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> T query(NodeContext context, CacheKey<T> 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.
+ *
+ * <p>
+ * Must be invoked during right after construction.
+ *
+ * @param serviceLocator
+ * a specific service locator or <code>null</code> 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 <T> NodeQueryProcessor<T> getProcessor(QueryKey<T> key) {
+ return explorerContext.getProcessor(key);
+ }
+
+ @Override
+ public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(PrimitiveQueryKey<T> key) {
+ return explorerContext.getPrimitiveProcessor(key);
+ }
+
+ private HashSet<UpdateItem> pendingItems = new HashSet<UpdateItem>();
+ 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<NodeContext> coll = AdaptionUtils.adaptToCollection(selection, NodeContext.class);
+ Collection<TreeNode> nodes = new ArrayList<TreeNode>();
+ for (NodeContext c : coll) {
+ List<TreeNode> 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<TreeNode> treeRowModel = new GETreeRowModel<TreeNode>(treeData);
+ columnAccessor = new GEColumnAccessor(this);
+
+ IDataProvider dataProvider = new ListDataProvider<TreeNode>(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<TreeNode> sortHeaderLayer = new SortHeaderLayer<TreeNode>(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<String> configLabels) {
+ return EditConfigHelper.openInline(configRegistry, configLabels);
+ }
+
+ @Override
+ public boolean supportMultiEdit(IConfigRegistry configRegistry, List<String> 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<String> 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<String> 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<NodeContext, ImageTask> imageTasks = new THashMap<NodeContext, ImageTask>();
+ Map<TreeNode, ImageTask> imageTasks = new THashMap<TreeNode, ImageTask>();
+
+ 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<UpdateItem> items;
+
+ ScrollBar verticalBar = ge.natTable.getVerticalBar();
+
+
+ synchronized (ge.pendingItems) {
+ items = ge.pendingItems;
+ ge.pendingItems = new HashSet<UpdateItem>();
+ }
+ 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<Runnable> scheduleList = new ArrayList<Runnable>();
+ final Deque<Integer> activity = new LinkedList<Integer>();
+ int activityInt = 0;
+
+ AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();
+
+ /**
+ * 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<NodeContext, Boolean> autoExpanded = new WeakHashMap<NodeContext, Boolean>();
+
+ public GeViewerContext(NatTableGraphExplorer ge) {
+ this.ge = ge;
+ }
+
+ public MapList<NodeContext,TreeNode> 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 <T> NodeQueryProcessor<T> getProcessor(Object o) {
+ if (ge == null)
+ return null;
+ return ge.processors.get(o);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {
+ return ge.primitiveProcessors.get(o);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> DataSource<T> getDataSource(Class<T> 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<Runnable> getScheduleList() {
+ return scheduleList;
+ }
+
+ @Override
+ public void setScheduleList(List<Runnable> list) {
+ this.scheduleList = list;
+ }
+
+ @Override
+ public Deque<Integer> 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<Boolean> implements
+ IsExpandedProcessor, ProcessorLifecycle {
+ /**
+ * The set of currently expanded node contexts.
+ */
+ private final HashSet<NodeContext> expanded = new HashSet<NodeContext>();
+ private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();
+
+ private NatTable natTable;
+ private List<TreeNode> 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<Boolean> key) {
+ boolean isExpanded = expanded.contains(context);
+ expandedQueries.put(context, updater);
+ return Boolean.valueOf(isExpanded);
+ }
+
+ @Override
+ public Collection<NodeContext> getExpanded() {
+ return new HashSet<NodeContext>(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<GECacheKey, IGECacheEntry> entries = new HashMap<GECacheKey, IGECacheEntry>();
+ final HashMap<GECacheKey, Set<UIElementReference>> treeReferences = new HashMap<GECacheKey, Set<UIElementReference>>();
+ final HashMap<NodeContext, Set<GECacheKey>> keyRefs = new HashMap<NodeContext, Set<GECacheKey>>();
+
+ /**
+ * 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> T getConstant(ConstantKey<T> key) {
+ return null;
+ }
+
+ @Override
+ public Set<ConstantKey<?>> getKeys() {
+ return Collections.emptySet();
+ }
+ };
+ CacheKey<?> getCK = new CacheKey<Object>() {
+ @Override
+ public Object processorIdenfitier() {
+ return this;
+ }
+ };
+ GECacheKey getKey = new GECacheKey(getNC, getCK);
+
+
+ private void addKey(GECacheKey key) {
+ Set<GECacheKey> refs = keyRefs.get(key.context);
+ if (refs != null) {
+ refs.add(key);
+ } else {
+ refs = new HashSet<GECacheKey>();
+ refs.add(key);
+ keyRefs.put(key.context, refs);
+ }
+ }
+
+ private void removeKey(GECacheKey key) {
+ Set<GECacheKey> refs = keyRefs.get(key.context);
+ if (refs != null) {
+ refs.remove(key);
+ }
+ }
+
+ public <T> IGECacheEntry put(NodeContext context, CacheKey<T> 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> T get(NodeContext context, CacheKey<T> key) {
+ getKey.setValues(context, key);
+ IGECacheEntry entry = entries.get(getKey);
+ if (entry == null)
+ return null;
+ return (T) entry.getValue();
+ }
+
+ @Override
+ public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
+ assert(context != null);
+ assert(key != null);
+ getKey.setValues(context, key);
+ return entries.get(getKey);
+ }
+
+ @Override
+ public <T> void remove(NodeContext context, CacheKey<T> key) {
+// if (DEBUG) System.out.println("Remove entry " + context + " " + key);
+ getKey.setValues(context, key);
+ entries.remove(getKey);
+ removeKey(getKey);
+ }
+
+ @Override
+ public <T> Set<UIElementReference> getTreeReference(NodeContext context, CacheKey<T> key) {
+ assert(context != null);
+ assert(key != null);
+ getKey.setValues(context, key);
+ return treeReferences.get(getKey);
+ }
+
+ @Override
+ public <T> void putTreeReference(NodeContext context, CacheKey<T> key, UIElementReference reference) {
+ assert(context != null);
+ assert(key != null);
+ //if (DEBUG) System.out.println("Add tree reference " + context + " " + key);
+ getKey.setValues(context, key);
+ Set<UIElementReference> refs = treeReferences.get(getKey);
+ if (refs != null) {
+ refs.add(reference);
+ } else {
+ refs = new HashSet<UIElementReference>(4);
+ refs.add(reference);
+ GECacheKey gekey = new GECacheKey(getKey);
+ treeReferences.put(gekey, refs);
+ addKey(gekey);
+ }
+ }
+
+ @Override
+ public <T> Set<UIElementReference> removeTreeReference(NodeContext context, CacheKey<T> 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<NodeContext> references = new TObjectIntHashMap<NodeContext>();
+
+ @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<GECacheKey> 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 <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
+ return null;
+ }
+
+ @Override
+ public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key,
+ T value) {
+ return null;
+ }
+
+ @Override
+ public <T> void putTreeReference(NodeContext context, CacheKey<T> key,
+ UIElementReference reference) {
+ }
+
+ @Override
+ public <T> T get(NodeContext context, CacheKey<T> key) {
+ return null;
+ }
+
+ @Override
+ public <T> Set<UIElementReference> getTreeReference(
+ NodeContext context, CacheKey<T> key) {
+ return null;
+ }
+
+ @Override
+ public <T> void remove(NodeContext context, CacheKey<T> key) {
+
+ }
+
+ @Override
+ public <T> Set<UIElementReference> removeTreeReference(
+ NodeContext context, CacheKey<T> 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);
+ }
+}