1 package org.simantics.browsing.ui.nattable;
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.Collection;
6 import java.util.Collections;
7 import java.util.Deque;
8 import java.util.HashMap;
9 import java.util.HashSet;
10 import java.util.LinkedList;
11 import java.util.List;
14 import java.util.WeakHashMap;
15 import java.util.concurrent.CopyOnWriteArrayList;
16 import java.util.concurrent.ExecutorService;
17 import java.util.concurrent.ScheduledExecutorService;
18 import java.util.concurrent.Semaphore;
19 import java.util.concurrent.TimeUnit;
20 import java.util.concurrent.atomic.AtomicBoolean;
21 import java.util.concurrent.atomic.AtomicReference;
22 import java.util.function.BiFunction;
23 import java.util.function.Consumer;
25 import org.eclipse.core.runtime.Assert;
26 import org.eclipse.core.runtime.IProgressMonitor;
27 import org.eclipse.core.runtime.IStatus;
28 import org.eclipse.core.runtime.MultiStatus;
29 import org.eclipse.core.runtime.Status;
30 import org.eclipse.core.runtime.jobs.Job;
31 import org.eclipse.jface.resource.ColorDescriptor;
32 import org.eclipse.jface.resource.DeviceResourceException;
33 import org.eclipse.jface.resource.DeviceResourceManager;
34 import org.eclipse.jface.resource.FontDescriptor;
35 import org.eclipse.jface.resource.ImageDescriptor;
36 import org.eclipse.jface.resource.JFaceResources;
37 import org.eclipse.jface.resource.LocalResourceManager;
38 import org.eclipse.jface.viewers.ColumnWeightData;
39 import org.eclipse.jface.viewers.ICellEditorValidator;
40 import org.eclipse.jface.viewers.IPostSelectionProvider;
41 import org.eclipse.jface.viewers.ISelection;
42 import org.eclipse.jface.viewers.ISelectionChangedListener;
43 import org.eclipse.jface.viewers.ISelectionProvider;
44 import org.eclipse.jface.viewers.SelectionChangedEvent;
45 import org.eclipse.jface.viewers.StructuredSelection;
46 import org.eclipse.jface.window.Window;
47 import org.eclipse.nebula.widgets.nattable.NatTable;
48 import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;
49 import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
50 import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
51 import org.eclipse.nebula.widgets.nattable.config.IEditableRule;
52 import org.eclipse.nebula.widgets.nattable.coordinate.Range;
53 import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
54 import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
55 import org.eclipse.nebula.widgets.nattable.data.convert.DefaultDisplayConverter;
56 import org.eclipse.nebula.widgets.nattable.data.validate.IDataValidator;
57 import org.eclipse.nebula.widgets.nattable.data.validate.ValidationFailedException;
58 import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;
59 import org.eclipse.nebula.widgets.nattable.edit.EditConfigHelper;
60 import org.eclipse.nebula.widgets.nattable.edit.ICellEditHandler;
61 import org.eclipse.nebula.widgets.nattable.edit.config.DefaultEditConfiguration;
62 import org.eclipse.nebula.widgets.nattable.edit.config.DialogErrorHandling;
63 import org.eclipse.nebula.widgets.nattable.edit.editor.AbstractCellEditor;
64 import org.eclipse.nebula.widgets.nattable.edit.editor.ComboBoxCellEditor;
65 import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor;
66 import org.eclipse.nebula.widgets.nattable.edit.editor.IEditErrorHandler;
67 import org.eclipse.nebula.widgets.nattable.edit.editor.TextCellEditor;
68 import org.eclipse.nebula.widgets.nattable.edit.gui.AbstractDialogCellEditor;
69 import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
70 import org.eclipse.nebula.widgets.nattable.grid.cell.AlternatingRowConfigLabelAccumulator;
71 import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
72 import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;
73 import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
74 import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
75 import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultColumnHeaderDataLayer;
76 import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;
77 import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
78 import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
79 import org.eclipse.nebula.widgets.nattable.hideshow.ColumnHideShowLayer;
80 import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent;
81 import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent;
82 import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
83 import org.eclipse.nebula.widgets.nattable.layer.ILayerListener;
84 import org.eclipse.nebula.widgets.nattable.layer.LabelStack;
85 import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator;
86 import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
87 import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;
88 import org.eclipse.nebula.widgets.nattable.painter.NatTableBorderOverlayPainter;
89 import org.eclipse.nebula.widgets.nattable.reorder.ColumnReorderLayer;
90 import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
91 import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum;
92 import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration;
93 import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;
94 import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
95 import org.eclipse.nebula.widgets.nattable.style.Style;
96 import org.eclipse.nebula.widgets.nattable.ui.menu.AbstractHeaderMenuConfiguration;
97 import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder;
98 import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
99 import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
100 import org.eclipse.nebula.widgets.nattable.widget.EditModeEnum;
101 import org.eclipse.swt.SWT;
102 import org.eclipse.swt.events.DisposeEvent;
103 import org.eclipse.swt.events.DisposeListener;
104 import org.eclipse.swt.events.FocusEvent;
105 import org.eclipse.swt.events.FocusListener;
106 import org.eclipse.swt.events.KeyEvent;
107 import org.eclipse.swt.events.KeyListener;
108 import org.eclipse.swt.events.MouseEvent;
109 import org.eclipse.swt.events.MouseListener;
110 import org.eclipse.swt.events.SelectionListener;
111 import org.eclipse.swt.graphics.Color;
112 import org.eclipse.swt.graphics.Point;
113 import org.eclipse.swt.graphics.RGB;
114 import org.eclipse.swt.graphics.Rectangle;
115 import org.eclipse.swt.widgets.Composite;
116 import org.eclipse.swt.widgets.Control;
117 import org.eclipse.swt.widgets.Display;
118 import org.eclipse.swt.widgets.Event;
119 import org.eclipse.swt.widgets.Listener;
120 import org.eclipse.swt.widgets.ScrollBar;
121 import org.eclipse.ui.PlatformUI;
122 import org.eclipse.ui.contexts.IContextActivation;
123 import org.eclipse.ui.contexts.IContextService;
124 import org.eclipse.ui.services.IServiceLocator;
125 import org.eclipse.ui.swt.IFocusService;
126 import org.simantics.browsing.ui.BuiltinKeys;
127 import org.simantics.browsing.ui.Column;
128 import org.simantics.browsing.ui.Column.Align;
129 import org.simantics.browsing.ui.DataSource;
130 import org.simantics.browsing.ui.ExplorerState;
131 import org.simantics.browsing.ui.GraphExplorer;
132 import org.simantics.browsing.ui.NodeContext;
133 import org.simantics.browsing.ui.NodeContext.CacheKey;
134 import org.simantics.browsing.ui.NodeContext.PrimitiveQueryKey;
135 import org.simantics.browsing.ui.NodeContext.QueryKey;
136 import org.simantics.browsing.ui.NodeQueryManager;
137 import org.simantics.browsing.ui.NodeQueryProcessor;
138 import org.simantics.browsing.ui.PrimitiveQueryProcessor;
139 import org.simantics.browsing.ui.PrimitiveQueryUpdater;
140 import org.simantics.browsing.ui.SelectionDataResolver;
141 import org.simantics.browsing.ui.SelectionFilter;
142 import org.simantics.browsing.ui.StatePersistor;
143 import org.simantics.browsing.ui.common.AdaptableHintContext;
144 import org.simantics.browsing.ui.common.ColumnKeys;
145 import org.simantics.browsing.ui.common.ErrorLogger;
146 import org.simantics.browsing.ui.common.NodeContextBuilder;
147 import org.simantics.browsing.ui.common.NodeContextUtil;
148 import org.simantics.browsing.ui.common.internal.GENodeQueryManager;
149 import org.simantics.browsing.ui.common.internal.IGECache;
150 import org.simantics.browsing.ui.common.internal.IGraphExplorerContext;
151 import org.simantics.browsing.ui.common.internal.UIElementReference;
152 import org.simantics.browsing.ui.common.processors.AbstractPrimitiveQueryProcessor;
153 import org.simantics.browsing.ui.common.processors.DefaultCheckedStateProcessor;
154 import org.simantics.browsing.ui.common.processors.DefaultComparableChildrenProcessor;
155 import org.simantics.browsing.ui.common.processors.DefaultFinalChildrenProcessor;
156 import org.simantics.browsing.ui.common.processors.DefaultImageDecoratorProcessor;
157 import org.simantics.browsing.ui.common.processors.DefaultImagerFactoriesProcessor;
158 import org.simantics.browsing.ui.common.processors.DefaultImagerProcessor;
159 import org.simantics.browsing.ui.common.processors.DefaultLabelDecoratorProcessor;
160 import org.simantics.browsing.ui.common.processors.DefaultLabelerFactoriesProcessor;
161 import org.simantics.browsing.ui.common.processors.DefaultLabelerProcessor;
162 import org.simantics.browsing.ui.common.processors.DefaultPrunedChildrenProcessor;
163 import org.simantics.browsing.ui.common.processors.DefaultSelectedImageDecoratorFactoriesProcessor;
164 import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelDecoratorFactoriesProcessor;
165 import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelerProcessor;
166 import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointFactoryProcessor;
167 import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointProcessor;
168 import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionProcessor;
169 import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionsProcessor;
170 import org.simantics.browsing.ui.common.processors.DefaultViewpointProcessor;
171 import org.simantics.browsing.ui.common.processors.IsExpandedProcessor;
172 import org.simantics.browsing.ui.common.processors.NoSelectionRequestProcessor;
173 import org.simantics.browsing.ui.common.processors.ProcessorLifecycle;
174 import org.simantics.browsing.ui.common.state.ExplorerStates;
175 import org.simantics.browsing.ui.content.Labeler;
176 import org.simantics.browsing.ui.content.Labeler.CustomModifier;
177 import org.simantics.browsing.ui.content.Labeler.DialogModifier;
178 import org.simantics.browsing.ui.content.Labeler.EnumerationModifier;
179 import org.simantics.browsing.ui.content.Labeler.Modifier;
180 import org.simantics.browsing.ui.nattable.override.DefaultTreeLayerConfiguration2;
181 import org.simantics.browsing.ui.swt.Activator;
182 import org.simantics.browsing.ui.swt.DefaultImageDecoratorsProcessor;
183 import org.simantics.browsing.ui.swt.DefaultIsExpandedProcessor;
184 import org.simantics.browsing.ui.swt.DefaultLabelDecoratorsProcessor;
185 import org.simantics.browsing.ui.swt.DefaultSelectedImagerProcessor;
186 import org.simantics.browsing.ui.swt.DefaultShowMaxChildrenProcessor;
187 import org.simantics.browsing.ui.swt.GraphExplorerImplBase;
188 import org.simantics.browsing.ui.swt.ImageLoaderJob;
189 import org.simantics.browsing.ui.swt.ViewerCellReference;
190 import org.simantics.browsing.ui.swt.ViewerRowReference;
191 import org.simantics.browsing.ui.swt.internal.Threads;
192 import org.simantics.db.layer0.SelectionHints;
193 import org.simantics.utils.datastructures.MapList;
194 import org.simantics.utils.datastructures.disposable.AbstractDisposable;
195 import org.simantics.utils.datastructures.hints.IHintContext;
196 import org.simantics.utils.threads.IThreadWorkQueue;
197 import org.simantics.utils.threads.SWTThread;
198 import org.simantics.utils.threads.ThreadUtils;
199 import org.simantics.utils.ui.AdaptionUtils;
200 import org.simantics.utils.ui.ISelectionUtils;
201 import org.simantics.utils.ui.SWTUtils;
202 import org.simantics.utils.ui.jface.BasePostSelectionProvider;
204 import gnu.trove.map.hash.THashMap;
205 import gnu.trove.map.hash.TObjectIntHashMap;
208 * NatTable based GraphExplorer
210 * This GraphExplorer is not fully compatible with the other implementations, since it is not based on SWT.Tree.
212 * 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.
215 * TODO: ability to hide headers
216 * TODO: code cleanup (copied from GraphExplorerImpl2)
218 * @author Marko Luukkainen <marko.luukkainen@vtt.fi>
221 public class NatTableGraphExplorer extends GraphExplorerImplBase implements GraphExplorer{
222 public static final int DEFAULT_MAX_CHILDREN = 10000;
223 private static final boolean DEBUG_SELECTION_LISTENERS = false;
224 private static final boolean DEBUG = false;
226 private Composite composite;
227 private NatTable natTable;
229 private GETreeLayer treeLayer;
230 private DataLayer dataLayer;
231 private ViewportLayer viewportLayer;
232 private SelectionLayer selectionLayer;
233 private GEColumnHeaderDataProvider columnHeaderDataProvider;
234 private GEColumnAccessor columnAccessor;
235 private DefaultRowHeaderDataLayer rowHeaderDataLayer;
236 private DataLayer columnHeaderDataLayer;
237 private DataLayer cornerDataLayer;
239 private List<TreeNode> list = new ArrayList<>();
241 private NatTableSelectionAdaptor selectionAdaptor;
242 private NatTableColumnLayout layout;
244 LocalResourceManager localResourceManager;
245 DeviceResourceManager resourceManager;
248 private IThreadWorkQueue thread;
250 @SuppressWarnings({ "rawtypes" })
251 final HashMap<CacheKey<?>, NodeQueryProcessor> processors = new HashMap<CacheKey<?>, NodeQueryProcessor>();
252 @SuppressWarnings({ "rawtypes" })
253 final HashMap<Object, PrimitiveQueryProcessor> primitiveProcessors = new HashMap<Object, PrimitiveQueryProcessor>();
254 @SuppressWarnings({ "rawtypes" })
255 final HashMap<Class, DataSource> dataSources = new HashMap<Class, DataSource>();
257 FontDescriptor originalFont;
258 protected ColorDescriptor originalForeground;
259 protected ColorDescriptor originalBackground;
260 private Color invalidModificationColor;
262 private Column[] columns;
263 private Map<String,Integer> columnKeyToIndex;
264 private boolean columnsAreVisible = true;
266 private NodeContext rootContext;
267 private TreeNode rootNode;
268 private StatePersistor persistor = null;
270 private boolean editable = true;
272 private boolean disposed = false;
274 private final CopyOnWriteArrayList<FocusListener> focusListeners = new CopyOnWriteArrayList<FocusListener>();
275 private final CopyOnWriteArrayList<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();
276 private final CopyOnWriteArrayList<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>();
278 private int autoExpandLevel = 0;
279 private IServiceLocator serviceLocator;
280 private IContextService contextService = null;
281 private IFocusService focusService = null;
282 private IContextActivation editingContext = null;
284 GeViewerContext explorerContext = new GeViewerContext(this);
286 private GraphExplorerPostSelectionProvider postSelectionProvider = new GraphExplorerPostSelectionProvider(this);
287 private BasePostSelectionProvider selectionProvider = new BasePostSelectionProvider();
288 private SelectionDataResolver selectionDataResolver;
289 private SelectionFilter selectionFilter;
291 private MapList<NodeContext, TreeNode> contextToNodeMap;
293 private ModificationContext modificationContext = null;
295 private boolean filterSelectionEdit = true;
297 private boolean expand;
298 private boolean verticalBarVisible = false;
300 private BiFunction<GraphExplorer, Object[], Object[]> selectionTransformation = new BiFunction<GraphExplorer, Object[], Object[]>() {
303 public Object[] apply(GraphExplorer explorer, Object[] objects) {
304 Object[] result = new Object[objects.length];
305 for (int i = 0; i < objects.length; i++) {
306 IHintContext context = new AdaptableHintContext(SelectionHints.KEY_MAIN);
307 context.setHint(SelectionHints.KEY_MAIN, objects[i]);
315 static class TransientStateImpl implements TransientExplorerState {
317 private Integer activeColumn = null;
320 public synchronized Integer getActiveColumn() {
324 public synchronized void setActiveColumn(Integer column) {
325 activeColumn = column;
330 private TransientStateImpl transientState = new TransientStateImpl();
332 public NatTableGraphExplorer(Composite parent) {
333 this(parent, SWT.BORDER | SWT.MULTI );
336 public NatTableGraphExplorer(Composite parent, int style) {
337 this.composite = parent;
340 this.localResourceManager = new LocalResourceManager(JFaceResources.getResources());
341 this.resourceManager = new DeviceResourceManager(parent.getDisplay());
343 this.imageLoaderJob = new ImageLoaderJob(this);
344 this.imageLoaderJob.setPriority(Job.DECORATE);
345 contextToNodeMap = new MapList<NodeContext, TreeNode>();
347 invalidModificationColor = (Color) localResourceManager.get(ColorDescriptor.createFrom(new RGB(255, 128, 128)));
349 this.thread = SWTThread.getThreadAccess(parent);
351 for (int i = 0; i < 10; i++)
352 explorerContext.activity.push(0);
354 originalFont = JFaceResources.getDefaultFontDescriptor();
356 columns = new Column[0];
357 createNatTable(style);
358 layout = new NatTableColumnLayout(natTable, columnHeaderDataProvider, rowHeaderDataLayer);
359 this.composite.setLayout(layout);
362 setDefaultProcessors();
364 natTable.addDisposeListener(new DisposeListener() {
367 public void widgetDisposed(DisposeEvent e) {
373 Listener listener = new Listener() {
376 public void handleEvent(Event event) {
378 switch (event.type) {
394 natTable.addListener(SWT.Activate, listener);
395 natTable.addListener(SWT.Deactivate, listener);
396 natTable.addListener(SWT.Show, listener);
397 natTable.addListener(SWT.Hide, listener);
398 natTable.addListener(SWT.Paint,listener);
400 setColumns( new Column[] { new Column(ColumnKeys.SINGLE) });
404 private long focusGainedAt = 0L;
405 private boolean visible = false;
406 private Collection<TreeNode> selectedNodes = new ArrayList<TreeNode>();
408 protected void setBasicListeners() {
410 natTable.addFocusListener(new FocusListener() {
412 public void focusGained(FocusEvent e) {
413 focusGainedAt = ((long) e.time) & 0xFFFFFFFFL;
414 for (FocusListener listener : focusListeners)
415 listener.focusGained(e);
418 public void focusLost(FocusEvent e) {
419 for (FocusListener listener : focusListeners)
420 listener.focusLost(e);
423 natTable.addMouseListener(new MouseListener() {
425 public void mouseDoubleClick(MouseEvent e) {
426 for (MouseListener listener : mouseListeners) {
427 listener.mouseDoubleClick(e);
431 public void mouseDown(MouseEvent e) {
432 for (MouseListener listener : mouseListeners) {
433 listener.mouseDown(e);
437 public void mouseUp(MouseEvent e) {
438 for (MouseListener listener : mouseListeners) {
443 natTable.addKeyListener(new KeyListener() {
445 public void keyPressed(KeyEvent e) {
446 for (KeyListener listener : keyListeners) {
447 listener.keyPressed(e);
451 public void keyReleased(KeyEvent e) {
452 for (KeyListener listener : keyListeners) {
453 listener.keyReleased(e);
458 selectionAdaptor.addSelectionChangedListener(new ISelectionChangedListener() {
461 public void selectionChanged(SelectionChangedEvent event) {
462 //System.out.println("GraphExplorerImpl2.fireSelection");
463 selectedNodes = AdaptionUtils.adaptToCollection(event.getSelection(), TreeNode.class);
464 Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);
465 selectionProvider.setAndFireSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));
469 selectionAdaptor.addPostSelectionChangedListener(new ISelectionChangedListener() {
472 public void selectionChanged(SelectionChangedEvent event) {
473 //System.out.println("GraphExplorerImpl2.firePostSelection");
474 Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);
475 selectionProvider.firePostSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));
482 private NodeContext pendingRoot;
484 private void activate() {
485 if (pendingRoot != null && !expand) {
486 doSetRoot(pendingRoot);
492 * Invoke only from SWT thread to reset the root of the graph explorer tree.
496 private void doSetRoot(NodeContext root) {
497 Display display = composite.getDisplay();
498 if (display.getThread() != Thread.currentThread()) {
499 throw new RuntimeException("Invoke from SWT thread only");
501 // System.out.println("doSetRoot " + root);
504 if (natTable.isDisposed())
506 if (root.getConstant(BuiltinKeys.INPUT) == null) {
507 ErrorLogger.defaultLogError("root node context does not contain BuiltinKeys.INPUT key. Node = " + root, new Exception("trace"));
513 // Empty caches, release queries.
514 if (rootNode != null) {
517 GeViewerContext oldContext = explorerContext;
518 GeViewerContext newContext = new GeViewerContext(this);
519 this.explorerContext = newContext;
520 oldContext.safeDispose();
522 // Need to empty these or otherwise they won't be emptied until the
523 // explorer is disposed which would mean that many unwanted references
524 // will be held by this map.
525 clearPrimitiveProcessors();
527 this.rootContext = root.getConstant(BuiltinKeys.IS_ROOT) != null ? root
528 : NodeContextUtil.withConstant(root, BuiltinKeys.IS_ROOT, Boolean.TRUE);
530 explorerContext.getCache().incRef(this.rootContext);
536 //refreshColumnSizes();
537 rootNode = new TreeNode(rootContext, explorerContext);
538 if (DEBUG) System.out.println("setRoot " + rootNode);
540 // Delay content reading.
542 // This is required for cases when GEImpl2 is attached to selection view. Reading content
543 // instantly could stagnate SWT thread under rapid changes in selection. By delaying the
544 // content reading we give the system a change to dispose the GEImpl2 before the content is read.
545 display.asyncExec(new Runnable() {
549 if (rootNode != null) {
550 rootNode.updateChildren();
551 rootNode.setExpanded(true);
559 private synchronized void listReIndex() {
560 for (TreeNode n : list) {
564 for (TreeNode c : rootNode.getChildren())
569 private void _insertToList(TreeNode n) {
570 n.setListIndex(list.size());
572 for (TreeNode c : n.getChildren()) {
577 public List<TreeNode> getItems() {
578 return Collections.unmodifiableList(list);
581 private void initializeState() {
582 if (persistor == null)
584 ExplorerStates.scheduleRead(getRoot(), persistor)
585 .thenAccept(state -> SWTUtils.asyncExec(natTable, () -> restoreState(state)));
588 private void restoreState(ExplorerState state) {
589 Object processor = getPrimitiveProcessor(BuiltinKeys.IS_EXPANDED);
590 if (processor instanceof DefaultIsExpandedProcessor) {
591 DefaultIsExpandedProcessor isExpandedProcessor = (DefaultIsExpandedProcessor)processor;
592 for(NodeContext expanded : state.expandedNodes) {
593 isExpandedProcessor.replaceExpanded(expanded, true);
599 public NodeContext getRoot() {
604 public IThreadWorkQueue getThread() {
609 public NodeContext getParentContext(NodeContext context) {
611 throw new IllegalStateException("disposed");
612 if (!thread.currentThreadAccess())
613 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
615 List<TreeNode> nodes = contextToNodeMap.getValuesUnsafe(context);
616 for (int i = 0; i < nodes.size(); i++) {
617 if (nodes.get(i).getParent() != null)
618 return nodes.get(i).getParent().getContext();
625 @SuppressWarnings("unchecked")
627 public <T> T getAdapter(Class<T> adapter) {
628 if(ISelectionProvider.class == adapter) return (T) postSelectionProvider;
629 else if(IPostSelectionProvider.class == adapter) return (T) postSelectionProvider;
634 protected void setDefaultProcessors() {
635 // Add a simple IMAGER query processor that always returns null.
636 // With this processor no images will ever be shown.
637 // setPrimitiveProcessor(new StaticImagerProcessor(null));
639 setProcessor(new DefaultComparableChildrenProcessor());
640 setProcessor(new DefaultLabelDecoratorsProcessor());
641 setProcessor(new DefaultImageDecoratorsProcessor());
642 setProcessor(new DefaultSelectedLabelerProcessor());
643 setProcessor(new DefaultLabelerFactoriesProcessor());
644 setProcessor(new DefaultSelectedImagerProcessor());
645 setProcessor(new DefaultImagerFactoriesProcessor());
646 setPrimitiveProcessor(new DefaultLabelerProcessor());
647 setPrimitiveProcessor(new DefaultCheckedStateProcessor());
648 setPrimitiveProcessor(new DefaultImagerProcessor());
649 setPrimitiveProcessor(new DefaultLabelDecoratorProcessor());
650 setPrimitiveProcessor(new DefaultImageDecoratorProcessor());
651 setPrimitiveProcessor(new NoSelectionRequestProcessor());
653 setProcessor(new DefaultFinalChildrenProcessor(this));
655 setProcessor(new DefaultPrunedChildrenProcessor());
656 setProcessor(new DefaultSelectedViewpointProcessor());
657 setProcessor(new DefaultSelectedLabelDecoratorFactoriesProcessor());
658 setProcessor(new DefaultSelectedImageDecoratorFactoriesProcessor());
659 setProcessor(new DefaultViewpointContributionsProcessor());
661 setPrimitiveProcessor(new DefaultViewpointProcessor());
662 setPrimitiveProcessor(new DefaultViewpointContributionProcessor());
663 setPrimitiveProcessor(new DefaultSelectedViewpointFactoryProcessor());
664 setPrimitiveProcessor(new TreeNodeIsExpandedProcessor());
665 setPrimitiveProcessor(new DefaultShowMaxChildrenProcessor());
669 public Column[] getColumns() {
670 return Arrays.copyOf(columns, columns.length);
674 public void setColumnsVisible(boolean visible) {
675 columnsAreVisible = visible;
676 //FIXME if(natTable != null) this.columnHeaderDataLayer.setHeaderVisible(columnsAreVisible);
680 public void setColumns(final Column[] columns) {
681 setColumns(columns, null);
685 public void setColumns(final Column[] columns, Consumer<Map<Column, Object>> callback) {
687 checkUniqueColumnKeys(columns);
689 Display d = composite.getDisplay();
690 if (d.getThread() == Thread.currentThread()) {
691 doSetColumns(columns, callback);
692 natTable.refresh(true);
694 d.asyncExec(new Runnable() {
697 if (natTable == null)
699 if (natTable.isDisposed())
701 doSetColumns(columns, callback);
703 natTable.getParent().layout();
708 private void checkUniqueColumnKeys(Column[] cols) {
709 Set<String> usedColumnKeys = new HashSet<String>();
710 List<Column> duplicateColumns = new ArrayList<Column>();
711 for (Column c : cols) {
712 if (!usedColumnKeys.add(c.getKey()))
713 duplicateColumns.add(c);
715 if (!duplicateColumns.isEmpty()) {
716 throw new IllegalArgumentException("All columns do not have unique keys: " + cols + ", overlapping: " + duplicateColumns);
720 private void doSetColumns(Column[] cols, Consumer<Map<Column, Object>> callback) {
722 HashMap<String, Integer> keyToIndex = new HashMap<String, Integer>();
723 for (int i = 0; i < cols.length; ++i) {
724 keyToIndex.put(cols[i].getKey(), i);
727 this.columns = Arrays.copyOf(cols, cols.length);
728 //this.columns[cols.length] = FILLER_COLUMN;
729 this.columnKeyToIndex = keyToIndex;
731 columnHeaderDataProvider.updateColumnSizes();
733 Map<Column, Object> map = new HashMap<Column, Object>();
735 // FIXME : temporary workaround for ModelBrowser.
736 // natTable.setHeaderVisible(columns.length == 1 ? false : columnsAreVisible);
740 for (Column column : columns) {
741 int width = column.getWidth();
742 if(column.hasGrab()) {
745 layout.setColumnData(columnIndex, new ColumnWeightData(column.getWeight(), width));
750 layout.setColumnData(columnIndex, new ColumnWeightData(columns.length > 1 ? 0 : 1, width));
758 if(callback != null) callback.accept(map);
761 int toSWT(Align alignment) {
763 case LEFT: return SWT.LEFT;
764 case CENTER: return SWT.CENTER;
765 case RIGHT: return SWT.RIGHT;
766 default: throw new Error("unhandled alignment: " + alignment);
771 public <T> void setProcessor(NodeQueryProcessor<T> processor) {
773 if (processor == null)
774 throw new IllegalArgumentException("null processor");
776 processors.put(processor.getIdentifier(), processor);
780 public <T> void setPrimitiveProcessor(PrimitiveQueryProcessor<T> processor) {
782 if (processor == null)
783 throw new IllegalArgumentException("null processor");
785 PrimitiveQueryProcessor<?> oldProcessor = primitiveProcessors.put(
786 processor.getIdentifier(), processor);
788 if (oldProcessor instanceof ProcessorLifecycle)
789 ((ProcessorLifecycle) oldProcessor).detached(this);
790 if (processor instanceof ProcessorLifecycle)
791 ((ProcessorLifecycle) processor).attached(this);
795 public <T> void setDataSource(DataSource<T> provider) {
797 if (provider == null)
798 throw new IllegalArgumentException("null provider");
799 dataSources.put(provider.getProvidedClass(), provider);
802 @SuppressWarnings("unchecked")
804 public <T> DataSource<T> removeDataSource(Class<T> forProvidedClass) {
806 if (forProvidedClass == null)
807 throw new IllegalArgumentException("null class");
808 return dataSources.remove(forProvidedClass);
812 public void setPersistor(StatePersistor persistor) {
813 this.persistor = persistor;
817 public SelectionDataResolver getSelectionDataResolver() {
818 return selectionDataResolver;
822 public void setSelectionDataResolver(SelectionDataResolver r) {
823 this.selectionDataResolver = r;
827 public SelectionFilter getSelectionFilter() {
828 return selectionFilter;
832 public void setSelectionFilter(SelectionFilter f) {
833 this.selectionFilter = f;
834 // TODO: re-filter current selection?
837 protected ISelection constructSelection(NodeContext... contexts) {
838 if (contexts == null)
839 throw new IllegalArgumentException("null contexts");
840 if (contexts.length == 0)
841 return StructuredSelection.EMPTY;
842 if (selectionFilter == null)
843 return new StructuredSelection(transformSelection(contexts));
844 return new StructuredSelection( transformSelection(filter(selectionFilter, contexts)) );
847 protected Object[] transformSelection(Object[] objects) {
848 return selectionTransformation.apply(this, objects);
851 protected static Object[] filter(SelectionFilter filter, NodeContext[] contexts) {
852 int len = contexts.length;
853 Object[] objects = new Object[len];
854 for (int i = 0; i < len; ++i)
855 objects[i] = filter.filter(contexts[i]);
860 public void setSelectionTransformation(
861 BiFunction<GraphExplorer, Object[], Object[]> f) {
862 this.selectionTransformation = f;
865 public ISelection getWidgetSelection() {
866 return selectionAdaptor.getSelection();
870 public <T> void addListener(T listener) {
871 if (listener instanceof FocusListener) {
872 focusListeners.add((FocusListener) listener);
873 } else if (listener instanceof MouseListener) {
874 mouseListeners.add((MouseListener) listener);
875 } else if (listener instanceof KeyListener) {
876 keyListeners.add((KeyListener) listener);
881 public <T> void removeListener(T listener) {
882 if (listener instanceof FocusListener) {
883 focusListeners.remove(listener);
884 } else if (listener instanceof MouseListener) {
885 mouseListeners.remove(listener);
886 } else if (listener instanceof KeyListener) {
887 keyListeners.remove(listener);
891 public void addSelectionListener(SelectionListener listener) {
892 selectionAdaptor.addSelectionListener(listener);
895 public void removeSelectionListener(SelectionListener listener) {
896 selectionAdaptor.removeSelectionListener(listener);
899 private Set<String> uiContexts;
902 public void setUIContexts(Set<String> contexts) {
903 this.uiContexts = contexts;
907 public void setRoot(final Object root) {
908 if(uiContexts != null && uiContexts.size() == 1)
909 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root, BuiltinKeys.UI_CONTEXT, uiContexts.iterator().next()));
911 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root));
915 public void setRootContext(final NodeContext context) {
916 setRootContext0(context);
919 private void setRoot(NodeContext context) {
921 pendingRoot = context;
922 Display.getDefault().asyncExec(new Runnable() {
925 if (natTable!= null && !natTable.isDisposed())
934 private void setRootContext0(final NodeContext context) {
935 Assert.isNotNull(context, "root must not be null");
936 if (isDisposed() || natTable.isDisposed())
938 Display display = natTable.getDisplay();
939 if (display.getThread() == Thread.currentThread()) {
942 display.asyncExec(new Runnable() {
952 public void setFocus() {
956 @SuppressWarnings("unchecked")
958 public <T> T getControl() {
964 public boolean isDisposed() {
968 protected void assertNotDisposed() {
970 throw new IllegalStateException("disposed");
974 public boolean isEditable() {
979 public void setEditable(boolean editable) {
980 if (!thread.currentThreadAccess())
981 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
983 this.editable = editable;
984 Display display = natTable.getDisplay();
985 natTable.setBackground(editable ? null : display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
988 private void doDispose() {
992 // TODO: Since GENodeQueryManager is cached in QueryChache and it refers to this class
993 // we have to remove all references here to reduce memory consumption.
995 // Proper fix would be to remove references between QueryCache and GENodeQueryManagers.
997 // Clearing explorerContext replaces GECache with dummy implementation, which makes node disposal much faster.
998 explorerContext.close();
999 if (rootNode != null) {
1000 // Using fastDispose bypasses item removal from nodeMap, which is cleared later.
1001 rootNode.fastDispose();
1004 explorerContext.dispose();
1005 explorerContext = null;
1007 detachPrimitiveProcessors();
1008 primitiveProcessors.clear();
1009 dataSources.clear();
1010 pendingItems.clear();
1012 mouseListeners.clear();
1013 selectionProvider.clearListeners();
1014 selectionProvider = null;
1015 selectionDataResolver = null;
1016 selectedNodes.clear();
1017 selectedNodes = null;
1018 selectionTransformation = null;
1019 originalFont = null;
1020 localResourceManager.dispose();
1021 localResourceManager = null;
1022 // Must shutdown image loader job before disposing its ResourceManager
1023 imageLoaderJob.dispose();
1024 imageLoaderJob.cancel();
1026 imageLoaderJob.join();
1027 imageLoaderJob = null;
1028 } catch (InterruptedException e) {
1029 ErrorLogger.defaultLogError(e);
1031 resourceManager.dispose();
1032 resourceManager = null;
1033 contextToNodeMap.clear();
1034 contextToNodeMap = null;
1035 if (postSelectionProvider != null) {
1036 postSelectionProvider.dispose();
1037 postSelectionProvider = null;
1040 modificationContext = null;
1041 focusService = null;
1042 contextService = null;
1043 serviceLocator = null;
1045 columnKeyToIndex.clear();
1046 columnKeyToIndex = null;
1047 // if (natTable != null) {
1048 // natTable.dispose();
1053 viewportLayer = null;
1054 selectionLayer = null;
1055 columnHeaderDataProvider = null;
1056 columnAccessor = null;
1057 rowHeaderDataLayer = null;
1058 columnHeaderDataLayer = null;
1059 cornerDataLayer = null;
1064 public boolean select(NodeContext context) {
1066 assertNotDisposed();
1068 if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) {
1069 StructuredSelection s = new StructuredSelection();
1070 selectionAdaptor.setSelection(s);
1071 selectionProvider.setAndFireNonEqualSelection(s);
1075 selectionAdaptor.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0)));
1081 public boolean select(TreeNode node) {
1082 assertNotDisposed();
1084 if (!list.contains(node)) {
1085 StructuredSelection s = new StructuredSelection();
1086 selectionAdaptor.setSelection(s);
1087 selectionProvider.setAndFireNonEqualSelection(s);
1090 selectionAdaptor.setSelection(new StructuredSelection(node));
1094 public void show(TreeNode node) {
1095 int index = node.getListIndex();
1097 int position = treeLayer.getRowPositionByIndex(index);
1099 treeLayer.expandToTreeRow(index);
1100 position = treeLayer.getRowPositionByIndex(index);
1102 viewportLayer.moveRowPositionIntoViewport(position);
1106 public boolean selectPath(Collection<NodeContext> contexts) {
1108 if(contexts == null) throw new IllegalArgumentException("Null list is not allowed");
1109 if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed");
1111 return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0);
1115 private boolean selectPathInternal(NodeContext[] contexts, int position) {
1117 NodeContext head = contexts[position];
1119 if(position == contexts.length-1) {
1120 return select(head);
1124 setExpanded(head, true);
1125 if(!waitVisible(contexts[position+1])) return false;
1127 return selectPathInternal(contexts, position+1);
1131 private boolean waitVisible(NodeContext context) {
1132 long start = System.nanoTime();
1133 while(!isVisible(context)) {
1134 Display.getCurrent().readAndDispatch();
1135 long duration = System.nanoTime() - start;
1136 if(duration > 10e9) return false;
1142 public boolean isVisible(NodeContext context) {
1143 if (contextToNodeMap.getValuesUnsafe(context).size() == 0)
1146 return true; //FIXME
1147 // Object elements[] = viewer.getVisibleExpandedElements();
1148 // return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0));
1154 public TransientExplorerState getTransientState() {
1155 if (!thread.currentThreadAccess())
1156 throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread());
1157 return transientState;
1161 public <T> T query(NodeContext context, CacheKey<T> key) {
1162 return this.explorerContext.cache.get(context, key);
1166 * For setting a more local service locator for the explorer than the global
1167 * workbench service locator. Sometimes required to give this implementation
1168 * access to local workbench services like IFocusService.
1171 * Must be invoked during right after construction.
1173 * @param serviceLocator
1174 * a specific service locator or <code>null</code> to use the
1175 * workbench global service locator
1177 public void setServiceLocator(IServiceLocator serviceLocator) {
1178 if (serviceLocator == null && PlatformUI.isWorkbenchRunning())
1179 serviceLocator = PlatformUI.getWorkbench();
1180 this.serviceLocator = serviceLocator;
1181 if (serviceLocator != null) {
1182 this.contextService = (IContextService) serviceLocator.getService(IContextService.class);
1183 this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class);
1187 private void detachPrimitiveProcessors() {
1188 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
1189 if (p instanceof ProcessorLifecycle) {
1190 ((ProcessorLifecycle) p).detached(this);
1195 private void clearPrimitiveProcessors() {
1196 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
1197 if (p instanceof ProcessorLifecycle) {
1198 ((ProcessorLifecycle) p).clear();
1204 public void setExpanded(NodeContext context, boolean expanded) {
1205 for (TreeNode n : contextToNodeMap.getValues(context)) {
1207 treeLayer.expandTreeRow(n.getListIndex());
1209 treeLayer.collapseTreeRow(n.getListIndex());
1215 public void setAutoExpandLevel(int level) {
1216 this.autoExpandLevel = level;
1217 treeLayer.expandAllToLevel(level);
1220 int maxChildren = DEFAULT_MAX_CHILDREN;
1223 public int getMaxChildren() {
1228 public void setMaxChildren(int maxChildren) {
1229 this.maxChildren = maxChildren;
1234 public int getMaxChildren(NodeQueryManager manager, NodeContext context) {
1235 Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN);
1236 //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result);
1237 if (result != null) {
1239 throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result);
1246 public <T> NodeQueryProcessor<T> getProcessor(QueryKey<T> key) {
1247 return explorerContext.getProcessor(key);
1251 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(PrimitiveQueryKey<T> key) {
1252 return explorerContext.getPrimitiveProcessor(key);
1255 private HashSet<UpdateItem> pendingItems = new HashSet<UpdateItem>();
1256 private boolean updating = false;
1257 private int updateCounter = 0;
1258 final ScheduledExecutorService uiUpdateScheduler = ThreadUtils.getNonBlockingWorkExecutor();
1260 private class UpdateItem {
1264 public UpdateItem(TreeNode element) {
1268 public UpdateItem(TreeNode element, int columnIndex) {
1269 this.element = element;
1270 this.columnIndex = columnIndex;
1271 if (element != null && element.isDisposed()) {
1272 throw new IllegalArgumentException("Node is disposed. " + element);
1276 public void update(NatTable natTable) {
1277 if (element != null) {
1279 if (element.isDisposed()) {
1282 if (element.updateChildren()) {
1284 System.out.println("Update Item updateChildren " + element.listIndex + " " + element);
1285 printDebug(NatTableGraphExplorer.this);
1288 if (!element.isHidden()) {
1289 if (!element.isExpanded()) {
1290 if (element.listIndex >= 0)
1291 treeLayer.collapseTreeRow(element.listIndex);
1293 System.out.println("Update Item collapse " + element.listIndex);
1294 printDebug(NatTableGraphExplorer.this);
1297 for (TreeNode c : element.getChildren())
1301 TreeNode p = element.getCollapsedAncestor();
1303 if (element.listIndex >= 0)
1304 treeLayer.collapseTreeRow(element.listIndex);
1305 if (p.listIndex >= 0)
1306 treeLayer.collapseTreeRow(p.listIndex);
1308 System.out.println("Update Item ancetor collapse " + p.listIndex);
1309 printDebug(NatTableGraphExplorer.this);
1314 // if (columnIndex >= 0) {
1315 // viewer.update(element, new String[]{columns[columnIndex].getKey()});
1317 // viewer.refresh(element,true);
1323 if (!element.autoExpanded && !element.isDisposed() && autoExpandLevel > 1 && !element.isExpanded() && element.getDepth() <= autoExpandLevel) {
1325 element.autoExpanded = true;
1327 if (DEBUG) System.out.println("Update Item expand " + element.listIndex);
1328 treeLayer.expandTreeRow(element.getListIndex());
1329 //viewer.setExpandedState(element, true);
1333 if (rootNode.updateChildren()) {
1340 public boolean equals(Object obj) {
1343 if (obj.getClass() != getClass())
1345 UpdateItem other = (UpdateItem)obj;
1346 if (columnIndex != other.columnIndex)
1348 if (element != null)
1349 return element.equals(other.element);
1350 return other.element == null;
1354 public int hashCode() {
1355 if (element != null)
1356 return element.hashCode() + columnIndex;
1361 private void update(final TreeNode element, final int columnIndex) {
1362 if (natTable.isDisposed())
1364 if (element != null && element.isDisposed())
1366 if (DEBUG) System.out.println("update " + element + " " + columnIndex);
1367 synchronized (pendingItems) {
1368 pendingItems.add(new UpdateItem(element, columnIndex));
1369 if (updating) return;
1375 private void update(final TreeNode element) {
1377 if (natTable.isDisposed())
1379 if (element != null && element.isDisposed())
1381 if (DEBUG) System.out.println("update " + element);
1382 synchronized (pendingItems) {
1383 pendingItems.add(new UpdateItem(element));
1384 if (updating) return;
1390 boolean scheduleUpdater() {
1392 if (natTable.isDisposed())
1395 if (!pendingItems.isEmpty()) {
1397 int activity = explorerContext.activityInt;
1399 if (activity < 100) {
1400 //System.out.println("Scheduling update immediately.");
1401 } else if (activity < 1000) {
1402 //System.out.println("Scheduling update after 500ms.");
1405 //System.out.println("Scheduling update after 3000ms.");
1411 //System.out.println("Scheduling UI update after " + delay + " ms.");
1412 uiUpdateScheduler.schedule(new Runnable() {
1416 if (natTable == null || natTable.isDisposed())
1419 if (updateCounter > 0) {
1421 uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS);
1423 natTable.getDisplay().asyncExec(new UpdateRunner(NatTableGraphExplorer.this, NatTableGraphExplorer.this.explorerContext));
1427 }, delay, TimeUnit.MILLISECONDS);
1437 public String startEditing(NodeContext context, String columnKey) {
1438 assertNotDisposed();
1439 if (!thread.currentThreadAccess())
1440 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
1442 if(columnKey.startsWith("#")) {
1443 columnKey = columnKey.substring(1);
1446 Integer columnIndex = columnKeyToIndex.get(columnKey);
1447 if (columnIndex == null)
1448 return "Rename not supported for selection";
1450 // viewer.editElement(context, columnIndex);
1451 // if(viewer.isCellEditorActive()) return null;
1452 return "Rename not supported for selection";
1456 public String startEditing(String columnKey) {
1457 ISelection selection = postSelectionProvider.getSelection();
1458 if(selection == null) return "Rename not supported for selection";
1459 NodeContext context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class);
1460 if(context == null) return "Rename not supported for selection";
1462 return startEditing(context, columnKey);
1466 public void setSelection(final ISelection selection, boolean forceControlUpdate) {
1467 assertNotDisposed();
1468 boolean equalsOld = selectionProvider.selectionEquals(selection);
1469 if (equalsOld && !forceControlUpdate) {
1470 // Just set the selection object instance, fire no events nor update
1471 // the viewer selection.
1472 selectionProvider.setSelection(selection);
1474 Collection<NodeContext> coll = AdaptionUtils.adaptToCollection(selection, NodeContext.class);
1475 Collection<TreeNode> nodes = new ArrayList<TreeNode>();
1476 for (NodeContext c : coll) {
1477 List<TreeNode> match = contextToNodeMap.getValuesUnsafe(c);
1478 if(match.size() > 0)
1479 nodes.add(match.get(0));
1481 final ISelection sel = new StructuredSelection(nodes.toArray());
1482 if (coll.size() == 0)
1484 // Schedule viewer and selection update if necessary.
1485 if (natTable.isDisposed())
1487 Display d = natTable.getDisplay();
1488 if (d.getThread() == Thread.currentThread()) {
1489 selectionAdaptor.setSelection(sel);
1491 d.asyncExec(new Runnable() {
1494 if (natTable.isDisposed())
1496 selectionAdaptor.setSelection(sel);
1504 public void setModificationContext(ModificationContext modificationContext) {
1505 this.modificationContext = modificationContext;
1509 final ExecutorService queryUpdateScheduler = Threads.getExecutor();
1512 public static double getDisplayScale() {
1513 Point dpi = Display.getCurrent().getDPI();
1514 return (double)dpi.x/96.0;
1517 private void createNatTable(int style) {
1518 GETreeData treeData = new GETreeData(list);
1519 GETreeRowModel<TreeNode> treeRowModel = new GETreeRowModel<TreeNode>(treeData);
1520 columnAccessor = new GEColumnAccessor(this);
1522 IDataProvider dataProvider = new ListDataProvider<TreeNode>(list, columnAccessor);
1524 // FIXME: NatTable 1.0 required help to work with custom display scaling (Windows 7 display scaling).
1525 // It seems that NatTable 1.4 breaks with the same code in Windows 7, so now the code is disabled.
1526 // More testing with different hardware is required...
1527 // int defaultFontSize = 12;
1528 // int height = (int)Math.ceil(((double)(defaultFontSize))*getDisplayScale()) + DataLayer.DEFAULT_ROW_HEIGHT-defaultFontSize;
1529 // dataLayer = new DataLayer(dataProvider, DataLayer.DEFAULT_COLUMN_WIDTH, height);
1530 dataLayer = new DataLayer(dataProvider);
1532 // resizable rows are unnecessary in Sulca report.
1533 dataLayer.setRowsResizableByDefault(false);
1536 DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(dataProvider);
1537 rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider);
1539 // adjust row header column width so that row numbers fit into the column.
1540 //adjustRowHeaderWidth(list.size());
1542 // Column header layer
1543 columnHeaderDataProvider = new GEColumnHeaderDataProvider(this, dataLayer);
1544 columnHeaderDataLayer = new DefaultColumnHeaderDataLayer(columnHeaderDataProvider);
1545 //columnHeaderDataLayer.setDefaultRowHeight(height);
1546 columnHeaderDataProvider.updateColumnSizes();
1548 //ISortModel sortModel = new EcoSortModel(this, generator,dataLayer);
1550 // Column re-order + hide
1551 ColumnReorderLayer columnReorderLayer = new ColumnReorderLayer(dataLayer);
1552 ColumnHideShowLayer columnHideShowLayer = new ColumnHideShowLayer(columnReorderLayer);
1555 treeLayer = new GETreeLayer(columnHideShowLayer, treeRowModel, false);
1557 selectionLayer = new SelectionLayer(treeLayer);
1559 viewportLayer = new ViewportLayer(selectionLayer);
1561 ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, viewportLayer, selectionLayer);
1562 // Note: The column header layer is wrapped in a filter row composite.
1563 // This plugs in the filter row functionality
1565 ColumnOverrideLabelAccumulator labelAccumulator = new ColumnOverrideLabelAccumulator(columnHeaderDataLayer);
1566 columnHeaderDataLayer.setConfigLabelAccumulator(labelAccumulator);
1569 //SortHeaderLayer<TreeNode> sortHeaderLayer = new SortHeaderLayer<TreeNode>(columnHeaderLayer, sortModel, false);
1571 RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, viewportLayer, selectionLayer);
1574 DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider);
1575 cornerDataLayer = new DataLayer(cornerDataProvider);
1576 //CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, sortHeaderLayer);
1577 CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, columnHeaderLayer);
1580 //GridLayer gridLayer = new GridLayer(viewportLayer,sortHeaderLayer,rowHeaderLayer, cornerLayer);
1581 GridLayer gridLayer = new GridLayer(viewportLayer, columnHeaderLayer,rowHeaderLayer, cornerLayer, false);
1583 /* Since 1.4.0, alternative row rendering uses row indexes in the original data list.
1584 When combined with collapsed tree rows, rows with odd or even index may end up next to each other,
1585 which defeats the purpose of alternating colors. This overrides that and returns the functionality
1586 that we had with 1.0.1. */
1587 gridLayer.setConfigLabelAccumulatorForRegion(GridRegion.BODY, new RelativeAlternatingRowConfigLabelAccumulator());
1588 gridLayer.addConfiguration(new DefaultEditConfiguration());
1589 //gridLayer.addConfiguration(new DefaultEditBindings());
1590 gridLayer.addConfiguration(new GEEditBindings());
1592 natTable = new NatTable(composite,gridLayer,false);
1594 //selectionLayer.registerCommandHandler(new EcoCopyDataCommandHandler(selectionLayer,columnHeaderDataLayer,columnAccessor, columnHeaderDataProvider));
1596 natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable));
1597 natTable.addConfiguration(new DefaultTreeLayerConfiguration2(treeLayer));
1598 natTable.addConfiguration(new SingleClickSortConfiguration());
1599 //natTable.addLayerListener(this);
1601 natTable.addConfiguration(new GENatTableThemeConfiguration(treeData, style));
1602 natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable));
1604 natTable.addConfiguration(new AbstractRegistryConfiguration() {
1607 public void configureRegistry(IConfigRegistry configRegistry) {
1608 configRegistry.registerConfigAttribute(
1609 EditConfigAttributes.CELL_EDITABLE_RULE,
1610 new IEditableRule() {
1613 public boolean isEditable(ILayerCell cell,
1614 IConfigRegistry configRegistry) {
1615 int col = cell.getColumnIndex();
1616 int row = cell.getRowIndex();
1617 TreeNode node = list.get(row);
1618 Modifier modifier = getModifier(node,col);
1619 return modifier != null;
1624 public boolean isEditable(int columnIndex, int rowIndex) {
1625 // there are no callers?
1630 configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, new AdaptableCellEditor());
1631 configRegistry.registerConfigAttribute(EditConfigAttributes.CONVERSION_ERROR_HANDLER, new DialogErrorHandling(), DisplayMode.EDIT);
1632 configRegistry.registerConfigAttribute(EditConfigAttributes.VALIDATION_ERROR_HANDLER, new DialogErrorHandling(), DisplayMode.EDIT);
1633 configRegistry.registerConfigAttribute(EditConfigAttributes.DATA_VALIDATOR, new AdaptableDataValidator(),DisplayMode.EDIT);
1635 Style conversionErrorStyle = new Style();
1636 conversionErrorStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, GUIHelper.COLOR_RED);
1637 conversionErrorStyle.setAttributeValue(CellStyleAttributes.FOREGROUND_COLOR, GUIHelper.COLOR_WHITE);
1638 configRegistry.registerConfigAttribute(EditConfigAttributes.CONVERSION_ERROR_STYLE, conversionErrorStyle, DisplayMode.EDIT);
1640 configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, new DefaultDisplayConverter(),DisplayMode.EDIT);
1646 if ((style & SWT.BORDER) > 0) {
1647 natTable.addOverlayPainter(new NatTableBorderOverlayPainter());
1650 natTable.configure();
1652 // natTable.addListener(SWT.MenuDetect, new NatTableMenuListener());
1654 // DefaultToolTip toolTip = new EcoCellToolTip(natTable, columnAccessor);
1655 // toolTip.setBackgroundColor(natTable.getDisplay().getSystemColor(SWT.COLOR_WHITE));
1656 // toolTip.setPopupDelay(500);
1657 // toolTip.activate();
1658 // toolTip.setShift(new Point(10, 10));
1661 // menuManager.createContextMenu(composite);
1662 // natTable.setMenu(getMenuManager().getMenu());
1664 selectionAdaptor = new NatTableSelectionAdaptor(natTable, selectionLayer, treeData);
1667 Modifier getModifier(TreeNode element, int columnIndex) {
1668 GENodeQueryManager manager = element.getManager();
1669 final NodeContext context = element.getContext();
1670 Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
1671 if (labeler == null)
1673 Column column = columns[columnIndex];
1675 return labeler.getModifier(modificationContext, column.getKey());
1679 private class AdaptableCellEditor implements ICellEditor {
1683 public Control activateCell(Composite parent, Object originalCanonicalValue, EditModeEnum editMode,
1684 ICellEditHandler editHandler, ILayerCell cell, IConfigRegistry configRegistry) {
1685 int col = cell.getColumnIndex();
1686 int row = cell.getRowIndex();
1687 TreeNode node = list.get(row);
1688 Modifier modifier = getModifier(node, col);
1689 if (modifier == null)
1693 if (modifier instanceof DialogModifier) {
1694 DialogModifier mod = (DialogModifier)modifier;
1695 editor = new DialogCellEditor(node, col, mod);
1696 } else if (modifier instanceof CustomModifier) {
1697 CustomModifier mod = (CustomModifier)modifier;
1698 editor = new CustomCellEditor(node, col, mod);
1699 } else if (modifier instanceof EnumerationModifier) {
1700 EnumerationModifier mod = (EnumerationModifier)modifier;
1701 editor = new ComboBoxCellEditor(mod.getValues());
1703 editor = new TextCellEditor();
1706 return editor.activateCell(parent, originalCanonicalValue, editMode, editHandler, cell, configRegistry);
1710 public int getColumnIndex() {
1711 return editor.getColumnIndex();
1715 public int getRowIndex() {
1716 return editor.getRowIndex();
1720 public int getColumnPosition() {
1721 return editor.getColumnPosition();
1725 public int getRowPosition() {
1726 return editor.getRowPosition();
1730 public Object getEditorValue() {
1731 return editor.getEditorValue();
1735 public void setEditorValue(Object value) {
1736 editor.setEditorValue(value);
1741 public Object getCanonicalValue() {
1742 return editor.getCanonicalValue();
1746 public Object getCanonicalValue(IEditErrorHandler conversionErrorHandler) {
1747 return editor.getCanonicalValue();
1751 public void setCanonicalValue(Object canonicalValue) {
1752 editor.setCanonicalValue(canonicalValue);
1757 public boolean validateCanonicalValue(Object canonicalValue) {
1758 return editor.validateCanonicalValue(canonicalValue);
1762 public boolean validateCanonicalValue(Object canonicalValue, IEditErrorHandler validationErrorHandler) {
1763 return editor.validateCanonicalValue(canonicalValue, validationErrorHandler);
1767 public boolean commit(MoveDirectionEnum direction) {
1768 return editor.commit(direction);
1772 public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit) {
1773 return editor.commit(direction, closeAfterCommit);
1777 public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit, boolean skipValidation) {
1778 return editor.commit(direction, closeAfterCommit, skipValidation);
1782 public void close() {
1788 public boolean isClosed() {
1789 return editor.isClosed();
1793 public Control getEditorControl() {
1794 return editor.getEditorControl();
1798 public Control createEditorControl(Composite parent) {
1799 return editor.createEditorControl(parent);
1803 public boolean openInline(IConfigRegistry configRegistry, List<String> configLabels) {
1804 return EditConfigHelper.openInline(configRegistry, configLabels);
1808 public boolean supportMultiEdit(IConfigRegistry configRegistry, List<String> configLabels) {
1809 return editor.supportMultiEdit(configRegistry, configLabels);
1813 public boolean openMultiEditDialog() {
1814 return editor.openMultiEditDialog();
1818 public boolean openAdjacentEditor() {
1819 return editor.openAdjacentEditor();
1823 public boolean activateAtAnyPosition() {
1828 public boolean activateOnTraversal(IConfigRegistry configRegistry, List<String> configLabels) {
1829 return editor.activateOnTraversal(configRegistry, configLabels);
1833 public void addEditorControlListeners() {
1834 editor.addEditorControlListeners();
1839 public void removeEditorControlListeners() {
1840 editor.removeEditorControlListeners();
1845 public Rectangle calculateControlBounds(Rectangle cellBounds) {
1846 return editor.calculateControlBounds(cellBounds);
1850 private class AdaptableDataValidator implements IDataValidator {
1852 public boolean validate(ILayerCell cell, IConfigRegistry configRegistry, Object newValue) {
1853 int col = cell.getColumnIndex();
1854 int row = cell.getRowIndex();
1855 return validate(col, row, newValue);
1859 public boolean validate(int col, int row, Object newValue) {
1860 TreeNode node = list.get(row);
1861 Modifier modifier = getModifier(node, col);
1862 if (modifier == null)
1865 String err = modifier.isValid(newValue != null ? newValue.toString() : "");
1868 throw new ValidationFailedException(err);
1872 private class CustomCellEditor extends AbstractCellEditor {
1874 CustomModifier customModifier;
1878 public CustomCellEditor(TreeNode node, int column, CustomModifier customModifier) {
1879 this.customModifier = customModifier;
1881 this.column = column;
1885 public Object getEditorValue() {
1886 return customModifier.getValue();
1890 public void setEditorValue(Object value) {
1891 customModifier.modify(value.toString());
1896 public Control getEditorControl() {
1901 public Control createEditorControl(Composite parent) {
1902 return (Control)customModifier.createControl(parent, null, column, node.getContext());
1907 protected Control activateCell(Composite parent, Object originalCanonicalValue) {
1908 this.control = createEditorControl(parent);
1915 private class DialogCellEditor extends AbstractDialogCellEditor {
1917 DialogModifier dialogModifier;
1923 boolean closed = false;
1925 public DialogCellEditor(TreeNode node, int column, DialogModifier dialogModifier) {
1926 this.dialogModifier = dialogModifier;
1928 this.column = column;
1933 sem = new Semaphore(1);
1934 Consumer<String> callback = result -> {
1938 String status = dialogModifier.query(this.parent.getShell(), null, column, node.getContext(), callback);
1939 if (status != null) {
1941 return Window.CANCEL;
1946 } catch (InterruptedException e) {
1947 e.printStackTrace();
1954 public DialogModifier createDialogInstance() {
1956 return dialogModifier;
1960 public Object getDialogInstance() {
1961 return (DialogModifier)this.dialog;
1965 public void close() {
1970 public Object getEditorValue() {
1975 public void setEditorValue(Object value) {
1976 // dialog modifier handles this internally
1980 public boolean isClosed() {
1988 * The job that is used for off-loading image loading tasks (see
1989 * {@link ImageTask} to a worker thread from the main UI thread.
1991 ImageLoaderJob imageLoaderJob;
1993 // Map<NodeContext, ImageTask> imageTasks = new THashMap<NodeContext, ImageTask>();
1994 Map<TreeNode, ImageTask> imageTasks = new THashMap<TreeNode, ImageTask>();
1996 void queueImageTask(TreeNode node, ImageTask task) {
1997 synchronized (imageTasks) {
1998 imageTasks.put(node, task);
2000 imageLoaderJob.scheduleIfNecessary(100);
2004 * Invoked in a job worker thread.
2009 protected IStatus setPendingImages(IProgressMonitor monitor) {
2010 ImageTask[] tasks = null;
2011 synchronized (imageTasks) {
2012 tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]);
2016 MultiStatus status = null;
2018 // Load missing images
2019 for (ImageTask task : tasks) {
2020 Object desc = task.descsOrImage;
2021 if (desc instanceof ImageDescriptor) {
2023 desc = resourceManager.get((ImageDescriptor) desc);
2024 task.descsOrImage = desc;
2025 } catch (DeviceResourceException e) {
2027 status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null);
2028 status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e));
2034 // Perform final UI updates in the UI thread.
2035 final ImageTask[] _tasks = tasks;
2036 thread.asyncExec(new Runnable() {
2043 return status != null ? status : Status.OK_STATUS;
2047 void setImages(ImageTask[] tasks) {
2048 for (ImageTask task : tasks)
2053 void setImage(ImageTask task) {
2054 if (!task.node.isDisposed())
2055 update(task.node, 0);
2058 private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider {
2060 private NatTableGraphExplorer ge;
2062 GraphExplorerPostSelectionProvider(NatTableGraphExplorer ge) {
2071 public void setSelection(final ISelection selection) {
2072 if(ge == null) return;
2073 ge.setSelection(selection, false);
2079 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
2080 if(ge == null) return;
2081 if(ge.isDisposed()) {
2082 if (DEBUG_SELECTION_LISTENERS)
2083 System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener);
2086 ge.selectionProvider.removeSelectionChangedListener(listener);
2090 public void addPostSelectionChangedListener(ISelectionChangedListener listener) {
2091 if(ge == null) return;
2092 if (!ge.thread.currentThreadAccess())
2093 throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2094 if(ge.isDisposed()) {
2095 System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener);
2098 ge.selectionProvider.addPostSelectionChangedListener(listener);
2102 public void removePostSelectionChangedListener(ISelectionChangedListener listener) {
2103 if(ge == null) return;
2104 if(ge.isDisposed()) {
2105 if (DEBUG_SELECTION_LISTENERS)
2106 System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener);
2109 ge.selectionProvider.removePostSelectionChangedListener(listener);
2114 public void addSelectionChangedListener(ISelectionChangedListener listener) {
2115 if(ge == null) return;
2116 if (!ge.thread.currentThreadAccess())
2117 throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2118 if (ge.natTable.isDisposed() || ge.selectionProvider == null) {
2119 System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener);
2123 ge.selectionProvider.addSelectionChangedListener(listener);
2128 public ISelection getSelection() {
2129 if(ge == null) return StructuredSelection.EMPTY;
2130 if (!ge.thread.currentThreadAccess())
2131 throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread());
2132 if (ge.natTable.isDisposed() || ge.selectionProvider == null)
2133 return StructuredSelection.EMPTY;
2134 return ge.selectionProvider.getSelection();
2139 static class ModifierValidator implements ICellEditorValidator {
2140 private Modifier modifier;
2141 public ModifierValidator(Modifier modifier) {
2142 this.modifier = modifier;
2146 public String isValid(Object value) {
2147 return modifier.isValid((String)value);
2151 static class UpdateRunner implements Runnable {
2153 final NatTableGraphExplorer ge;
2155 UpdateRunner(NatTableGraphExplorer ge, IGraphExplorerContext geContext) {
2162 } catch (Throwable t) {
2163 t.printStackTrace();
2167 public void doRun() {
2169 if (ge.isDisposed())
2172 HashSet<UpdateItem> items;
2174 ScrollBar verticalBar = ge.natTable.getVerticalBar();
2177 synchronized (ge.pendingItems) {
2178 items = ge.pendingItems;
2179 ge.pendingItems = new HashSet<UpdateItem>();
2181 if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size());
2183 //ge.natTable.setRedraw(false);
2184 for (UpdateItem item : items) {
2185 item.update(ge.natTable);
2188 // check if vertical scroll bar has become visible and refresh layout.
2189 boolean currentlyVerticalBarVisible = verticalBar.isVisible();
2190 if (ge.verticalBarVisible != currentlyVerticalBarVisible) {
2191 ge.verticalBarVisible = currentlyVerticalBarVisible;
2192 ge.natTable.getParent().layout();
2195 //ge.natTable.setRedraw(true);
2197 synchronized (ge.pendingItems) {
2198 if (!ge.scheduleUpdater()) {
2199 ge.updating = false;
2211 private static void printDebug(NatTableGraphExplorer ge) {
2212 ge.printTree(ge.rootNode, 0);
2213 System.out.println("Expanded");
2214 for (TreeNode n : ge.treeLayer.expanded)
2215 System.out.println(n);
2216 System.out.println("Expanded end");
2217 System.out.println("Hidden ");
2218 for (int i : ge.treeLayer.getHiddenRowIndexes()) {
2219 System.out.print(i + " ");
2221 System.out.println();
2222 // Display.getCurrent().timerExec(1000, new Runnable() {
2225 // public void run() {
2226 // System.out.println("Hidden delayed ");
2227 // for (int i : ge.treeLayer.getHiddenRowIndexes()) {
2228 // System.out.print(i + " ");
2230 // System.out.println();
2236 public static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {
2237 // This is for query debugging only.
2239 private NatTableGraphExplorer ge;
2240 int queryIndent = 0;
2242 GECache2 cache = new GECache2();
2243 AtomicBoolean propagating = new AtomicBoolean(false);
2244 Object propagateList = new Object();
2245 Object propagate = new Object();
2246 List<Runnable> scheduleList = new ArrayList<Runnable>();
2247 final Deque<Integer> activity = new LinkedList<Integer>();
2248 int activityInt = 0;
2250 AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();
2253 * Keeps track of nodes that have already been auto-expanded. After
2254 * being inserted into this set, nodes will not be forced to stay in an
2255 * expanded state after that. This makes it possible for the user to
2256 * close auto-expanded nodes.
2258 Map<NodeContext, Boolean> autoExpanded = new WeakHashMap<NodeContext, Boolean>();
2260 public GeViewerContext(NatTableGraphExplorer ge) {
2264 public MapList<NodeContext,TreeNode> getContextToNodeMap() {
2267 return ge.contextToNodeMap;
2270 public NatTableGraphExplorer getGe() {
2275 protected void doDispose() {
2277 autoExpanded.clear();
2281 public IGECache getCache() {
2286 public int queryIndent() {
2291 public int queryIndent(int offset) {
2292 queryIndent += offset;
2297 @SuppressWarnings("unchecked")
2298 public <T> NodeQueryProcessor<T> getProcessor(Object o) {
2301 return ge.processors.get(o);
2305 @SuppressWarnings("unchecked")
2306 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {
2307 return ge.primitiveProcessors.get(o);
2310 @SuppressWarnings("unchecked")
2312 public <T> DataSource<T> getDataSource(Class<T> clazz) {
2313 return ge.dataSources.get(clazz);
2317 public void update(UIElementReference ref) {
2318 if (ref instanceof ViewerCellReference) {
2319 ViewerCellReference tiref = (ViewerCellReference) ref;
2320 Object element = tiref.getElement();
2321 int columnIndex = tiref.getColumn();
2322 // NOTE: must be called regardless of the the item value.
2323 // A null item is currently used to indicate a tree root update.
2324 ge.update((TreeNode)element,columnIndex);
2325 } else if (ref instanceof ViewerRowReference) {
2326 ViewerRowReference rref = (ViewerRowReference)ref;
2327 Object element = rref.getElement();
2328 ge.update((TreeNode)element);
2330 throw new IllegalArgumentException("Ui Reference is unknkown " + ref);
2335 public Object getPropagateLock() {
2340 public Object getPropagateListLock() {
2341 return propagateList;
2345 public boolean isPropagating() {
2346 return propagating.get();
2350 public void setPropagating(boolean b) {
2351 this.propagating.set(b);
2355 public List<Runnable> getScheduleList() {
2356 return scheduleList;
2360 public void setScheduleList(List<Runnable> list) {
2361 this.scheduleList = list;
2365 public Deque<Integer> getActivity() {
2370 public void setActivityInt(int i) {
2371 this.activityInt = i;
2375 public int getActivityInt() {
2380 public void scheduleQueryUpdate(Runnable r) {
2383 if (ge.isDisposed())
2385 if (currentQueryUpdater.compareAndSet(null, r)) {
2386 ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER);
2390 Runnable QUERY_UPDATE_SCHEDULER = new Runnable() {
2393 Runnable r = currentQueryUpdater.getAndSet(null);
2400 public void close() {
2402 cache = new DummyCache();
2403 scheduleList.clear();
2404 autoExpanded.clear();
2408 public void dispose() {
2410 cache = new DummyCache();
2411 scheduleList.clear();
2412 autoExpanded.clear();
2413 autoExpanded = null;
2419 private class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor<Boolean> implements
2420 IsExpandedProcessor, ProcessorLifecycle {
2422 * The set of currently expanded node contexts.
2424 private final HashSet<NodeContext> expanded = new HashSet<NodeContext>();
2425 private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();
2427 private NatTable natTable;
2428 private List<TreeNode> list;
2430 public TreeNodeIsExpandedProcessor() {
2434 public Object getIdentifier() {
2435 return BuiltinKeys.IS_EXPANDED;
2439 public String toString() {
2440 return "IsExpandedProcessor";
2444 public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Boolean> key) {
2445 boolean isExpanded = expanded.contains(context);
2446 expandedQueries.put(context, updater);
2447 return Boolean.valueOf(isExpanded);
2451 public Collection<NodeContext> getExpanded() {
2452 return new HashSet<NodeContext>(expanded);
2456 public boolean getExpanded(NodeContext context) {
2457 return this.expanded.contains(context);
2461 public boolean setExpanded(NodeContext context, boolean expanded) {
2462 return _setExpanded(context, expanded);
2466 public boolean replaceExpanded(NodeContext context, boolean expanded) {
2467 return nodeStatusChanged(context, expanded);
2470 private boolean _setExpanded(NodeContext context, boolean expanded) {
2472 return this.expanded.add(context);
2474 return this.expanded.remove(context);
2478 ILayerListener treeListener = new ILayerListener() {
2481 public void handleLayerEvent(ILayerEvent event) {
2482 if (event instanceof ShowRowPositionsEvent) {
2483 ShowRowPositionsEvent e = (ShowRowPositionsEvent)event;
2484 for (Range r : e.getRowPositionRanges()) {
2485 int expanded = viewportLayer.getRowIndexByPosition(r.start-2)+1;
2486 if (DEBUG)System.out.println("IsExpandedProcessor expand " + expanded);
2487 if (expanded < 0 || expanded >= list.size()) {
2490 nodeStatusChanged(list.get(expanded).getContext(), true);
2492 } else if (event instanceof HideRowPositionsEvent) {
2493 HideRowPositionsEvent e = (HideRowPositionsEvent)event;
2494 for (Range r : e.getRowPositionRanges()) {
2495 int collapsed = viewportLayer.getRowIndexByPosition(r.start-2);
2496 if (DEBUG)System.out.println("IsExpandedProcessor collapse " + collapsed);
2497 if (collapsed < 0 || collapsed >= list.size()) {
2500 nodeStatusChanged(list.get(collapsed).getContext(), false);
2507 protected boolean nodeStatusChanged(NodeContext context, boolean expanded) {
2508 boolean result = _setExpanded(context, expanded);
2509 PrimitiveQueryUpdater updater = expandedQueries.get(context);
2510 if (updater != null)
2511 updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded);
2516 public void attached(GraphExplorer explorer) {
2517 Object control = explorer.getControl();
2518 if (control instanceof NatTable) {
2519 this.natTable = (NatTable) control;
2520 this.list = ((NatTableGraphExplorer)explorer).list;
2521 natTable.addLayerListener(treeListener);
2524 System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control);
2529 public void clear() {
2531 expandedQueries.clear();
2535 public void detached(GraphExplorer explorer) {
2537 if (natTable != null) {
2538 natTable.removeLayerListener(treeListener);
2539 // natTable.removeListener(SWT.Expand, treeListener);
2540 // natTable.removeListener(SWT.Collapse, treeListener);
2546 private void printTree(TreeNode node, int depth) {
2548 for (int i = 0; i < depth; i++) {
2552 System.out.println(s);
2554 for (TreeNode n : node.getChildren()) {
2561 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used)
2563 final private static class GECacheKey {
2565 private NodeContext context;
2566 private CacheKey<?> key;
2568 GECacheKey(NodeContext context, CacheKey<?> key) {
2569 this.context = context;
2571 if (context == null || key == null)
2572 throw new IllegalArgumentException("Null context or key is not accepted");
2575 GECacheKey(GECacheKey other) {
2576 this.context = other.context;
2577 this.key = other.key;
2578 if (context == null || key == null)
2579 throw new IllegalArgumentException("Null context or key is not accepted");
2582 void setValues(NodeContext context, CacheKey<?> key) {
2583 this.context = context;
2585 if (context == null || key == null)
2586 throw new IllegalArgumentException("Null context or key is not accepted");
2590 public int hashCode() {
2591 return context.hashCode() | key.hashCode();
2595 public boolean equals(Object object) {
2599 else if (object == null)
2602 GECacheKey i = (GECacheKey) object;
2604 return key.equals(i.key) && context.equals(i.context);
2611 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data.
2613 public static class GECache2 implements IGECache {
2615 final HashMap<GECacheKey, IGECacheEntry> entries = new HashMap<GECacheKey, IGECacheEntry>();
2616 final HashMap<GECacheKey, Set<UIElementReference>> treeReferences = new HashMap<GECacheKey, Set<UIElementReference>>();
2617 final HashMap<NodeContext, Set<GECacheKey>> keyRefs = new HashMap<NodeContext, Set<GECacheKey>>();
2618 private TObjectIntHashMap<NodeContext> references = new TObjectIntHashMap<NodeContext>();
2621 * This single instance is used for all get operations from the cache. This
2622 * should work since the GE cache is meant to be single-threaded within the
2623 * current UI thread, what ever that thread is. For put operations which
2624 * store the key, this is not used.
2626 NodeContext getNC = new NodeContext() {
2627 @SuppressWarnings("rawtypes")
2629 public Object getAdapter(Class adapter) {
2634 public <T> T getConstant(ConstantKey<T> key) {
2639 public Set<ConstantKey<?>> getKeys() {
2640 return Collections.emptySet();
2643 CacheKey<?> getCK = new CacheKey<Object>() {
2645 public Object processorIdenfitier() {
2649 GECacheKey getKey = new GECacheKey(getNC, getCK);
2652 private void addKey(GECacheKey key) {
2653 Set<GECacheKey> refs = keyRefs.get(key.context);
2657 refs = new HashSet<GECacheKey>();
2659 keyRefs.put(key.context, refs);
2663 private void removeKey(GECacheKey key) {
2664 Set<GECacheKey> refs = keyRefs.get(key.context);
2670 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key, T value) {
2671 // if (DEBUG) System.out.println("Add entry " + context + " " + key);
2672 IGECacheEntry entry = new GECacheEntry(context, key, value);
2673 GECacheKey gekey = new GECacheKey(context, key);
2674 entries.put(gekey, entry);
2679 @SuppressWarnings("unchecked")
2680 public <T> T get(NodeContext context, CacheKey<T> key) {
2681 getKey.setValues(context, key);
2682 IGECacheEntry entry = entries.get(getKey);
2685 return (T) entry.getValue();
2689 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
2690 assert(context != null);
2691 assert(key != null);
2692 getKey.setValues(context, key);
2693 return entries.get(getKey);
2697 public <T> void remove(NodeContext context, CacheKey<T> key) {
2698 // if (DEBUG) System.out.println("Remove entry " + context + " " + key);
2699 getKey.setValues(context, key);
2700 entries.remove(getKey);
2705 public <T> Set<UIElementReference> getTreeReference(NodeContext context, CacheKey<T> key) {
2706 assert(context != null);
2707 assert(key != null);
2708 getKey.setValues(context, key);
2709 return treeReferences.get(getKey);
2713 public <T> void putTreeReference(NodeContext context, CacheKey<T> key, UIElementReference reference) {
2714 assert(context != null);
2715 assert(key != null);
2716 //if (DEBUG) System.out.println("Add tree reference " + context + " " + key);
2717 getKey.setValues(context, key);
2718 Set<UIElementReference> refs = treeReferences.get(getKey);
2720 refs.add(reference);
2722 refs = new HashSet<UIElementReference>(4);
2723 refs.add(reference);
2724 GECacheKey gekey = new GECacheKey(getKey);
2725 treeReferences.put(gekey, refs);
2731 public <T> Set<UIElementReference> removeTreeReference(NodeContext context, CacheKey<T> key) {
2732 assert(context != null);
2733 assert(key != null);
2734 //if (DEBUG) System.out.println("Remove tree reference " + context + " " + key);
2735 getKey.setValues(context, key);
2737 return treeReferences.remove(getKey);
2741 public boolean isShown(NodeContext context) {
2742 return references.get(context) > 0;
2748 public void incRef(NodeContext context) {
2749 int exist = references.get(context);
2750 references.put(context, exist+1);
2754 public void decRef(NodeContext context) {
2755 int exist = references.get(context);
2756 references.put(context, exist-1);
2758 references.remove(context);
2762 public void dispose() {
2765 treeReferences.clear();
2769 public void dispose(NodeContext context) {
2770 Set<GECacheKey> keys = keyRefs.remove(context);
2772 for (GECacheKey key : keys) {
2773 entries.remove(key);
2774 treeReferences.remove(key);
2782 * Non-functional cache to replace actual cache when GEContext is disposed.
2787 private static class DummyCache extends GECache2 {
2790 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
2795 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key,
2801 public <T> void putTreeReference(NodeContext context, CacheKey<T> key,
2802 UIElementReference reference) {
2806 public <T> T get(NodeContext context, CacheKey<T> key) {
2811 public <T> Set<UIElementReference> getTreeReference(
2812 NodeContext context, CacheKey<T> key) {
2817 public <T> void remove(NodeContext context, CacheKey<T> key) {
2822 public <T> Set<UIElementReference> removeTreeReference(
2823 NodeContext context, CacheKey<T> key) {
2828 public boolean isShown(NodeContext context) {
2833 public void incRef(NodeContext context) {
2838 public void decRef(NodeContext context) {
2843 public void dispose() {
2848 private class NatTableHeaderMenuConfiguration extends AbstractHeaderMenuConfiguration {
2851 public NatTableHeaderMenuConfiguration(NatTable natTable) {
2856 protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) {
2857 return super.createColumnHeaderMenu(natTable)
2858 .withHideColumnMenuItem()
2859 .withShowAllColumnsMenuItem()
2860 .withAutoResizeSelectedColumnsMenuItem();
2864 protected PopupMenuBuilder createCornerMenu(NatTable natTable) {
2865 return super.createCornerMenu(natTable)
2866 .withShowAllColumnsMenuItem();
2869 protected PopupMenuBuilder createRowHeaderMenu(NatTable natTable) {
2870 return super.createRowHeaderMenu(natTable);
2874 private static class RelativeAlternatingRowConfigLabelAccumulator extends AlternatingRowConfigLabelAccumulator {
2877 public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) {
2878 configLabels.addLabel((rowPosition % 2 == 0 ? EVEN_ROW_CONFIG_TYPE : ODD_ROW_CONFIG_TYPE));
2883 public Object getClicked(Object event) {
2884 MouseEvent e = (MouseEvent)event;
2885 final NatTable tree = (NatTable) e.getSource();
2886 Point point = new Point(e.x, e.y);
2887 int y = natTable.getRowPositionByY(point.y);
2888 int x = natTable.getColumnPositionByX(point.x);
2891 return list.get(y-1);