package org.simantics.browsing.ui.nattable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiFunction; import java.util.function.Consumer; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.resource.ColorDescriptor; import org.eclipse.jface.resource.DeviceResourceException; import org.eclipse.jface.resource.DeviceResourceManager; import org.eclipse.jface.resource.FontDescriptor; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.LocalResourceManager; import org.eclipse.jface.viewers.ColumnWeightData; import org.eclipse.jface.viewers.ICellEditorValidator; import org.eclipse.jface.viewers.IPostSelectionProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.window.Window; import org.eclipse.nebula.widgets.nattable.NatTable; import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration; import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes; import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; import org.eclipse.nebula.widgets.nattable.config.IEditableRule; import org.eclipse.nebula.widgets.nattable.coordinate.Range; import org.eclipse.nebula.widgets.nattable.data.IDataProvider; import org.eclipse.nebula.widgets.nattable.data.ListDataProvider; import org.eclipse.nebula.widgets.nattable.data.convert.DefaultDisplayConverter; import org.eclipse.nebula.widgets.nattable.data.validate.IDataValidator; import org.eclipse.nebula.widgets.nattable.data.validate.ValidationFailedException; import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes; import org.eclipse.nebula.widgets.nattable.edit.EditConfigHelper; import org.eclipse.nebula.widgets.nattable.edit.ICellEditHandler; import org.eclipse.nebula.widgets.nattable.edit.config.DefaultEditConfiguration; import org.eclipse.nebula.widgets.nattable.edit.config.DialogErrorHandling; import org.eclipse.nebula.widgets.nattable.edit.editor.AbstractCellEditor; import org.eclipse.nebula.widgets.nattable.edit.editor.ComboBoxCellEditor; import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor; import org.eclipse.nebula.widgets.nattable.edit.editor.IEditErrorHandler; import org.eclipse.nebula.widgets.nattable.edit.editor.TextCellEditor; import org.eclipse.nebula.widgets.nattable.edit.gui.AbstractDialogCellEditor; import org.eclipse.nebula.widgets.nattable.grid.GridRegion; import org.eclipse.nebula.widgets.nattable.grid.cell.AlternatingRowConfigLabelAccumulator; import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider; import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider; import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer; import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer; import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultColumnHeaderDataLayer; import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer; import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer; import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer; import org.eclipse.nebula.widgets.nattable.hideshow.ColumnHideShowLayer; import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent; import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent; import org.eclipse.nebula.widgets.nattable.layer.DataLayer; import org.eclipse.nebula.widgets.nattable.layer.ILayerListener; import org.eclipse.nebula.widgets.nattable.layer.LabelStack; import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator; import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent; import org.eclipse.nebula.widgets.nattable.painter.NatTableBorderOverlayPainter; import org.eclipse.nebula.widgets.nattable.reorder.ColumnReorderLayer; import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum; import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration; import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes; import org.eclipse.nebula.widgets.nattable.style.DisplayMode; import org.eclipse.nebula.widgets.nattable.style.Style; import org.eclipse.nebula.widgets.nattable.ui.menu.AbstractHeaderMenuConfiguration; import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder; import org.eclipse.nebula.widgets.nattable.util.GUIHelper; import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer; import org.eclipse.nebula.widgets.nattable.widget.EditModeEnum; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.ScrollBar; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.contexts.IContextActivation; import org.eclipse.ui.contexts.IContextService; import org.eclipse.ui.services.IServiceLocator; import org.eclipse.ui.swt.IFocusService; import org.simantics.browsing.ui.BuiltinKeys; import org.simantics.browsing.ui.Column; import org.simantics.browsing.ui.Column.Align; import org.simantics.browsing.ui.DataSource; import org.simantics.browsing.ui.ExplorerState; import org.simantics.browsing.ui.GraphExplorer; import org.simantics.browsing.ui.NodeContext; import org.simantics.browsing.ui.NodeContext.CacheKey; import org.simantics.browsing.ui.NodeContext.PrimitiveQueryKey; import org.simantics.browsing.ui.NodeContext.QueryKey; import org.simantics.browsing.ui.NodeQueryManager; import org.simantics.browsing.ui.NodeQueryProcessor; import org.simantics.browsing.ui.PrimitiveQueryProcessor; import org.simantics.browsing.ui.PrimitiveQueryUpdater; import org.simantics.browsing.ui.SelectionDataResolver; import org.simantics.browsing.ui.SelectionFilter; import org.simantics.browsing.ui.StatePersistor; import org.simantics.browsing.ui.common.ColumnKeys; import org.simantics.browsing.ui.common.ErrorLogger; import org.simantics.browsing.ui.common.NodeContextBuilder; import org.simantics.browsing.ui.common.NodeContextUtil; import org.simantics.browsing.ui.common.internal.GENodeQueryManager; import org.simantics.browsing.ui.common.internal.IGECache; import org.simantics.browsing.ui.common.internal.IGraphExplorerContext; import org.simantics.browsing.ui.common.internal.UIElementReference; import org.simantics.browsing.ui.common.processors.AbstractPrimitiveQueryProcessor; import org.simantics.browsing.ui.common.processors.DefaultCheckedStateProcessor; import org.simantics.browsing.ui.common.processors.DefaultComparableChildrenProcessor; import org.simantics.browsing.ui.common.processors.DefaultFinalChildrenProcessor; import org.simantics.browsing.ui.common.processors.DefaultImageDecoratorProcessor; import org.simantics.browsing.ui.common.processors.DefaultImagerFactoriesProcessor; import org.simantics.browsing.ui.common.processors.DefaultImagerProcessor; import org.simantics.browsing.ui.common.processors.DefaultLabelDecoratorProcessor; import org.simantics.browsing.ui.common.processors.DefaultLabelerFactoriesProcessor; import org.simantics.browsing.ui.common.processors.DefaultLabelerProcessor; import org.simantics.browsing.ui.common.processors.DefaultPrunedChildrenProcessor; import org.simantics.browsing.ui.common.processors.DefaultSelectedImageDecoratorFactoriesProcessor; import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelDecoratorFactoriesProcessor; import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelerProcessor; import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointFactoryProcessor; import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointProcessor; import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionProcessor; import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionsProcessor; import org.simantics.browsing.ui.common.processors.DefaultViewpointProcessor; import org.simantics.browsing.ui.common.processors.IsExpandedProcessor; import org.simantics.browsing.ui.common.processors.NoSelectionRequestProcessor; import org.simantics.browsing.ui.common.processors.ProcessorLifecycle; import org.simantics.browsing.ui.content.Labeler; import org.simantics.browsing.ui.content.Labeler.CustomModifier; import org.simantics.browsing.ui.content.Labeler.DialogModifier; import org.simantics.browsing.ui.content.Labeler.EnumerationModifier; import org.simantics.browsing.ui.content.Labeler.Modifier; import org.simantics.browsing.ui.nattable.override.DefaultTreeLayerConfiguration2; import org.simantics.browsing.ui.swt.Activator; import org.simantics.browsing.ui.swt.AdaptableHintContext; import org.simantics.browsing.ui.swt.DefaultImageDecoratorsProcessor; import org.simantics.browsing.ui.swt.DefaultIsExpandedProcessor; import org.simantics.browsing.ui.swt.DefaultLabelDecoratorsProcessor; import org.simantics.browsing.ui.swt.DefaultSelectedImagerProcessor; import org.simantics.browsing.ui.swt.DefaultShowMaxChildrenProcessor; import org.simantics.browsing.ui.swt.GraphExplorerImplBase; import org.simantics.browsing.ui.swt.ImageLoaderJob; import org.simantics.browsing.ui.swt.ViewerCellReference; import org.simantics.browsing.ui.swt.ViewerRowReference; import org.simantics.browsing.ui.swt.internal.Threads; import org.simantics.db.layer0.SelectionHints; import org.simantics.utils.datastructures.MapList; import org.simantics.utils.datastructures.disposable.AbstractDisposable; import org.simantics.utils.datastructures.hints.IHintContext; import org.simantics.utils.threads.IThreadWorkQueue; import org.simantics.utils.threads.SWTThread; import org.simantics.utils.threads.ThreadUtils; import org.simantics.utils.ui.AdaptionUtils; import org.simantics.utils.ui.ISelectionUtils; import org.simantics.utils.ui.jface.BasePostSelectionProvider; import gnu.trove.map.hash.THashMap; import gnu.trove.map.hash.TObjectIntHashMap; /** * NatTable based GraphExplorer * * This GraphExplorer is not fully compatible with the other implementations, since it is not based on SWT.Tree. * * This implementation is useful in scenarios, where there are a lot of data to be displayed, the performance of NatTable is much better to SWT.Tree based implementations. * * * TODO: ability to hide headers * TODO: code cleanup (copied from GraphExplorerImpl2) * * @author Marko Luukkainen * */ public class NatTableGraphExplorer extends GraphExplorerImplBase implements GraphExplorer{ public static final int DEFAULT_MAX_CHILDREN = 10000; private static final boolean DEBUG_SELECTION_LISTENERS = false; private static final boolean DEBUG = false; private Composite composite; private NatTable natTable; private GETreeLayer treeLayer; private DataLayer dataLayer; private ViewportLayer viewportLayer; private SelectionLayer selectionLayer; private GEColumnHeaderDataProvider columnHeaderDataProvider; private GEColumnAccessor columnAccessor; private DefaultRowHeaderDataLayer rowHeaderDataLayer; private DataLayer columnHeaderDataLayer; private DataLayer cornerDataLayer; private List list = new ArrayList<>(); private NatTableSelectionAdaptor selectionAdaptor; private NatTableColumnLayout layout; LocalResourceManager localResourceManager; DeviceResourceManager resourceManager; private IThreadWorkQueue thread; @SuppressWarnings({ "rawtypes" }) final HashMap, NodeQueryProcessor> processors = new HashMap, NodeQueryProcessor>(); @SuppressWarnings({ "rawtypes" }) final HashMap primitiveProcessors = new HashMap(); @SuppressWarnings({ "rawtypes" }) final HashMap dataSources = new HashMap(); FontDescriptor originalFont; protected ColorDescriptor originalForeground; protected ColorDescriptor originalBackground; private Color invalidModificationColor; private Column[] columns; private Map columnKeyToIndex; private boolean columnsAreVisible = true; private NodeContext rootContext; private TreeNode rootNode; private StatePersistor persistor = null; private boolean editable = true; private boolean disposed = false; private final CopyOnWriteArrayList focusListeners = new CopyOnWriteArrayList(); private final CopyOnWriteArrayList mouseListeners = new CopyOnWriteArrayList(); private final CopyOnWriteArrayList keyListeners = new CopyOnWriteArrayList(); private int autoExpandLevel = 0; private IServiceLocator serviceLocator; private IContextService contextService = null; private IFocusService focusService = null; private IContextActivation editingContext = null; GeViewerContext explorerContext = new GeViewerContext(this); private GraphExplorerPostSelectionProvider postSelectionProvider = new GraphExplorerPostSelectionProvider(this); private BasePostSelectionProvider selectionProvider = new BasePostSelectionProvider(); private SelectionDataResolver selectionDataResolver; private SelectionFilter selectionFilter; private MapList contextToNodeMap; private ModificationContext modificationContext = null; private boolean filterSelectionEdit = true; private boolean expand; private boolean verticalBarVisible = false; private BiFunction selectionTransformation = new BiFunction() { @Override public Object[] apply(GraphExplorer explorer, Object[] objects) { Object[] result = new Object[objects.length]; for (int i = 0; i < objects.length; i++) { IHintContext context = new AdaptableHintContext(SelectionHints.KEY_MAIN); context.setHint(SelectionHints.KEY_MAIN, objects[i]); result[i] = context; } return result; } }; static class TransientStateImpl implements TransientExplorerState { private Integer activeColumn = null; @Override public synchronized Integer getActiveColumn() { return activeColumn; } public synchronized void setActiveColumn(Integer column) { activeColumn = column; } } private TransientStateImpl transientState = new TransientStateImpl(); public NatTableGraphExplorer(Composite parent) { this(parent, SWT.BORDER | SWT.MULTI ); } public NatTableGraphExplorer(Composite parent, int style) { this.composite = parent; this.localResourceManager = new LocalResourceManager(JFaceResources.getResources()); this.resourceManager = new DeviceResourceManager(parent.getDisplay()); this.imageLoaderJob = new ImageLoaderJob(this); this.imageLoaderJob.setPriority(Job.DECORATE); contextToNodeMap = new MapList(); invalidModificationColor = (Color) localResourceManager.get(ColorDescriptor.createFrom(new RGB(255, 128, 128))); this.thread = SWTThread.getThreadAccess(parent); for (int i = 0; i < 10; i++) explorerContext.activity.push(0); originalFont = JFaceResources.getDefaultFontDescriptor(); columns = new Column[0]; createNatTable(style); layout = new NatTableColumnLayout(natTable, columnHeaderDataProvider, rowHeaderDataLayer); this.composite.setLayout(layout); setBasicListeners(); setDefaultProcessors(); natTable.addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent e) { doDispose(); } }); Listener listener = new Listener() { @Override public void handleEvent(Event event) { switch (event.type) { case SWT.Activate: case SWT.Show: case SWT.Paint: { visible = true; activate(); break; } case SWT.Deactivate: case SWT.Hide: visible = false; } } }; natTable.addListener(SWT.Activate, listener); natTable.addListener(SWT.Deactivate, listener); natTable.addListener(SWT.Show, listener); natTable.addListener(SWT.Hide, listener); natTable.addListener(SWT.Paint,listener); setColumns( new Column[] { new Column(ColumnKeys.SINGLE) }); } private long focusGainedAt = 0L; private boolean visible = false; private Collection selectedNodes = new ArrayList(); protected void setBasicListeners() { natTable.addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent e) { focusGainedAt = ((long) e.time) & 0xFFFFFFFFL; for (FocusListener listener : focusListeners) listener.focusGained(e); } @Override public void focusLost(FocusEvent e) { for (FocusListener listener : focusListeners) listener.focusLost(e); } }); natTable.addMouseListener(new MouseListener() { @Override public void mouseDoubleClick(MouseEvent e) { for (MouseListener listener : mouseListeners) { listener.mouseDoubleClick(e); } } @Override public void mouseDown(MouseEvent e) { for (MouseListener listener : mouseListeners) { listener.mouseDown(e); } } @Override public void mouseUp(MouseEvent e) { for (MouseListener listener : mouseListeners) { listener.mouseUp(e); } } }); natTable.addKeyListener(new KeyListener() { @Override public void keyPressed(KeyEvent e) { for (KeyListener listener : keyListeners) { listener.keyPressed(e); } } @Override public void keyReleased(KeyEvent e) { for (KeyListener listener : keyListeners) { listener.keyReleased(e); } } }); selectionAdaptor.addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { //System.out.println("GraphExplorerImpl2.fireSelection"); selectedNodes = AdaptionUtils.adaptToCollection(event.getSelection(), TreeNode.class); Collection selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class); selectionProvider.setAndFireSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()]))); } }); selectionAdaptor.addPostSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { //System.out.println("GraphExplorerImpl2.firePostSelection"); Collection selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class); selectionProvider.firePostSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()]))); } }); } private NodeContext pendingRoot; private void activate() { if (pendingRoot != null && !expand) { doSetRoot(pendingRoot); pendingRoot = null; } } /** * Invoke only from SWT thread to reset the root of the graph explorer tree. * * @param root */ private void doSetRoot(NodeContext root) { Display display = composite.getDisplay(); if (display.getThread() != Thread.currentThread()) { throw new RuntimeException("Invoke from SWT thread only"); } // System.out.println("doSetRoot " + root); if (isDisposed()) return; if (natTable.isDisposed()) return; if (root.getConstant(BuiltinKeys.INPUT) == null) { ErrorLogger.defaultLogError("root node context does not contain BuiltinKeys.INPUT key. Node = " + root, new Exception("trace")); return; } // Empty caches, release queries. if (rootNode != null) { rootNode.dispose(); } GeViewerContext oldContext = explorerContext; GeViewerContext newContext = new GeViewerContext(this); this.explorerContext = newContext; oldContext.safeDispose(); // Need to empty these or otherwise they won't be emptied until the // explorer is disposed which would mean that many unwanted references // will be held by this map. clearPrimitiveProcessors(); this.rootContext = root.getConstant(BuiltinKeys.IS_ROOT) != null ? root : NodeContextUtil.withConstant(root, BuiltinKeys.IS_ROOT, Boolean.TRUE); explorerContext.getCache().incRef(this.rootContext); initializeState(); select(rootContext); //refreshColumnSizes(); rootNode = new TreeNode(rootContext, explorerContext); if (DEBUG) System.out.println("setRoot " + rootNode); // Delay content reading. // This is required for cases when GEImpl2 is attached to selection view. Reading content // instantly could stagnate SWT thread under rapid changes in selection. By delaying the // content reading we give the system a change to dispose the GEImpl2 before the content is read. display.asyncExec(new Runnable() { @Override public void run() { if (rootNode != null) { rootNode.updateChildren(); rootNode.setExpanded(true); listReIndex(); } } }); } private synchronized void listReIndex() { for (TreeNode n : list) { n.setListIndex(-2); } list.clear(); for (TreeNode c : rootNode.getChildren()) _insertToList(c); natTable.refresh(); } private void _insertToList(TreeNode n) { n.setListIndex(list.size()); list.add(n); for (TreeNode c : n.getChildren()) { _insertToList(c); } } public List getItems() { return Collections.unmodifiableList(list); } private void initializeState() { if (persistor == null) return; ExplorerState state = persistor.deserialize( Platform.getStateLocation(Activator.getDefault().getBundle()).toFile(), getRoot()); Object processor = getPrimitiveProcessor(BuiltinKeys.IS_EXPANDED); if (processor instanceof DefaultIsExpandedProcessor) { DefaultIsExpandedProcessor isExpandedProcessor = (DefaultIsExpandedProcessor)processor; for(NodeContext expanded : state.expandedNodes) { isExpandedProcessor.setExpanded(expanded, true); } } } @Override public NodeContext getRoot() { return rootContext; } @Override public IThreadWorkQueue getThread() { return thread; } @Override public NodeContext getParentContext(NodeContext context) { if (disposed) throw new IllegalStateException("disposed"); if (!thread.currentThreadAccess()) throw new IllegalStateException("not in SWT display thread " + thread.getThread()); List nodes = contextToNodeMap.getValuesUnsafe(context); for (int i = 0; i < nodes.size(); i++) { if (nodes.get(i).getParent() != null) return nodes.get(i).getParent().getContext(); } return null; } @SuppressWarnings("unchecked") @Override public T getAdapter(Class adapter) { if(ISelectionProvider.class == adapter) return (T) postSelectionProvider; else if(IPostSelectionProvider.class == adapter) return (T) postSelectionProvider; return null; } protected void setDefaultProcessors() { // Add a simple IMAGER query processor that always returns null. // With this processor no images will ever be shown. // setPrimitiveProcessor(new StaticImagerProcessor(null)); setProcessor(new DefaultComparableChildrenProcessor()); setProcessor(new DefaultLabelDecoratorsProcessor()); setProcessor(new DefaultImageDecoratorsProcessor()); setProcessor(new DefaultSelectedLabelerProcessor()); setProcessor(new DefaultLabelerFactoriesProcessor()); setProcessor(new DefaultSelectedImagerProcessor()); setProcessor(new DefaultImagerFactoriesProcessor()); setPrimitiveProcessor(new DefaultLabelerProcessor()); setPrimitiveProcessor(new DefaultCheckedStateProcessor()); setPrimitiveProcessor(new DefaultImagerProcessor()); setPrimitiveProcessor(new DefaultLabelDecoratorProcessor()); setPrimitiveProcessor(new DefaultImageDecoratorProcessor()); setPrimitiveProcessor(new NoSelectionRequestProcessor()); setProcessor(new DefaultFinalChildrenProcessor(this)); setProcessor(new DefaultPrunedChildrenProcessor()); setProcessor(new DefaultSelectedViewpointProcessor()); setProcessor(new DefaultSelectedLabelDecoratorFactoriesProcessor()); setProcessor(new DefaultSelectedImageDecoratorFactoriesProcessor()); setProcessor(new DefaultViewpointContributionsProcessor()); setPrimitiveProcessor(new DefaultViewpointProcessor()); setPrimitiveProcessor(new DefaultViewpointContributionProcessor()); setPrimitiveProcessor(new DefaultSelectedViewpointFactoryProcessor()); setPrimitiveProcessor(new TreeNodeIsExpandedProcessor()); setPrimitiveProcessor(new DefaultShowMaxChildrenProcessor()); } @Override public Column[] getColumns() { return Arrays.copyOf(columns, columns.length); } @Override public void setColumnsVisible(boolean visible) { columnsAreVisible = visible; //FIXME if(natTable != null) this.columnHeaderDataLayer.setHeaderVisible(columnsAreVisible); } @Override public void setColumns(final Column[] columns) { setColumns(columns, null); } @Override public void setColumns(final Column[] columns, Consumer> callback) { assertNotDisposed(); checkUniqueColumnKeys(columns); Display d = composite.getDisplay(); if (d.getThread() == Thread.currentThread()) { doSetColumns(columns, callback); natTable.refresh(true); }else d.asyncExec(new Runnable() { @Override public void run() { if (natTable == null) return; if (natTable.isDisposed()) return; doSetColumns(columns, callback); natTable.refresh(); natTable.getParent().layout(); } }); } private void checkUniqueColumnKeys(Column[] cols) { Set usedColumnKeys = new HashSet(); List duplicateColumns = new ArrayList(); for (Column c : cols) { if (!usedColumnKeys.add(c.getKey())) duplicateColumns.add(c); } if (!duplicateColumns.isEmpty()) { throw new IllegalArgumentException("All columns do not have unique keys: " + cols + ", overlapping: " + duplicateColumns); } } private void doSetColumns(Column[] cols, Consumer> callback) { HashMap keyToIndex = new HashMap(); for (int i = 0; i < cols.length; ++i) { keyToIndex.put(cols[i].getKey(), i); } this.columns = Arrays.copyOf(cols, cols.length); //this.columns[cols.length] = FILLER_COLUMN; this.columnKeyToIndex = keyToIndex; columnHeaderDataProvider.updateColumnSizes(); Map map = new HashMap(); // FIXME : temporary workaround for ModelBrowser. // natTable.setHeaderVisible(columns.length == 1 ? false : columnsAreVisible); int columnIndex = 0; for (Column column : columns) { int width = column.getWidth(); if(column.hasGrab()) { if (width < 0) width = 1; layout.setColumnData(columnIndex, new ColumnWeightData(column.getWeight(), width)); } else { if (width < 0) width = 50; layout.setColumnData(columnIndex, new ColumnWeightData(columns.length > 1 ? 0 : 1, width)); } columnIndex++; } if(callback != null) callback.accept(map); } int toSWT(Align alignment) { switch (alignment) { case LEFT: return SWT.LEFT; case CENTER: return SWT.CENTER; case RIGHT: return SWT.RIGHT; default: throw new Error("unhandled alignment: " + alignment); } } @Override public void setProcessor(NodeQueryProcessor processor) { assertNotDisposed(); if (processor == null) throw new IllegalArgumentException("null processor"); processors.put(processor.getIdentifier(), processor); } @Override public void setPrimitiveProcessor(PrimitiveQueryProcessor processor) { assertNotDisposed(); if (processor == null) throw new IllegalArgumentException("null processor"); PrimitiveQueryProcessor oldProcessor = primitiveProcessors.put( processor.getIdentifier(), processor); if (oldProcessor instanceof ProcessorLifecycle) ((ProcessorLifecycle) oldProcessor).detached(this); if (processor instanceof ProcessorLifecycle) ((ProcessorLifecycle) processor).attached(this); } @Override public void setDataSource(DataSource provider) { assertNotDisposed(); if (provider == null) throw new IllegalArgumentException("null provider"); dataSources.put(provider.getProvidedClass(), provider); } @SuppressWarnings("unchecked") @Override public DataSource removeDataSource(Class forProvidedClass) { assertNotDisposed(); if (forProvidedClass == null) throw new IllegalArgumentException("null class"); return dataSources.remove(forProvidedClass); } @Override public void setPersistor(StatePersistor persistor) { this.persistor = persistor; } @Override public SelectionDataResolver getSelectionDataResolver() { return selectionDataResolver; } @Override public void setSelectionDataResolver(SelectionDataResolver r) { this.selectionDataResolver = r; } @Override public SelectionFilter getSelectionFilter() { return selectionFilter; } @Override public void setSelectionFilter(SelectionFilter f) { this.selectionFilter = f; // TODO: re-filter current selection? } protected ISelection constructSelection(NodeContext... contexts) { if (contexts == null) throw new IllegalArgumentException("null contexts"); if (contexts.length == 0) return StructuredSelection.EMPTY; if (selectionFilter == null) return new StructuredSelection(transformSelection(contexts)); return new StructuredSelection( transformSelection(filter(selectionFilter, contexts)) ); } protected Object[] transformSelection(Object[] objects) { return selectionTransformation.apply(this, objects); } protected static Object[] filter(SelectionFilter filter, NodeContext[] contexts) { int len = contexts.length; Object[] objects = new Object[len]; for (int i = 0; i < len; ++i) objects[i] = filter.filter(contexts[i]); return objects; } @Override public void setSelectionTransformation( BiFunction f) { this.selectionTransformation = f; } public ISelection getWidgetSelection() { return selectionAdaptor.getSelection(); } @Override public void addListener(T listener) { if (listener instanceof FocusListener) { focusListeners.add((FocusListener) listener); } else if (listener instanceof MouseListener) { mouseListeners.add((MouseListener) listener); } else if (listener instanceof KeyListener) { keyListeners.add((KeyListener) listener); } } @Override public void removeListener(T listener) { if (listener instanceof FocusListener) { focusListeners.remove(listener); } else if (listener instanceof MouseListener) { mouseListeners.remove(listener); } else if (listener instanceof KeyListener) { keyListeners.remove(listener); } } public void addSelectionListener(SelectionListener listener) { selectionAdaptor.addSelectionListener(listener); } public void removeSelectionListener(SelectionListener listener) { selectionAdaptor.removeSelectionListener(listener); } private Set uiContexts; @Override public void setUIContexts(Set contexts) { this.uiContexts = contexts; } @Override public void setRoot(final Object root) { if(uiContexts != null && uiContexts.size() == 1) setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root, BuiltinKeys.UI_CONTEXT, uiContexts.iterator().next())); else setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root)); } @Override public void setRootContext(final NodeContext context) { setRootContext0(context); } private void setRoot(NodeContext context) { if (!visible) { pendingRoot = context; Display.getDefault().asyncExec(new Runnable() { @Override public void run() { if (natTable!= null && !natTable.isDisposed()) natTable.redraw(); } }); return; } doSetRoot(context); } private void setRootContext0(final NodeContext context) { Assert.isNotNull(context, "root must not be null"); if (isDisposed() || natTable.isDisposed()) return; Display display = natTable.getDisplay(); if (display.getThread() == Thread.currentThread()) { setRoot(context); } else { display.asyncExec(new Runnable() { @Override public void run() { setRoot(context); } }); } } @Override public void setFocus() { natTable.setFocus(); } @SuppressWarnings("unchecked") @Override public T getControl() { return (T)natTable; } @Override public boolean isDisposed() { return disposed; } protected void assertNotDisposed() { if (isDisposed()) throw new IllegalStateException("disposed"); } @Override public boolean isEditable() { return editable; } @Override public void setEditable(boolean editable) { if (!thread.currentThreadAccess()) throw new IllegalStateException("not in SWT display thread " + thread.getThread()); this.editable = editable; Display display = natTable.getDisplay(); natTable.setBackground(editable ? null : display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND)); } private void doDispose() { if (disposed) return; disposed = true; // TODO: Since GENodeQueryManager is cached in QueryChache and it refers to this class // we have to remove all references here to reduce memory consumption. // // Proper fix would be to remove references between QueryCache and GENodeQueryManagers. if (rootNode != null) { rootNode.dispose(); rootNode = null; } explorerContext.dispose(); explorerContext = null; processors.clear(); detachPrimitiveProcessors(); primitiveProcessors.clear(); dataSources.clear(); pendingItems.clear(); rootContext = null; mouseListeners.clear(); selectionProvider.clearListeners(); selectionProvider = null; selectionDataResolver = null; selectedNodes.clear(); selectedNodes = null; selectionTransformation = null; originalFont = null; localResourceManager.dispose(); localResourceManager = null; // Must shutdown image loader job before disposing its ResourceManager imageLoaderJob.dispose(); imageLoaderJob.cancel(); try { imageLoaderJob.join(); imageLoaderJob = null; } catch (InterruptedException e) { ErrorLogger.defaultLogError(e); } resourceManager.dispose(); resourceManager = null; contextToNodeMap.clear(); // should be empty at this point. contextToNodeMap = null; if (postSelectionProvider != null) { postSelectionProvider.dispose(); postSelectionProvider = null; } imageTasks = null; modificationContext = null; focusService = null; contextService = null; serviceLocator = null; columns = null; columnKeyToIndex.clear(); columnKeyToIndex = null; // if (natTable != null) { // natTable.dispose(); // natTable = null; // } treeLayer = null; dataLayer = null; viewportLayer = null; selectionLayer = null; columnHeaderDataProvider = null; columnAccessor = null; rowHeaderDataLayer = null; columnHeaderDataLayer = null; cornerDataLayer = null; } @Override public boolean select(NodeContext context) { assertNotDisposed(); if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) { StructuredSelection s = new StructuredSelection(); selectionAdaptor.setSelection(s); selectionProvider.setAndFireNonEqualSelection(s); return true; } selectionAdaptor.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0))); return false; } public boolean select(TreeNode node) { assertNotDisposed(); if (!list.contains(node)) { StructuredSelection s = new StructuredSelection(); selectionAdaptor.setSelection(s); selectionProvider.setAndFireNonEqualSelection(s); return true; } selectionAdaptor.setSelection(new StructuredSelection(node)); return false; } public void show(TreeNode node) { int index = node.getListIndex(); int position = treeLayer.getRowPositionByIndex(index); if (position < 0) { treeLayer.expandToTreeRow(index); position = treeLayer.getRowPositionByIndex(index); } viewportLayer.moveRowPositionIntoViewport(position); } @Override public boolean selectPath(Collection contexts) { if(contexts == null) throw new IllegalArgumentException("Null list is not allowed"); if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed"); return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0); } private boolean selectPathInternal(NodeContext[] contexts, int position) { NodeContext head = contexts[position]; if(position == contexts.length-1) { return select(head); } setExpanded(head, true); if(!waitVisible(contexts[position+1])) return false; return selectPathInternal(contexts, position+1); } private boolean waitVisible(NodeContext context) { long start = System.nanoTime(); while(!isVisible(context)) { Display.getCurrent().readAndDispatch(); long duration = System.nanoTime() - start; if(duration > 10e9) return false; } return true; } @Override public boolean isVisible(NodeContext context) { if (contextToNodeMap.getValuesUnsafe(context).size() == 0) return false; return true; //FIXME // Object elements[] = viewer.getVisibleExpandedElements(); // return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0)); } @Override public TransientExplorerState getTransientState() { if (!thread.currentThreadAccess()) throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread()); return transientState; } @Override public T query(NodeContext context, CacheKey key) { return this.explorerContext.cache.get(context, key); } /** * For setting a more local service locator for the explorer than the global * workbench service locator. Sometimes required to give this implementation * access to local workbench services like IFocusService. * *

* Must be invoked during right after construction. * * @param serviceLocator * a specific service locator or null to use the * workbench global service locator */ public void setServiceLocator(IServiceLocator serviceLocator) { if (serviceLocator == null && PlatformUI.isWorkbenchRunning()) serviceLocator = PlatformUI.getWorkbench(); this.serviceLocator = serviceLocator; if (serviceLocator != null) { this.contextService = (IContextService) serviceLocator.getService(IContextService.class); this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class); } } private void detachPrimitiveProcessors() { for (PrimitiveQueryProcessor p : primitiveProcessors.values()) { if (p instanceof ProcessorLifecycle) { ((ProcessorLifecycle) p).detached(this); } } } private void clearPrimitiveProcessors() { for (PrimitiveQueryProcessor p : primitiveProcessors.values()) { if (p instanceof ProcessorLifecycle) { ((ProcessorLifecycle) p).clear(); } } } @Override public void setExpanded(NodeContext context, boolean expanded) { for (TreeNode n : contextToNodeMap.getValues(context)) { if (expanded) treeLayer.expandTreeRow(n.getListIndex()); else treeLayer.collapseTreeRow(n.getListIndex()); } } @Override public void setAutoExpandLevel(int level) { this.autoExpandLevel = level; treeLayer.expandAllToLevel(level); } int maxChildren = DEFAULT_MAX_CHILDREN; @Override public int getMaxChildren() { return maxChildren; } @Override public void setMaxChildren(int maxChildren) { this.maxChildren = maxChildren; } @Override public int getMaxChildren(NodeQueryManager manager, NodeContext context) { Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN); //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result); if (result != null) { if (result < 0) throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result); return result; } return maxChildren; } @Override public NodeQueryProcessor getProcessor(QueryKey key) { return explorerContext.getProcessor(key); } @Override public PrimitiveQueryProcessor getPrimitiveProcessor(PrimitiveQueryKey key) { return explorerContext.getPrimitiveProcessor(key); } private HashSet pendingItems = new HashSet(); private boolean updating = false; private int updateCounter = 0; final ScheduledExecutorService uiUpdateScheduler = ThreadUtils.getNonBlockingWorkExecutor(); private class UpdateItem { TreeNode element; int columnIndex; public UpdateItem(TreeNode element) { this(element,-1); } public UpdateItem(TreeNode element, int columnIndex) { this.element = element; this.columnIndex = columnIndex; if (element != null && element.isDisposed()) { throw new IllegalArgumentException("Node is disposed. " + element); } } public void update(NatTable natTable) { if (element != null) { if (element.isDisposed()) { return; } if (element.updateChildren()) { if (DEBUG) { System.out.println("Update Item updateChildren " + element.listIndex + " " + element); printDebug(NatTableGraphExplorer.this); } listReIndex(); if (!element.isHidden()) { if (!element.isExpanded()) { if (element.listIndex >= 0) treeLayer.collapseTreeRow(element.listIndex); if (DEBUG) { System.out.println("Update Item collapse " + element.listIndex); printDebug(NatTableGraphExplorer.this); } } else { for (TreeNode c : element.getChildren()) c.initData(); } } else { TreeNode p = element.getCollapsedAncestor(); if (p != null) { if (element.listIndex >= 0) treeLayer.collapseTreeRow(element.listIndex); if (p.listIndex >= 0) treeLayer.collapseTreeRow(p.listIndex); if (DEBUG) { System.out.println("Update Item ancetor collapse " + p.listIndex); printDebug(NatTableGraphExplorer.this); } } } } else { // if (columnIndex >= 0) { // viewer.update(element, new String[]{columns[columnIndex].getKey()}); // } else { // viewer.refresh(element,true); // } element.initData(); natTable.redraw(); } if (!element.autoExpanded && !element.isDisposed() && autoExpandLevel > 1 && !element.isExpanded() && element.getDepth() <= autoExpandLevel) { expand = true; element.autoExpanded = true; element.initData(); if (DEBUG) System.out.println("Update Item expand " + element.listIndex); treeLayer.expandTreeRow(element.getListIndex()); //viewer.setExpandedState(element, true); expand = false; } } else { if (rootNode.updateChildren()) { listReIndex(); } } } @Override public boolean equals(Object obj) { if (obj == null) return false; if (obj.getClass() != getClass()) return false; UpdateItem other = (UpdateItem)obj; if (columnIndex != other.columnIndex) return false; if (element != null) return element.equals(other.element); return other.element == null; } @Override public int hashCode() { if (element != null) return element.hashCode() + columnIndex; return 0; } } private void update(final TreeNode element, final int columnIndex) { if (natTable.isDisposed()) return; if (element != null && element.isDisposed()) return; if (DEBUG) System.out.println("update " + element + " " + columnIndex); synchronized (pendingItems) { pendingItems.add(new UpdateItem(element, columnIndex)); if (updating) return; updateCounter++; scheduleUpdater(); } } private void update(final TreeNode element) { if (natTable.isDisposed()) return; if (element != null && element.isDisposed()) return; if (DEBUG) System.out.println("update " + element); synchronized (pendingItems) { pendingItems.add(new UpdateItem(element)); if (updating) return; updateCounter++; scheduleUpdater(); } } boolean scheduleUpdater() { if (natTable.isDisposed()) return false; if (!pendingItems.isEmpty()) { int activity = explorerContext.activityInt; long delay = 30; if (activity < 100) { //System.out.println("Scheduling update immediately."); } else if (activity < 1000) { //System.out.println("Scheduling update after 500ms."); delay = 500; } else { //System.out.println("Scheduling update after 3000ms."); delay = 3000; } updateCounter = 0; //System.out.println("Scheduling UI update after " + delay + " ms."); uiUpdateScheduler.schedule(new Runnable() { @Override public void run() { if (natTable == null || natTable.isDisposed()) return; if (updateCounter > 0) { updateCounter = 0; uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS); } else { natTable.getDisplay().asyncExec(new UpdateRunner(NatTableGraphExplorer.this, NatTableGraphExplorer.this.explorerContext)); } } }, delay, TimeUnit.MILLISECONDS); updating = true; return true; } return false; } @Override public String startEditing(NodeContext context, String columnKey) { assertNotDisposed(); if (!thread.currentThreadAccess()) throw new IllegalStateException("not in SWT display thread " + thread.getThread()); if(columnKey.startsWith("#")) { columnKey = columnKey.substring(1); } Integer columnIndex = columnKeyToIndex.get(columnKey); if (columnIndex == null) return "Rename not supported for selection"; // FIXME: // viewer.editElement(context, columnIndex); // if(viewer.isCellEditorActive()) return null; return "Rename not supported for selection"; } @Override public String startEditing(String columnKey) { ISelection selection = postSelectionProvider.getSelection(); if(selection == null) return "Rename not supported for selection"; NodeContext context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class); if(context == null) return "Rename not supported for selection"; return startEditing(context, columnKey); } public void setSelection(final ISelection selection, boolean forceControlUpdate) { assertNotDisposed(); boolean equalsOld = selectionProvider.selectionEquals(selection); if (equalsOld && !forceControlUpdate) { // Just set the selection object instance, fire no events nor update // the viewer selection. selectionProvider.setSelection(selection); } else { Collection coll = AdaptionUtils.adaptToCollection(selection, NodeContext.class); Collection nodes = new ArrayList(); for (NodeContext c : coll) { List match = contextToNodeMap.getValuesUnsafe(c); if(match.size() > 0) nodes.add(match.get(0)); } final ISelection sel = new StructuredSelection(nodes.toArray()); if (coll.size() == 0) return; // Schedule viewer and selection update if necessary. if (natTable.isDisposed()) return; Display d = natTable.getDisplay(); if (d.getThread() == Thread.currentThread()) { selectionAdaptor.setSelection(sel); } else { d.asyncExec(new Runnable() { @Override public void run() { if (natTable.isDisposed()) return; selectionAdaptor.setSelection(sel); } }); } } } @Override public void setModificationContext(ModificationContext modificationContext) { this.modificationContext = modificationContext; } final ExecutorService queryUpdateScheduler = Threads.getExecutor(); private double getDisplayScale() { Point dpi = Display.getCurrent().getDPI(); return (double)dpi.x/96.0; } private void createNatTable(int style) { GETreeData treeData = new GETreeData(list); GETreeRowModel treeRowModel = new GETreeRowModel(treeData); columnAccessor = new GEColumnAccessor(this); IDataProvider dataProvider = new ListDataProvider(list, columnAccessor); // FIXME: NatTable 1.0 required help to work with custom display scaling (Windows 7 display scaling). // It seems that NatTable 1.4 breaks with the same code in Windows 7, so now the code is disabled. // More testing with different hardware is required... // int defaultFontSize = 12; // int height = (int)Math.ceil(((double)(defaultFontSize))*getDisplayScale()) + DataLayer.DEFAULT_ROW_HEIGHT-defaultFontSize; // dataLayer = new DataLayer(dataProvider, DataLayer.DEFAULT_COLUMN_WIDTH, height); dataLayer = new DataLayer(dataProvider); // resizable rows are unnecessary in Sulca report. dataLayer.setRowsResizableByDefault(false); // Row header layer DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(dataProvider); rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider); // adjust row header column width so that row numbers fit into the column. //adjustRowHeaderWidth(list.size()); // Column header layer columnHeaderDataProvider = new GEColumnHeaderDataProvider(this, dataLayer); columnHeaderDataLayer = new DefaultColumnHeaderDataLayer(columnHeaderDataProvider); //columnHeaderDataLayer.setDefaultRowHeight(height); columnHeaderDataProvider.updateColumnSizes(); //ISortModel sortModel = new EcoSortModel(this, generator,dataLayer); // Column re-order + hide ColumnReorderLayer columnReorderLayer = new ColumnReorderLayer(dataLayer); ColumnHideShowLayer columnHideShowLayer = new ColumnHideShowLayer(columnReorderLayer); treeLayer = new GETreeLayer(columnHideShowLayer, treeRowModel, false); selectionLayer = new SelectionLayer(treeLayer); viewportLayer = new ViewportLayer(selectionLayer); ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, viewportLayer, selectionLayer); // Note: The column header layer is wrapped in a filter row composite. // This plugs in the filter row functionality ColumnOverrideLabelAccumulator labelAccumulator = new ColumnOverrideLabelAccumulator(columnHeaderDataLayer); columnHeaderDataLayer.setConfigLabelAccumulator(labelAccumulator); // Register labels //SortHeaderLayer sortHeaderLayer = new SortHeaderLayer(columnHeaderLayer, sortModel, false); RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, viewportLayer, selectionLayer); // Corner layer DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider); cornerDataLayer = new DataLayer(cornerDataProvider); //CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, sortHeaderLayer); CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, columnHeaderLayer); // Grid //GridLayer gridLayer = new GridLayer(viewportLayer,sortHeaderLayer,rowHeaderLayer, cornerLayer); GridLayer gridLayer = new GridLayer(viewportLayer, columnHeaderLayer,rowHeaderLayer, cornerLayer, false); /* Since 1.4.0, alternative row rendering uses row indexes in the original data list. When combined with collapsed tree rows, rows with odd or even index may end up next to each other, which defeats the purpose of alternating colors. This overrides that and returns the functionality that we had with 1.0.1. */ gridLayer.setConfigLabelAccumulatorForRegion(GridRegion.BODY, new RelativeAlternatingRowConfigLabelAccumulator()); gridLayer.addConfiguration(new DefaultEditConfiguration()); //gridLayer.addConfiguration(new DefaultEditBindings()); gridLayer.addConfiguration(new GEEditBindings()); natTable = new NatTable(composite,gridLayer,false); //selectionLayer.registerCommandHandler(new EcoCopyDataCommandHandler(selectionLayer,columnHeaderDataLayer,columnAccessor, columnHeaderDataProvider)); natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable)); natTable.addConfiguration(new DefaultTreeLayerConfiguration2(treeLayer)); natTable.addConfiguration(new SingleClickSortConfiguration()); //natTable.addLayerListener(this); natTable.addConfiguration(new GENatTableThemeConfiguration(treeData, style)); natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable)); natTable.addConfiguration(new AbstractRegistryConfiguration() { @Override public void configureRegistry(IConfigRegistry configRegistry) { configRegistry.registerConfigAttribute( EditConfigAttributes.CELL_EDITABLE_RULE, new IEditableRule() { @Override public boolean isEditable(ILayerCell cell, IConfigRegistry configRegistry) { int col = cell.getColumnIndex(); int row = cell.getRowIndex(); TreeNode node = list.get(row); Modifier modifier = getModifier(node,col); return modifier != null; } @Override public boolean isEditable(int columnIndex, int rowIndex) { // there are no callers? return false; } }); configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, new AdaptableCellEditor()); configRegistry.registerConfigAttribute(EditConfigAttributes.CONVERSION_ERROR_HANDLER, new DialogErrorHandling(), DisplayMode.EDIT); configRegistry.registerConfigAttribute(EditConfigAttributes.VALIDATION_ERROR_HANDLER, new DialogErrorHandling(), DisplayMode.EDIT); configRegistry.registerConfigAttribute(EditConfigAttributes.DATA_VALIDATOR, new AdaptableDataValidator(),DisplayMode.EDIT); Style conversionErrorStyle = new Style(); conversionErrorStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, GUIHelper.COLOR_RED); conversionErrorStyle.setAttributeValue(CellStyleAttributes.FOREGROUND_COLOR, GUIHelper.COLOR_WHITE); configRegistry.registerConfigAttribute(EditConfigAttributes.CONVERSION_ERROR_STYLE, conversionErrorStyle, DisplayMode.EDIT); configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, new DefaultDisplayConverter(),DisplayMode.EDIT); } }); if ((style & SWT.BORDER) > 0) { natTable.addOverlayPainter(new NatTableBorderOverlayPainter()); } natTable.configure(); // natTable.addListener(SWT.MenuDetect, new NatTableMenuListener()); // DefaultToolTip toolTip = new EcoCellToolTip(natTable, columnAccessor); // toolTip.setBackgroundColor(natTable.getDisplay().getSystemColor(SWT.COLOR_WHITE)); // toolTip.setPopupDelay(500); // toolTip.activate(); // toolTip.setShift(new Point(10, 10)); // menuManager.createContextMenu(composite); // natTable.setMenu(getMenuManager().getMenu()); selectionAdaptor = new NatTableSelectionAdaptor(natTable, selectionLayer, treeData); } Modifier getModifier(TreeNode element, int columnIndex) { GENodeQueryManager manager = element.getManager(); final NodeContext context = element.getContext(); Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER); if (labeler == null) return null; Column column = columns[columnIndex]; return labeler.getModifier(modificationContext, column.getKey()); } private class AdaptableCellEditor implements ICellEditor { ICellEditor editor; @Override public Control activateCell(Composite parent, Object originalCanonicalValue, EditModeEnum editMode, ICellEditHandler editHandler, ILayerCell cell, IConfigRegistry configRegistry) { int col = cell.getColumnIndex(); int row = cell.getRowIndex(); TreeNode node = list.get(row); Modifier modifier = getModifier(node, col); if (modifier == null) return null; editor = null; if (modifier instanceof DialogModifier) { DialogModifier mod = (DialogModifier)modifier; editor = new DialogCellEditor(node, col, mod); } else if (modifier instanceof CustomModifier) { CustomModifier mod = (CustomModifier)modifier; editor = new CustomCellEditor(node, col, mod); } else if (modifier instanceof EnumerationModifier) { EnumerationModifier mod = (EnumerationModifier)modifier; editor = new ComboBoxCellEditor(mod.getValues()); } else { editor = new TextCellEditor(); } return editor.activateCell(parent, originalCanonicalValue, editMode, editHandler, cell, configRegistry); } @Override public int getColumnIndex() { return editor.getColumnIndex(); } @Override public int getRowIndex() { return editor.getRowIndex(); } @Override public int getColumnPosition() { return editor.getColumnPosition(); } @Override public int getRowPosition() { return editor.getRowPosition(); } @Override public Object getEditorValue() { return editor.getEditorValue(); } @Override public void setEditorValue(Object value) { editor.setEditorValue(value); } @Override public Object getCanonicalValue() { return editor.getCanonicalValue(); } @Override public Object getCanonicalValue(IEditErrorHandler conversionErrorHandler) { return editor.getCanonicalValue(); } @Override public void setCanonicalValue(Object canonicalValue) { editor.setCanonicalValue(canonicalValue); } @Override public boolean validateCanonicalValue(Object canonicalValue) { return editor.validateCanonicalValue(canonicalValue); } @Override public boolean validateCanonicalValue(Object canonicalValue, IEditErrorHandler validationErrorHandler) { return editor.validateCanonicalValue(canonicalValue, validationErrorHandler); } @Override public boolean commit(MoveDirectionEnum direction) { return editor.commit(direction); } @Override public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit) { return editor.commit(direction, closeAfterCommit); } @Override public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit, boolean skipValidation) { return editor.commit(direction, closeAfterCommit, skipValidation); } @Override public void close() { editor.close(); } @Override public boolean isClosed() { return editor.isClosed(); } @Override public Control getEditorControl() { return editor.getEditorControl(); } @Override public Control createEditorControl(Composite parent) { return editor.createEditorControl(parent); } @Override public boolean openInline(IConfigRegistry configRegistry, List configLabels) { return EditConfigHelper.openInline(configRegistry, configLabels); } @Override public boolean supportMultiEdit(IConfigRegistry configRegistry, List configLabels) { return editor.supportMultiEdit(configRegistry, configLabels); } @Override public boolean openMultiEditDialog() { return editor.openMultiEditDialog(); } @Override public boolean openAdjacentEditor() { return editor.openAdjacentEditor(); } @Override public boolean activateAtAnyPosition() { return true; } @Override public boolean activateOnTraversal(IConfigRegistry configRegistry, List configLabels) { return editor.activateOnTraversal(configRegistry, configLabels); } @Override public void addEditorControlListeners() { editor.addEditorControlListeners(); } @Override public void removeEditorControlListeners() { editor.removeEditorControlListeners(); } @Override public Rectangle calculateControlBounds(Rectangle cellBounds) { return editor.calculateControlBounds(cellBounds); } } private class AdaptableDataValidator implements IDataValidator { @Override public boolean validate(ILayerCell cell, IConfigRegistry configRegistry, Object newValue) { int col = cell.getColumnIndex(); int row = cell.getRowIndex(); return validate(col, row, newValue); } @Override public boolean validate(int col, int row, Object newValue) { TreeNode node = list.get(row); Modifier modifier = getModifier(node, col); if (modifier == null) return false; String err = modifier.isValid(newValue != null ? newValue.toString() : ""); if (err == null) return true; throw new ValidationFailedException(err); } } private class CustomCellEditor extends AbstractCellEditor { TreeNode node; CustomModifier customModifier; Control control; int column; public CustomCellEditor(TreeNode node, int column, CustomModifier customModifier) { this.customModifier = customModifier; this.node = node; this.column = column; } @Override public Object getEditorValue() { return customModifier.getValue(); } @Override public void setEditorValue(Object value) { customModifier.modify(value.toString()); } @Override public Control getEditorControl() { return control; } @Override public Control createEditorControl(Composite parent) { return (Control)customModifier.createControl(parent, null, column, node.getContext()); } @Override protected Control activateCell(Composite parent, Object originalCanonicalValue) { this.control = createEditorControl(parent); return control; } } private class DialogCellEditor extends AbstractDialogCellEditor { TreeNode node; DialogModifier dialogModifier; int column; String res = null; Semaphore sem; boolean closed = false; public DialogCellEditor(TreeNode node, int column, DialogModifier dialogModifier) { this.dialogModifier = dialogModifier; this.node = node; this.column = column; } @Override public int open() { sem = new Semaphore(1); Consumer callback = result -> { res = result; sem.release(); }; String status = dialogModifier.query(this.parent.getShell(), null, column, node.getContext(), callback); if (status != null) { closed = true; return Window.CANCEL; } try { sem.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } closed = true; return Window.OK; } @Override public DialogModifier createDialogInstance() { closed = false; return dialogModifier; } @Override public Object getDialogInstance() { return (DialogModifier)this.dialog; } @Override public void close() { } @Override public Object getEditorValue() { return null; } @Override public void setEditorValue(Object value) { // dialog modifier handles this internally } @Override public boolean isClosed() { return closed; } } /** * The job that is used for off-loading image loading tasks (see * {@link ImageTask} to a worker thread from the main UI thread. */ ImageLoaderJob imageLoaderJob; // Map imageTasks = new THashMap(); Map imageTasks = new THashMap(); void queueImageTask(TreeNode node, ImageTask task) { synchronized (imageTasks) { imageTasks.put(node, task); } imageLoaderJob.scheduleIfNecessary(100); } /** * Invoked in a job worker thread. * * @param monitor */ @Override protected IStatus setPendingImages(IProgressMonitor monitor) { ImageTask[] tasks = null; synchronized (imageTasks) { tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]); imageTasks.clear(); } MultiStatus status = null; // Load missing images for (ImageTask task : tasks) { Object desc = task.descsOrImage; if (desc instanceof ImageDescriptor) { try { desc = resourceManager.get((ImageDescriptor) desc); task.descsOrImage = desc; } catch (DeviceResourceException e) { if (status == null) status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null); status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e)); } } } // Perform final UI updates in the UI thread. final ImageTask[] _tasks = tasks; thread.asyncExec(new Runnable() { @Override public void run() { setImages(_tasks); } }); return status != null ? status : Status.OK_STATUS; } void setImages(ImageTask[] tasks) { for (ImageTask task : tasks) if (task != null) setImage(task); } void setImage(ImageTask task) { if (!task.node.isDisposed()) update(task.node, 0); } private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider { private NatTableGraphExplorer ge; GraphExplorerPostSelectionProvider(NatTableGraphExplorer ge) { this.ge = ge; } void dispose() { ge = null; } @Override public void setSelection(final ISelection selection) { if(ge == null) return; ge.setSelection(selection, false); } @Override public void removeSelectionChangedListener(ISelectionChangedListener listener) { if(ge == null) return; if(ge.isDisposed()) { if (DEBUG_SELECTION_LISTENERS) System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener); return; } ge.selectionProvider.removeSelectionChangedListener(listener); } @Override public void addPostSelectionChangedListener(ISelectionChangedListener listener) { if(ge == null) return; if (!ge.thread.currentThreadAccess()) throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread()); if(ge.isDisposed()) { System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener); return; } ge.selectionProvider.addPostSelectionChangedListener(listener); } @Override public void removePostSelectionChangedListener(ISelectionChangedListener listener) { if(ge == null) return; if(ge.isDisposed()) { if (DEBUG_SELECTION_LISTENERS) System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener); return; } ge.selectionProvider.removePostSelectionChangedListener(listener); } @Override public void addSelectionChangedListener(ISelectionChangedListener listener) { if(ge == null) return; if (!ge.thread.currentThreadAccess()) throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread()); if (ge.natTable.isDisposed() || ge.selectionProvider == null) { System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener); return; } ge.selectionProvider.addSelectionChangedListener(listener); } @Override public ISelection getSelection() { if(ge == null) return StructuredSelection.EMPTY; if (!ge.thread.currentThreadAccess()) throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread()); if (ge.natTable.isDisposed() || ge.selectionProvider == null) return StructuredSelection.EMPTY; return ge.selectionProvider.getSelection(); } } static class ModifierValidator implements ICellEditorValidator { private Modifier modifier; public ModifierValidator(Modifier modifier) { this.modifier = modifier; } @Override public String isValid(Object value) { return modifier.isValid((String)value); } } static class UpdateRunner implements Runnable { final NatTableGraphExplorer ge; UpdateRunner(NatTableGraphExplorer ge, IGraphExplorerContext geContext) { this.ge = ge; } public void run() { try { doRun(); } catch (Throwable t) { t.printStackTrace(); } } public void doRun() { if (ge.isDisposed()) return; HashSet items; ScrollBar verticalBar = ge.natTable.getVerticalBar(); synchronized (ge.pendingItems) { items = ge.pendingItems; ge.pendingItems = new HashSet(); } if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size()); //ge.natTable.setRedraw(false); for (UpdateItem item : items) { item.update(ge.natTable); } // check if vertical scroll bar has become visible and refresh layout. boolean currentlyVerticalBarVisible = verticalBar.isVisible(); if (ge.verticalBarVisible != currentlyVerticalBarVisible) { ge.verticalBarVisible = currentlyVerticalBarVisible; ge.natTable.getParent().layout(); } //ge.natTable.setRedraw(true); synchronized (ge.pendingItems) { if (!ge.scheduleUpdater()) { ge.updating = false; } } if (DEBUG) { if (!ge.updating) { printDebug(ge); } } } } private static void printDebug(NatTableGraphExplorer ge) { ge.printTree(ge.rootNode, 0); System.out.println("Expanded"); for (TreeNode n : ge.treeLayer.expanded) System.out.println(n); System.out.println("Expanded end"); System.out.println("Hidden "); for (int i : ge.treeLayer.getHiddenRowIndexes()) { System.out.print(i + " "); } System.out.println(); // Display.getCurrent().timerExec(1000, new Runnable() { // // @Override // public void run() { // System.out.println("Hidden delayed "); // for (int i : ge.treeLayer.getHiddenRowIndexes()) { // System.out.print(i + " "); // } // System.out.println(); // } // }); } public static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext { // This is for query debugging only. private NatTableGraphExplorer ge; int queryIndent = 0; GECache2 cache = new GECache2(); AtomicBoolean propagating = new AtomicBoolean(false); Object propagateList = new Object(); Object propagate = new Object(); List scheduleList = new ArrayList(); final Deque activity = new LinkedList(); int activityInt = 0; AtomicReference currentQueryUpdater = new AtomicReference(); /** * Keeps track of nodes that have already been auto-expanded. After * being inserted into this set, nodes will not be forced to stay in an * expanded state after that. This makes it possible for the user to * close auto-expanded nodes. */ Map autoExpanded = new WeakHashMap(); public GeViewerContext(NatTableGraphExplorer ge) { this.ge = ge; } public MapList getContextToNodeMap() { if (ge == null) return null; return ge.contextToNodeMap; } public NatTableGraphExplorer getGe() { return ge; } @Override protected void doDispose() { //saveState(); autoExpanded.clear(); } @Override public IGECache getCache() { return cache; } @Override public int queryIndent() { return queryIndent; } @Override public int queryIndent(int offset) { queryIndent += offset; return queryIndent; } @Override @SuppressWarnings("unchecked") public NodeQueryProcessor getProcessor(Object o) { if (ge == null) return null; return ge.processors.get(o); } @Override @SuppressWarnings("unchecked") public PrimitiveQueryProcessor getPrimitiveProcessor(Object o) { return ge.primitiveProcessors.get(o); } @SuppressWarnings("unchecked") @Override public DataSource getDataSource(Class clazz) { return ge.dataSources.get(clazz); } @Override public void update(UIElementReference ref) { if (ref instanceof ViewerCellReference) { ViewerCellReference tiref = (ViewerCellReference) ref; Object element = tiref.getElement(); int columnIndex = tiref.getColumn(); // NOTE: must be called regardless of the the item value. // A null item is currently used to indicate a tree root update. ge.update((TreeNode)element,columnIndex); } else if (ref instanceof ViewerRowReference) { ViewerRowReference rref = (ViewerRowReference)ref; Object element = rref.getElement(); ge.update((TreeNode)element); } else { throw new IllegalArgumentException("Ui Reference is unknkown " + ref); } } @Override public Object getPropagateLock() { return propagate; } @Override public Object getPropagateListLock() { return propagateList; } @Override public boolean isPropagating() { return propagating.get(); } @Override public void setPropagating(boolean b) { this.propagating.set(b); } @Override public List getScheduleList() { return scheduleList; } @Override public void setScheduleList(List list) { this.scheduleList = list; } @Override public Deque getActivity() { return activity; } @Override public void setActivityInt(int i) { this.activityInt = i; } @Override public int getActivityInt() { return activityInt; } @Override public void scheduleQueryUpdate(Runnable r) { if (ge == null) return; if (ge.isDisposed()) return; if (currentQueryUpdater.compareAndSet(null, r)) { ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER); } } Runnable QUERY_UPDATE_SCHEDULER = new Runnable() { @Override public void run() { Runnable r = currentQueryUpdater.getAndSet(null); if (r != null) { r.run(); } } }; @Override public void dispose() { cache.dispose(); cache = new DummyCache(); scheduleList.clear(); autoExpanded.clear(); autoExpanded = null; ge = null; } } private class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor implements IsExpandedProcessor, ProcessorLifecycle { /** * The set of currently expanded node contexts. */ private final HashSet expanded = new HashSet(); private final HashMap expandedQueries = new HashMap(); private NatTable natTable; private List list; public TreeNodeIsExpandedProcessor() { } @Override public Object getIdentifier() { return BuiltinKeys.IS_EXPANDED; } @Override public String toString() { return "IsExpandedProcessor"; } @Override public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey key) { boolean isExpanded = expanded.contains(context); expandedQueries.put(context, updater); return Boolean.valueOf(isExpanded); } @Override public Collection getExpanded() { return new HashSet(expanded); } @Override public boolean getExpanded(NodeContext context) { return this.expanded.contains(context); } @Override public boolean setExpanded(NodeContext context, boolean expanded) { return _setExpanded(context, expanded); } @Override public boolean replaceExpanded(NodeContext context, boolean expanded) { return nodeStatusChanged(context, expanded); } private boolean _setExpanded(NodeContext context, boolean expanded) { if (expanded) { return this.expanded.add(context); } else { return this.expanded.remove(context); } } ILayerListener treeListener = new ILayerListener() { @Override public void handleLayerEvent(ILayerEvent event) { if (event instanceof ShowRowPositionsEvent) { ShowRowPositionsEvent e = (ShowRowPositionsEvent)event; for (Range r : e.getRowPositionRanges()) { int expanded = viewportLayer.getRowIndexByPosition(r.start-2)+1; if (DEBUG)System.out.println("IsExpandedProcessor expand " + expanded); if (expanded < 0 || expanded >= list.size()) { return; } nodeStatusChanged(list.get(expanded).getContext(), true); } } else if (event instanceof HideRowPositionsEvent) { HideRowPositionsEvent e = (HideRowPositionsEvent)event; for (Range r : e.getRowPositionRanges()) { int collapsed = viewportLayer.getRowIndexByPosition(r.start-2); if (DEBUG)System.out.println("IsExpandedProcessor collapse " + collapsed); if (collapsed < 0 || collapsed >= list.size()) { return; } nodeStatusChanged(list.get(collapsed).getContext(), false); } } } }; protected boolean nodeStatusChanged(NodeContext context, boolean expanded) { boolean result = _setExpanded(context, expanded); PrimitiveQueryUpdater updater = expandedQueries.get(context); if (updater != null) updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded); return result; } @Override public void attached(GraphExplorer explorer) { Object control = explorer.getControl(); if (control instanceof NatTable) { this.natTable = (NatTable) control; this.list = ((NatTableGraphExplorer)explorer).list; natTable.addLayerListener(treeListener); } else { System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control); } } @Override public void clear() { expanded.clear(); expandedQueries.clear(); } @Override public void detached(GraphExplorer explorer) { clear(); if (natTable != null) { natTable.removeLayerListener(treeListener); // natTable.removeListener(SWT.Expand, treeListener); // natTable.removeListener(SWT.Collapse, treeListener); natTable = null; } } } private void printTree(TreeNode node, int depth) { String s = ""; for (int i = 0; i < depth; i++) { s += " "; } s += node; System.out.println(s); int d = depth+1; for (TreeNode n : node.getChildren()) { printTree(n, d); } } /** * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used) */ final private static class GECacheKey { private NodeContext context; private CacheKey key; GECacheKey(NodeContext context, CacheKey key) { this.context = context; this.key = key; if (context == null || key == null) throw new IllegalArgumentException("Null context or key is not accepted"); } GECacheKey(GECacheKey other) { this.context = other.context; this.key = other.key; if (context == null || key == null) throw new IllegalArgumentException("Null context or key is not accepted"); } void setValues(NodeContext context, CacheKey key) { this.context = context; this.key = key; if (context == null || key == null) throw new IllegalArgumentException("Null context or key is not accepted"); } @Override public int hashCode() { return context.hashCode() | key.hashCode(); } @Override public boolean equals(Object object) { if (this == object) return true; else if (object == null) return false; GECacheKey i = (GECacheKey) object; return key.equals(i.key) && context.equals(i.context); } }; /** * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data. */ public static class GECache2 implements IGECache { final HashMap entries = new HashMap(); final HashMap> treeReferences = new HashMap>(); final HashMap> keyRefs = new HashMap>(); /** * This single instance is used for all get operations from the cache. This * should work since the GE cache is meant to be single-threaded within the * current UI thread, what ever that thread is. For put operations which * store the key, this is not used. */ NodeContext getNC = new NodeContext() { @SuppressWarnings("rawtypes") @Override public Object getAdapter(Class adapter) { return null; } @Override public T getConstant(ConstantKey key) { return null; } @Override public Set> getKeys() { return Collections.emptySet(); } }; CacheKey getCK = new CacheKey() { @Override public Object processorIdenfitier() { return this; } }; GECacheKey getKey = new GECacheKey(getNC, getCK); private void addKey(GECacheKey key) { Set refs = keyRefs.get(key.context); if (refs != null) { refs.add(key); } else { refs = new HashSet(); refs.add(key); keyRefs.put(key.context, refs); } } private void removeKey(GECacheKey key) { Set refs = keyRefs.get(key.context); if (refs != null) { refs.remove(key); } } public IGECacheEntry put(NodeContext context, CacheKey key, T value) { // if (DEBUG) System.out.println("Add entry " + context + " " + key); IGECacheEntry entry = new GECacheEntry(context, key, value); GECacheKey gekey = new GECacheKey(context, key); entries.put(gekey, entry); addKey(gekey); return entry; } @SuppressWarnings("unchecked") public T get(NodeContext context, CacheKey key) { getKey.setValues(context, key); IGECacheEntry entry = entries.get(getKey); if (entry == null) return null; return (T) entry.getValue(); } @Override public IGECacheEntry getEntry(NodeContext context, CacheKey key) { assert(context != null); assert(key != null); getKey.setValues(context, key); return entries.get(getKey); } @Override public void remove(NodeContext context, CacheKey key) { // if (DEBUG) System.out.println("Remove entry " + context + " " + key); getKey.setValues(context, key); entries.remove(getKey); removeKey(getKey); } @Override public Set getTreeReference(NodeContext context, CacheKey key) { assert(context != null); assert(key != null); getKey.setValues(context, key); return treeReferences.get(getKey); } @Override public void putTreeReference(NodeContext context, CacheKey key, UIElementReference reference) { assert(context != null); assert(key != null); //if (DEBUG) System.out.println("Add tree reference " + context + " " + key); getKey.setValues(context, key); Set refs = treeReferences.get(getKey); if (refs != null) { refs.add(reference); } else { refs = new HashSet(4); refs.add(reference); GECacheKey gekey = new GECacheKey(getKey); treeReferences.put(gekey, refs); addKey(gekey); } } @Override public Set removeTreeReference(NodeContext context, CacheKey key) { assert(context != null); assert(key != null); //if (DEBUG) System.out.println("Remove tree reference " + context + " " + key); getKey.setValues(context, key); removeKey(getKey); return treeReferences.remove(getKey); } @Override public boolean isShown(NodeContext context) { return references.get(context) > 0; } private TObjectIntHashMap references = new TObjectIntHashMap(); @Override public void incRef(NodeContext context) { int exist = references.get(context); references.put(context, exist+1); } @Override public void decRef(NodeContext context) { int exist = references.get(context); references.put(context, exist-1); if(exist == 1) { references.remove(context); } } public void dispose() { references.clear(); entries.clear(); treeReferences.clear(); keyRefs.clear(); } public void dispose(NodeContext context) { Set keys = keyRefs.remove(context); if (keys != null) { for (GECacheKey key : keys) { entries.remove(key); treeReferences.remove(key); } } } } /** * Non-functional cache to replace actual cache when GEContext is disposed. * * @author mlmarko * */ private static class DummyCache extends GECache2 { @Override public IGECacheEntry getEntry(NodeContext context, CacheKey key) { return null; } @Override public IGECacheEntry put(NodeContext context, CacheKey key, T value) { return null; } @Override public void putTreeReference(NodeContext context, CacheKey key, UIElementReference reference) { } @Override public T get(NodeContext context, CacheKey key) { return null; } @Override public Set getTreeReference( NodeContext context, CacheKey key) { return null; } @Override public void remove(NodeContext context, CacheKey key) { } @Override public Set removeTreeReference( NodeContext context, CacheKey key) { return null; } @Override public boolean isShown(NodeContext context) { return false; } @Override public void incRef(NodeContext context) { } @Override public void decRef(NodeContext context) { } @Override public void dispose() { super.dispose(); } } private class NatTableHeaderMenuConfiguration extends AbstractHeaderMenuConfiguration { public NatTableHeaderMenuConfiguration(NatTable natTable) { super(natTable); } @Override protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) { return super.createColumnHeaderMenu(natTable) .withHideColumnMenuItem() .withShowAllColumnsMenuItem() .withAutoResizeSelectedColumnsMenuItem(); } @Override protected PopupMenuBuilder createCornerMenu(NatTable natTable) { return super.createCornerMenu(natTable) .withShowAllColumnsMenuItem(); } @Override protected PopupMenuBuilder createRowHeaderMenu(NatTable natTable) { return super.createRowHeaderMenu(natTable); } } private static class RelativeAlternatingRowConfigLabelAccumulator extends AlternatingRowConfigLabelAccumulator { @Override public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) { configLabels.addLabel((rowPosition % 2 == 0 ? EVEN_ROW_CONFIG_TYPE : ODD_ROW_CONFIG_TYPE)); } } @Override public Object getClicked(Object event) { MouseEvent e = (MouseEvent)event; final NatTable tree = (NatTable) e.getSource(); Point point = new Point(e.x, e.y); int y = natTable.getRowPositionByY(point.y); int x = natTable.getColumnPositionByX(point.x); if (x < 0 | y <= 0) return null; return list.get(y-1); } }