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 // Disposing NatTable here causes SWT isDisposed exception when GraphExplorer composite disposes DragSource.
1048 // if (natTable != null) {
1049 // natTable.dispose();
1055 viewportLayer = null;
1056 selectionLayer = null;
1057 columnHeaderDataProvider = null;
1058 columnAccessor = null;
1059 rowHeaderDataLayer = null;
1060 columnHeaderDataLayer = null;
1061 cornerDataLayer = null;
1066 public boolean select(NodeContext context) {
1068 assertNotDisposed();
1070 if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) {
1071 StructuredSelection s = new StructuredSelection();
1072 selectionAdaptor.setSelection(s);
1073 selectionProvider.setAndFireNonEqualSelection(s);
1077 selectionAdaptor.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0)));
1083 public boolean select(TreeNode node) {
1084 assertNotDisposed();
1086 if (!list.contains(node)) {
1087 StructuredSelection s = new StructuredSelection();
1088 selectionAdaptor.setSelection(s);
1089 selectionProvider.setAndFireNonEqualSelection(s);
1092 selectionAdaptor.setSelection(new StructuredSelection(node));
1096 public void show(TreeNode node) {
1097 int index = node.getListIndex();
1099 int position = treeLayer.getRowPositionByIndex(index);
1101 treeLayer.expandToTreeRow(index);
1102 position = treeLayer.getRowPositionByIndex(index);
1104 viewportLayer.moveRowPositionIntoViewport(position);
1108 public boolean selectPath(Collection<NodeContext> contexts) {
1110 if(contexts == null) throw new IllegalArgumentException("Null list is not allowed");
1111 if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed");
1113 return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0);
1117 private boolean selectPathInternal(NodeContext[] contexts, int position) {
1119 NodeContext head = contexts[position];
1121 if(position == contexts.length-1) {
1122 return select(head);
1126 setExpanded(head, true);
1127 if(!waitVisible(contexts[position+1])) return false;
1129 return selectPathInternal(contexts, position+1);
1133 private boolean waitVisible(NodeContext context) {
1134 long start = System.nanoTime();
1135 while(!isVisible(context)) {
1136 Display.getCurrent().readAndDispatch();
1137 long duration = System.nanoTime() - start;
1138 if(duration > 10e9) return false;
1144 public boolean isVisible(NodeContext context) {
1145 if (contextToNodeMap.getValuesUnsafe(context).size() == 0)
1148 return true; //FIXME
1149 // Object elements[] = viewer.getVisibleExpandedElements();
1150 // return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0));
1156 public TransientExplorerState getTransientState() {
1157 if (!thread.currentThreadAccess())
1158 throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread());
1159 return transientState;
1163 public <T> T query(NodeContext context, CacheKey<T> key) {
1164 return this.explorerContext.cache.get(context, key);
1168 * For setting a more local service locator for the explorer than the global
1169 * workbench service locator. Sometimes required to give this implementation
1170 * access to local workbench services like IFocusService.
1173 * Must be invoked during right after construction.
1175 * @param serviceLocator
1176 * a specific service locator or <code>null</code> to use the
1177 * workbench global service locator
1179 public void setServiceLocator(IServiceLocator serviceLocator) {
1180 if (serviceLocator == null && PlatformUI.isWorkbenchRunning())
1181 serviceLocator = PlatformUI.getWorkbench();
1182 this.serviceLocator = serviceLocator;
1183 if (serviceLocator != null) {
1184 this.contextService = (IContextService) serviceLocator.getService(IContextService.class);
1185 this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class);
1189 private void detachPrimitiveProcessors() {
1190 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
1191 if (p instanceof ProcessorLifecycle) {
1192 ((ProcessorLifecycle) p).detached(this);
1197 private void clearPrimitiveProcessors() {
1198 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
1199 if (p instanceof ProcessorLifecycle) {
1200 ((ProcessorLifecycle) p).clear();
1206 public void setExpanded(NodeContext context, boolean expanded) {
1207 for (TreeNode n : contextToNodeMap.getValues(context)) {
1209 treeLayer.expandTreeRow(n.getListIndex());
1211 treeLayer.collapseTreeRow(n.getListIndex());
1217 public void setAutoExpandLevel(int level) {
1218 this.autoExpandLevel = level;
1219 treeLayer.expandAllToLevel(level);
1222 int maxChildren = DEFAULT_MAX_CHILDREN;
1225 public int getMaxChildren() {
1230 public void setMaxChildren(int maxChildren) {
1231 this.maxChildren = maxChildren;
1236 public int getMaxChildren(NodeQueryManager manager, NodeContext context) {
1237 Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN);
1238 //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result);
1239 if (result != null) {
1241 throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result);
1248 public <T> NodeQueryProcessor<T> getProcessor(QueryKey<T> key) {
1249 return explorerContext.getProcessor(key);
1253 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(PrimitiveQueryKey<T> key) {
1254 return explorerContext.getPrimitiveProcessor(key);
1257 private HashSet<UpdateItem> pendingItems = new HashSet<UpdateItem>();
1258 private boolean updating = false;
1259 private int updateCounter = 0;
1260 final ScheduledExecutorService uiUpdateScheduler = ThreadUtils.getNonBlockingWorkExecutor();
1262 private class UpdateItem {
1266 public UpdateItem(TreeNode element) {
1270 public UpdateItem(TreeNode element, int columnIndex) {
1271 this.element = element;
1272 this.columnIndex = columnIndex;
1273 if (element != null && element.isDisposed()) {
1274 throw new IllegalArgumentException("Node is disposed. " + element);
1278 public void update(NatTable natTable) {
1279 if (element != null) {
1281 if (element.isDisposed()) {
1284 if (element.updateChildren()) {
1286 System.out.println("Update Item updateChildren " + element.listIndex + " " + element);
1287 printDebug(NatTableGraphExplorer.this);
1290 if (!element.isHidden()) {
1291 if (!element.isExpanded()) {
1292 if (element.listIndex >= 0)
1293 treeLayer.collapseTreeRow(element.listIndex);
1295 System.out.println("Update Item collapse " + element.listIndex);
1296 printDebug(NatTableGraphExplorer.this);
1299 for (TreeNode c : element.getChildren())
1303 TreeNode p = element.getCollapsedAncestor();
1305 if (element.listIndex >= 0)
1306 treeLayer.collapseTreeRow(element.listIndex);
1307 if (p.listIndex >= 0)
1308 treeLayer.collapseTreeRow(p.listIndex);
1310 System.out.println("Update Item ancetor collapse " + p.listIndex);
1311 printDebug(NatTableGraphExplorer.this);
1316 // if (columnIndex >= 0) {
1317 // viewer.update(element, new String[]{columns[columnIndex].getKey()});
1319 // viewer.refresh(element,true);
1325 if (!element.autoExpanded && !element.isDisposed() && autoExpandLevel > 1 && !element.isExpanded() && element.getDepth() <= autoExpandLevel) {
1327 element.autoExpanded = true;
1329 if (DEBUG) System.out.println("Update Item expand " + element.listIndex);
1330 treeLayer.expandTreeRow(element.getListIndex());
1331 //viewer.setExpandedState(element, true);
1335 if (rootNode.updateChildren()) {
1342 public boolean equals(Object obj) {
1345 if (obj.getClass() != getClass())
1347 UpdateItem other = (UpdateItem)obj;
1348 if (columnIndex != other.columnIndex)
1350 if (element != null)
1351 return element.equals(other.element);
1352 return other.element == null;
1356 public int hashCode() {
1357 if (element != null)
1358 return element.hashCode() + columnIndex;
1363 private void update(final TreeNode element, final int columnIndex) {
1364 if (natTable.isDisposed())
1366 if (element != null && element.isDisposed())
1368 if (DEBUG) System.out.println("update " + element + " " + columnIndex);
1369 synchronized (pendingItems) {
1370 pendingItems.add(new UpdateItem(element, columnIndex));
1371 if (updating) return;
1377 private void update(final TreeNode element) {
1379 if (natTable.isDisposed())
1381 if (element != null && element.isDisposed())
1383 if (DEBUG) System.out.println("update " + element);
1384 synchronized (pendingItems) {
1385 pendingItems.add(new UpdateItem(element));
1386 if (updating) return;
1392 boolean scheduleUpdater() {
1394 if (natTable.isDisposed())
1397 if (!pendingItems.isEmpty()) {
1399 int activity = explorerContext.activityInt;
1401 if (activity < 100) {
1402 //System.out.println("Scheduling update immediately.");
1403 } else if (activity < 1000) {
1404 //System.out.println("Scheduling update after 500ms.");
1407 //System.out.println("Scheduling update after 3000ms.");
1413 //System.out.println("Scheduling UI update after " + delay + " ms.");
1414 uiUpdateScheduler.schedule(new Runnable() {
1418 if (natTable == null || natTable.isDisposed())
1421 if (updateCounter > 0) {
1423 uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS);
1425 natTable.getDisplay().asyncExec(new UpdateRunner(NatTableGraphExplorer.this, NatTableGraphExplorer.this.explorerContext));
1429 }, delay, TimeUnit.MILLISECONDS);
1439 public String startEditing(NodeContext context, String columnKey) {
1440 assertNotDisposed();
1441 if (!thread.currentThreadAccess())
1442 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
1444 if(columnKey.startsWith("#")) {
1445 columnKey = columnKey.substring(1);
1448 Integer columnIndex = columnKeyToIndex.get(columnKey);
1449 if (columnIndex == null)
1450 return "Rename not supported for selection";
1452 // viewer.editElement(context, columnIndex);
1453 // if(viewer.isCellEditorActive()) return null;
1454 return "Rename not supported for selection";
1458 public String startEditing(String columnKey) {
1459 ISelection selection = postSelectionProvider.getSelection();
1460 if(selection == null) return "Rename not supported for selection";
1461 NodeContext context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class);
1462 if(context == null) return "Rename not supported for selection";
1464 return startEditing(context, columnKey);
1468 public void setSelection(final ISelection selection, boolean forceControlUpdate) {
1469 assertNotDisposed();
1470 boolean equalsOld = selectionProvider.selectionEquals(selection);
1471 if (equalsOld && !forceControlUpdate) {
1472 // Just set the selection object instance, fire no events nor update
1473 // the viewer selection.
1474 selectionProvider.setSelection(selection);
1476 Collection<NodeContext> coll = AdaptionUtils.adaptToCollection(selection, NodeContext.class);
1477 Collection<TreeNode> nodes = new ArrayList<TreeNode>();
1478 for (NodeContext c : coll) {
1479 List<TreeNode> match = contextToNodeMap.getValuesUnsafe(c);
1480 if(match.size() > 0)
1481 nodes.add(match.get(0));
1483 final ISelection sel = new StructuredSelection(nodes.toArray());
1484 if (coll.size() == 0)
1486 // Schedule viewer and selection update if necessary.
1487 if (natTable.isDisposed())
1489 Display d = natTable.getDisplay();
1490 if (d.getThread() == Thread.currentThread()) {
1491 selectionAdaptor.setSelection(sel);
1493 d.asyncExec(new Runnable() {
1496 if (natTable.isDisposed())
1498 selectionAdaptor.setSelection(sel);
1506 public void setModificationContext(ModificationContext modificationContext) {
1507 this.modificationContext = modificationContext;
1511 final ExecutorService queryUpdateScheduler = Threads.getExecutor();
1514 public static double getDisplayScale() {
1515 Point dpi = Display.getCurrent().getDPI();
1516 return (double)dpi.x/96.0;
1519 private void createNatTable(int style) {
1520 GETreeData treeData = new GETreeData(list);
1521 GETreeRowModel<TreeNode> treeRowModel = new GETreeRowModel<TreeNode>(treeData);
1522 columnAccessor = new GEColumnAccessor(this);
1524 IDataProvider dataProvider = new ListDataProvider<TreeNode>(list, columnAccessor);
1526 // FIXME: NatTable 1.0 required help to work with custom display scaling (Windows 7 display scaling).
1527 // It seems that NatTable 1.4 breaks with the same code in Windows 7, so now the code is disabled.
1528 // More testing with different hardware is required...
1529 // int defaultFontSize = 12;
1530 // int height = (int)Math.ceil(((double)(defaultFontSize))*getDisplayScale()) + DataLayer.DEFAULT_ROW_HEIGHT-defaultFontSize;
1531 // dataLayer = new DataLayer(dataProvider, DataLayer.DEFAULT_COLUMN_WIDTH, height);
1532 dataLayer = new DataLayer(dataProvider);
1534 // resizable rows are unnecessary in Sulca report.
1535 dataLayer.setRowsResizableByDefault(false);
1538 DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(dataProvider);
1539 rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider);
1541 // adjust row header column width so that row numbers fit into the column.
1542 //adjustRowHeaderWidth(list.size());
1544 // Column header layer
1545 columnHeaderDataProvider = new GEColumnHeaderDataProvider(this, dataLayer);
1546 columnHeaderDataLayer = new DefaultColumnHeaderDataLayer(columnHeaderDataProvider);
1547 //columnHeaderDataLayer.setDefaultRowHeight(height);
1548 columnHeaderDataProvider.updateColumnSizes();
1550 //ISortModel sortModel = new EcoSortModel(this, generator,dataLayer);
1552 // Column re-order + hide
1553 ColumnReorderLayer columnReorderLayer = new ColumnReorderLayer(dataLayer);
1554 ColumnHideShowLayer columnHideShowLayer = new ColumnHideShowLayer(columnReorderLayer);
1557 treeLayer = new GETreeLayer(columnHideShowLayer, treeRowModel, false);
1559 selectionLayer = new SelectionLayer(treeLayer);
1561 viewportLayer = new ViewportLayer(selectionLayer);
1563 ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, viewportLayer, selectionLayer);
1564 // Note: The column header layer is wrapped in a filter row composite.
1565 // This plugs in the filter row functionality
1567 ColumnOverrideLabelAccumulator labelAccumulator = new ColumnOverrideLabelAccumulator(columnHeaderDataLayer);
1568 columnHeaderDataLayer.setConfigLabelAccumulator(labelAccumulator);
1571 //SortHeaderLayer<TreeNode> sortHeaderLayer = new SortHeaderLayer<TreeNode>(columnHeaderLayer, sortModel, false);
1573 RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, viewportLayer, selectionLayer);
1576 DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider);
1577 cornerDataLayer = new DataLayer(cornerDataProvider);
1578 //CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, sortHeaderLayer);
1579 CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, columnHeaderLayer);
1582 //GridLayer gridLayer = new GridLayer(viewportLayer,sortHeaderLayer,rowHeaderLayer, cornerLayer);
1583 GridLayer gridLayer = new GridLayer(viewportLayer, columnHeaderLayer,rowHeaderLayer, cornerLayer, false);
1585 /* Since 1.4.0, alternative row rendering uses row indexes in the original data list.
1586 When combined with collapsed tree rows, rows with odd or even index may end up next to each other,
1587 which defeats the purpose of alternating colors. This overrides that and returns the functionality
1588 that we had with 1.0.1. */
1589 gridLayer.setConfigLabelAccumulatorForRegion(GridRegion.BODY, new RelativeAlternatingRowConfigLabelAccumulator());
1590 gridLayer.addConfiguration(new DefaultEditConfiguration());
1591 //gridLayer.addConfiguration(new DefaultEditBindings());
1592 gridLayer.addConfiguration(new GEEditBindings());
1594 natTable = new NatTable(composite,gridLayer,false);
1596 //selectionLayer.registerCommandHandler(new EcoCopyDataCommandHandler(selectionLayer,columnHeaderDataLayer,columnAccessor, columnHeaderDataProvider));
1598 natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable));
1599 natTable.addConfiguration(new DefaultTreeLayerConfiguration2(treeLayer));
1600 natTable.addConfiguration(new SingleClickSortConfiguration());
1601 //natTable.addLayerListener(this);
1603 natTable.addConfiguration(new GENatTableThemeConfiguration(treeData, style));
1604 natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable));
1606 natTable.addConfiguration(new AbstractRegistryConfiguration() {
1609 public void configureRegistry(IConfigRegistry configRegistry) {
1610 configRegistry.registerConfigAttribute(
1611 EditConfigAttributes.CELL_EDITABLE_RULE,
1612 new IEditableRule() {
1615 public boolean isEditable(ILayerCell cell,
1616 IConfigRegistry configRegistry) {
1617 int col = cell.getColumnIndex();
1618 int row = cell.getRowIndex();
1619 TreeNode node = list.get(row);
1620 Modifier modifier = getModifier(node,col);
1621 return modifier != null;
1626 public boolean isEditable(int columnIndex, int rowIndex) {
1627 // there are no callers?
1632 configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, new AdaptableCellEditor());
1633 configRegistry.registerConfigAttribute(EditConfigAttributes.CONVERSION_ERROR_HANDLER, new DialogErrorHandling(), DisplayMode.EDIT);
1634 configRegistry.registerConfigAttribute(EditConfigAttributes.VALIDATION_ERROR_HANDLER, new DialogErrorHandling(), DisplayMode.EDIT);
1635 configRegistry.registerConfigAttribute(EditConfigAttributes.DATA_VALIDATOR, new AdaptableDataValidator(),DisplayMode.EDIT);
1637 Style conversionErrorStyle = new Style();
1638 conversionErrorStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, GUIHelper.COLOR_RED);
1639 conversionErrorStyle.setAttributeValue(CellStyleAttributes.FOREGROUND_COLOR, GUIHelper.COLOR_WHITE);
1640 configRegistry.registerConfigAttribute(EditConfigAttributes.CONVERSION_ERROR_STYLE, conversionErrorStyle, DisplayMode.EDIT);
1642 configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, new DefaultDisplayConverter(),DisplayMode.EDIT);
1648 if ((style & SWT.BORDER) > 0) {
1649 natTable.addOverlayPainter(new NatTableBorderOverlayPainter());
1652 natTable.configure();
1654 // natTable.addListener(SWT.MenuDetect, new NatTableMenuListener());
1656 // DefaultToolTip toolTip = new EcoCellToolTip(natTable, columnAccessor);
1657 // toolTip.setBackgroundColor(natTable.getDisplay().getSystemColor(SWT.COLOR_WHITE));
1658 // toolTip.setPopupDelay(500);
1659 // toolTip.activate();
1660 // toolTip.setShift(new Point(10, 10));
1663 // menuManager.createContextMenu(composite);
1664 // natTable.setMenu(getMenuManager().getMenu());
1666 selectionAdaptor = new NatTableSelectionAdaptor(natTable, selectionLayer, treeData);
1669 Modifier getModifier(TreeNode element, int columnIndex) {
1670 GENodeQueryManager manager = element.getManager();
1671 final NodeContext context = element.getContext();
1672 Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
1673 if (labeler == null)
1675 Column column = columns[columnIndex];
1677 return labeler.getModifier(modificationContext, column.getKey());
1681 private class AdaptableCellEditor implements ICellEditor {
1685 public Control activateCell(Composite parent, Object originalCanonicalValue, EditModeEnum editMode,
1686 ICellEditHandler editHandler, ILayerCell cell, IConfigRegistry configRegistry) {
1687 int col = cell.getColumnIndex();
1688 int row = cell.getRowIndex();
1689 TreeNode node = list.get(row);
1690 Modifier modifier = getModifier(node, col);
1691 if (modifier == null)
1695 if (modifier instanceof DialogModifier) {
1696 DialogModifier mod = (DialogModifier)modifier;
1697 editor = new DialogCellEditor(node, col, mod);
1698 } else if (modifier instanceof CustomModifier) {
1699 CustomModifier mod = (CustomModifier)modifier;
1700 editor = new CustomCellEditor(node, col, mod);
1701 } else if (modifier instanceof EnumerationModifier) {
1702 EnumerationModifier mod = (EnumerationModifier)modifier;
1703 editor = new ComboBoxCellEditor(mod.getValues());
1705 editor = new TextCellEditor();
1708 return editor.activateCell(parent, originalCanonicalValue, editMode, editHandler, cell, configRegistry);
1712 public int getColumnIndex() {
1713 return editor.getColumnIndex();
1717 public int getRowIndex() {
1718 return editor.getRowIndex();
1722 public int getColumnPosition() {
1723 return editor.getColumnPosition();
1727 public int getRowPosition() {
1728 return editor.getRowPosition();
1732 public Object getEditorValue() {
1733 return editor.getEditorValue();
1737 public void setEditorValue(Object value) {
1738 editor.setEditorValue(value);
1743 public Object getCanonicalValue() {
1744 return editor.getCanonicalValue();
1748 public Object getCanonicalValue(IEditErrorHandler conversionErrorHandler) {
1749 return editor.getCanonicalValue();
1753 public void setCanonicalValue(Object canonicalValue) {
1754 editor.setCanonicalValue(canonicalValue);
1759 public boolean validateCanonicalValue(Object canonicalValue) {
1760 return editor.validateCanonicalValue(canonicalValue);
1764 public boolean validateCanonicalValue(Object canonicalValue, IEditErrorHandler validationErrorHandler) {
1765 return editor.validateCanonicalValue(canonicalValue, validationErrorHandler);
1769 public boolean commit(MoveDirectionEnum direction) {
1770 return editor.commit(direction);
1774 public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit) {
1775 return editor.commit(direction, closeAfterCommit);
1779 public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit, boolean skipValidation) {
1780 return editor.commit(direction, closeAfterCommit, skipValidation);
1784 public void close() {
1790 public boolean isClosed() {
1791 return editor.isClosed();
1795 public Control getEditorControl() {
1796 return editor.getEditorControl();
1800 public Control createEditorControl(Composite parent) {
1801 return editor.createEditorControl(parent);
1805 public boolean openInline(IConfigRegistry configRegistry, List<String> configLabels) {
1806 return EditConfigHelper.openInline(configRegistry, configLabels);
1810 public boolean supportMultiEdit(IConfigRegistry configRegistry, List<String> configLabels) {
1811 return editor.supportMultiEdit(configRegistry, configLabels);
1815 public boolean openMultiEditDialog() {
1816 return editor.openMultiEditDialog();
1820 public boolean openAdjacentEditor() {
1821 return editor.openAdjacentEditor();
1825 public boolean activateAtAnyPosition() {
1830 public boolean activateOnTraversal(IConfigRegistry configRegistry, List<String> configLabels) {
1831 return editor.activateOnTraversal(configRegistry, configLabels);
1835 public void addEditorControlListeners() {
1836 editor.addEditorControlListeners();
1841 public void removeEditorControlListeners() {
1842 editor.removeEditorControlListeners();
1847 public Rectangle calculateControlBounds(Rectangle cellBounds) {
1848 return editor.calculateControlBounds(cellBounds);
1852 private class AdaptableDataValidator implements IDataValidator {
1854 public boolean validate(ILayerCell cell, IConfigRegistry configRegistry, Object newValue) {
1855 int col = cell.getColumnIndex();
1856 int row = cell.getRowIndex();
1857 return validate(col, row, newValue);
1861 public boolean validate(int col, int row, Object newValue) {
1862 TreeNode node = list.get(row);
1863 Modifier modifier = getModifier(node, col);
1864 if (modifier == null)
1867 String err = modifier.isValid(newValue != null ? newValue.toString() : "");
1870 throw new ValidationFailedException(err);
1874 private class CustomCellEditor extends AbstractCellEditor {
1876 CustomModifier customModifier;
1880 public CustomCellEditor(TreeNode node, int column, CustomModifier customModifier) {
1881 this.customModifier = customModifier;
1883 this.column = column;
1887 public Object getEditorValue() {
1888 return customModifier.getValue();
1892 public void setEditorValue(Object value) {
1893 customModifier.modify(value.toString());
1898 public Control getEditorControl() {
1903 public Control createEditorControl(Composite parent) {
1904 return (Control)customModifier.createControl(parent, null, column, node.getContext());
1909 protected Control activateCell(Composite parent, Object originalCanonicalValue) {
1910 this.control = createEditorControl(parent);
1917 private class DialogCellEditor extends AbstractDialogCellEditor {
1919 DialogModifier dialogModifier;
1925 boolean closed = false;
1927 public DialogCellEditor(TreeNode node, int column, DialogModifier dialogModifier) {
1928 this.dialogModifier = dialogModifier;
1930 this.column = column;
1935 sem = new Semaphore(1);
1936 Consumer<String> callback = result -> {
1940 String status = dialogModifier.query(this.parent.getShell(), null, column, node.getContext(), callback);
1941 if (status != null) {
1943 return Window.CANCEL;
1948 } catch (InterruptedException e) {
1949 e.printStackTrace();
1956 public DialogModifier createDialogInstance() {
1958 return dialogModifier;
1962 public Object getDialogInstance() {
1963 return (DialogModifier)this.dialog;
1967 public void close() {
1972 public Object getEditorValue() {
1977 public void setEditorValue(Object value) {
1978 // dialog modifier handles this internally
1982 public boolean isClosed() {
1990 * The job that is used for off-loading image loading tasks (see
1991 * {@link ImageTask} to a worker thread from the main UI thread.
1993 ImageLoaderJob imageLoaderJob;
1995 // Map<NodeContext, ImageTask> imageTasks = new THashMap<NodeContext, ImageTask>();
1996 Map<TreeNode, ImageTask> imageTasks = new THashMap<TreeNode, ImageTask>();
1998 void queueImageTask(TreeNode node, ImageTask task) {
1999 synchronized (imageTasks) {
2000 imageTasks.put(node, task);
2002 imageLoaderJob.scheduleIfNecessary(100);
2006 * Invoked in a job worker thread.
2011 protected IStatus setPendingImages(IProgressMonitor monitor) {
2012 ImageTask[] tasks = null;
2013 synchronized (imageTasks) {
2014 tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]);
2018 MultiStatus status = null;
2020 // Load missing images
2021 for (ImageTask task : tasks) {
2022 Object desc = task.descsOrImage;
2023 if (desc instanceof ImageDescriptor) {
2025 desc = resourceManager.get((ImageDescriptor) desc);
2026 task.descsOrImage = desc;
2027 } catch (DeviceResourceException e) {
2029 status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null);
2030 status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e));
2036 // Perform final UI updates in the UI thread.
2037 final ImageTask[] _tasks = tasks;
2038 thread.asyncExec(new Runnable() {
2045 return status != null ? status : Status.OK_STATUS;
2049 void setImages(ImageTask[] tasks) {
2050 for (ImageTask task : tasks)
2055 void setImage(ImageTask task) {
2056 if (!task.node.isDisposed())
2057 update(task.node, 0);
2060 private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider {
2062 private NatTableGraphExplorer ge;
2064 GraphExplorerPostSelectionProvider(NatTableGraphExplorer ge) {
2073 public void setSelection(final ISelection selection) {
2074 if(ge == null) return;
2075 ge.setSelection(selection, false);
2081 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
2082 if(ge == null) return;
2083 if(ge.isDisposed()) {
2084 if (DEBUG_SELECTION_LISTENERS)
2085 System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener);
2088 ge.selectionProvider.removeSelectionChangedListener(listener);
2092 public void addPostSelectionChangedListener(ISelectionChangedListener listener) {
2093 if(ge == null) return;
2094 if (!ge.thread.currentThreadAccess())
2095 throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2096 if(ge.isDisposed()) {
2097 System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener);
2100 ge.selectionProvider.addPostSelectionChangedListener(listener);
2104 public void removePostSelectionChangedListener(ISelectionChangedListener listener) {
2105 if(ge == null) return;
2106 if(ge.isDisposed()) {
2107 if (DEBUG_SELECTION_LISTENERS)
2108 System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener);
2111 ge.selectionProvider.removePostSelectionChangedListener(listener);
2116 public void addSelectionChangedListener(ISelectionChangedListener listener) {
2117 if(ge == null) return;
2118 if (!ge.thread.currentThreadAccess())
2119 throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2120 if (ge.natTable.isDisposed() || ge.selectionProvider == null) {
2121 System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener);
2125 ge.selectionProvider.addSelectionChangedListener(listener);
2130 public ISelection getSelection() {
2131 if(ge == null) return StructuredSelection.EMPTY;
2132 if (!ge.thread.currentThreadAccess())
2133 throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread());
2134 if (ge.natTable.isDisposed() || ge.selectionProvider == null)
2135 return StructuredSelection.EMPTY;
2136 return ge.selectionProvider.getSelection();
2141 static class ModifierValidator implements ICellEditorValidator {
2142 private Modifier modifier;
2143 public ModifierValidator(Modifier modifier) {
2144 this.modifier = modifier;
2148 public String isValid(Object value) {
2149 return modifier.isValid((String)value);
2153 static class UpdateRunner implements Runnable {
2155 final NatTableGraphExplorer ge;
2157 UpdateRunner(NatTableGraphExplorer ge, IGraphExplorerContext geContext) {
2164 } catch (Throwable t) {
2165 t.printStackTrace();
2169 public void doRun() {
2171 if (ge.isDisposed())
2174 HashSet<UpdateItem> items;
2176 ScrollBar verticalBar = ge.natTable.getVerticalBar();
2179 synchronized (ge.pendingItems) {
2180 items = ge.pendingItems;
2181 ge.pendingItems = new HashSet<UpdateItem>();
2183 if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size());
2185 //ge.natTable.setRedraw(false);
2186 for (UpdateItem item : items) {
2187 item.update(ge.natTable);
2190 // check if vertical scroll bar has become visible and refresh layout.
2191 boolean currentlyVerticalBarVisible = verticalBar.isVisible();
2192 if (ge.verticalBarVisible != currentlyVerticalBarVisible) {
2193 ge.verticalBarVisible = currentlyVerticalBarVisible;
2194 ge.natTable.getParent().layout();
2197 //ge.natTable.setRedraw(true);
2199 synchronized (ge.pendingItems) {
2200 if (!ge.scheduleUpdater()) {
2201 ge.updating = false;
2213 private static void printDebug(NatTableGraphExplorer ge) {
2214 ge.printTree(ge.rootNode, 0);
2215 System.out.println("Expanded");
2216 for (TreeNode n : ge.treeLayer.expanded)
2217 System.out.println(n);
2218 System.out.println("Expanded end");
2219 System.out.println("Hidden ");
2220 for (int i : ge.treeLayer.getHiddenRowIndexes()) {
2221 System.out.print(i + " ");
2223 System.out.println();
2224 // Display.getCurrent().timerExec(1000, new Runnable() {
2227 // public void run() {
2228 // System.out.println("Hidden delayed ");
2229 // for (int i : ge.treeLayer.getHiddenRowIndexes()) {
2230 // System.out.print(i + " ");
2232 // System.out.println();
2238 public static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {
2239 // This is for query debugging only.
2241 private NatTableGraphExplorer ge;
2242 int queryIndent = 0;
2244 GECache2 cache = new GECache2();
2245 AtomicBoolean propagating = new AtomicBoolean(false);
2246 Object propagateList = new Object();
2247 Object propagate = new Object();
2248 List<Runnable> scheduleList = new ArrayList<Runnable>();
2249 final Deque<Integer> activity = new LinkedList<Integer>();
2250 int activityInt = 0;
2252 AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();
2255 * Keeps track of nodes that have already been auto-expanded. After
2256 * being inserted into this set, nodes will not be forced to stay in an
2257 * expanded state after that. This makes it possible for the user to
2258 * close auto-expanded nodes.
2260 Map<NodeContext, Boolean> autoExpanded = new WeakHashMap<NodeContext, Boolean>();
2262 public GeViewerContext(NatTableGraphExplorer ge) {
2266 public MapList<NodeContext,TreeNode> getContextToNodeMap() {
2269 return ge.contextToNodeMap;
2272 public NatTableGraphExplorer getGe() {
2277 protected void doDispose() {
2279 autoExpanded.clear();
2283 public IGECache getCache() {
2288 public int queryIndent() {
2293 public int queryIndent(int offset) {
2294 queryIndent += offset;
2299 @SuppressWarnings("unchecked")
2300 public <T> NodeQueryProcessor<T> getProcessor(Object o) {
2303 return ge.processors.get(o);
2307 @SuppressWarnings("unchecked")
2308 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {
2309 return ge.primitiveProcessors.get(o);
2312 @SuppressWarnings("unchecked")
2314 public <T> DataSource<T> getDataSource(Class<T> clazz) {
2315 return ge.dataSources.get(clazz);
2319 public void update(UIElementReference ref) {
2320 if (ref instanceof ViewerCellReference) {
2321 ViewerCellReference tiref = (ViewerCellReference) ref;
2322 Object element = tiref.getElement();
2323 int columnIndex = tiref.getColumn();
2324 // NOTE: must be called regardless of the the item value.
2325 // A null item is currently used to indicate a tree root update.
2326 ge.update((TreeNode)element,columnIndex);
2327 } else if (ref instanceof ViewerRowReference) {
2328 ViewerRowReference rref = (ViewerRowReference)ref;
2329 Object element = rref.getElement();
2330 ge.update((TreeNode)element);
2332 throw new IllegalArgumentException("Ui Reference is unknkown " + ref);
2337 public Object getPropagateLock() {
2342 public Object getPropagateListLock() {
2343 return propagateList;
2347 public boolean isPropagating() {
2348 return propagating.get();
2352 public void setPropagating(boolean b) {
2353 this.propagating.set(b);
2357 public List<Runnable> getScheduleList() {
2358 return scheduleList;
2362 public void setScheduleList(List<Runnable> list) {
2363 this.scheduleList = list;
2367 public Deque<Integer> getActivity() {
2372 public void setActivityInt(int i) {
2373 this.activityInt = i;
2377 public int getActivityInt() {
2382 public void scheduleQueryUpdate(Runnable r) {
2385 if (ge.isDisposed())
2387 if (currentQueryUpdater.compareAndSet(null, r)) {
2388 ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER);
2392 Runnable QUERY_UPDATE_SCHEDULER = new Runnable() {
2395 Runnable r = currentQueryUpdater.getAndSet(null);
2402 public void close() {
2404 cache = new DummyCache();
2405 scheduleList.clear();
2406 autoExpanded.clear();
2410 public void dispose() {
2412 cache = new DummyCache();
2413 scheduleList.clear();
2414 autoExpanded.clear();
2415 autoExpanded = null;
2421 private class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor<Boolean> implements
2422 IsExpandedProcessor, ProcessorLifecycle {
2424 * The set of currently expanded node contexts.
2426 private final HashSet<NodeContext> expanded = new HashSet<NodeContext>();
2427 private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();
2429 private NatTable natTable;
2430 private List<TreeNode> list;
2432 public TreeNodeIsExpandedProcessor() {
2436 public Object getIdentifier() {
2437 return BuiltinKeys.IS_EXPANDED;
2441 public String toString() {
2442 return "IsExpandedProcessor";
2446 public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Boolean> key) {
2447 boolean isExpanded = expanded.contains(context);
2448 expandedQueries.put(context, updater);
2449 return Boolean.valueOf(isExpanded);
2453 public Collection<NodeContext> getExpanded() {
2454 return new HashSet<NodeContext>(expanded);
2458 public boolean getExpanded(NodeContext context) {
2459 return this.expanded.contains(context);
2463 public boolean setExpanded(NodeContext context, boolean expanded) {
2464 return _setExpanded(context, expanded);
2468 public boolean replaceExpanded(NodeContext context, boolean expanded) {
2469 return nodeStatusChanged(context, expanded);
2472 private boolean _setExpanded(NodeContext context, boolean expanded) {
2474 return this.expanded.add(context);
2476 return this.expanded.remove(context);
2480 ILayerListener treeListener = new ILayerListener() {
2483 public void handleLayerEvent(ILayerEvent event) {
2484 if (event instanceof ShowRowPositionsEvent) {
2485 ShowRowPositionsEvent e = (ShowRowPositionsEvent)event;
2486 for (Range r : e.getRowPositionRanges()) {
2487 int expanded = viewportLayer.getRowIndexByPosition(r.start-2)+1;
2488 if (DEBUG)System.out.println("IsExpandedProcessor expand " + expanded);
2489 if (expanded < 0 || expanded >= list.size()) {
2492 nodeStatusChanged(list.get(expanded).getContext(), true);
2494 } else if (event instanceof HideRowPositionsEvent) {
2495 HideRowPositionsEvent e = (HideRowPositionsEvent)event;
2496 for (Range r : e.getRowPositionRanges()) {
2497 int collapsed = viewportLayer.getRowIndexByPosition(r.start-2);
2498 if (DEBUG)System.out.println("IsExpandedProcessor collapse " + collapsed);
2499 if (collapsed < 0 || collapsed >= list.size()) {
2502 nodeStatusChanged(list.get(collapsed).getContext(), false);
2509 protected boolean nodeStatusChanged(NodeContext context, boolean expanded) {
2510 boolean result = _setExpanded(context, expanded);
2511 PrimitiveQueryUpdater updater = expandedQueries.get(context);
2512 if (updater != null)
2513 updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded);
2518 public void attached(GraphExplorer explorer) {
2519 Object control = explorer.getControl();
2520 if (control instanceof NatTable) {
2521 this.natTable = (NatTable) control;
2522 this.list = ((NatTableGraphExplorer)explorer).list;
2523 natTable.addLayerListener(treeListener);
2526 System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control);
2531 public void clear() {
2533 expandedQueries.clear();
2537 public void detached(GraphExplorer explorer) {
2539 if (natTable != null) {
2540 natTable.removeLayerListener(treeListener);
2541 // natTable.removeListener(SWT.Expand, treeListener);
2542 // natTable.removeListener(SWT.Collapse, treeListener);
2548 private void printTree(TreeNode node, int depth) {
2550 for (int i = 0; i < depth; i++) {
2554 System.out.println(s);
2556 for (TreeNode n : node.getChildren()) {
2563 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used)
2565 final private static class GECacheKey {
2567 private NodeContext context;
2568 private CacheKey<?> key;
2570 GECacheKey(NodeContext context, CacheKey<?> key) {
2571 this.context = context;
2573 if (context == null || key == null)
2574 throw new IllegalArgumentException("Null context or key is not accepted");
2577 GECacheKey(GECacheKey other) {
2578 this.context = other.context;
2579 this.key = other.key;
2580 if (context == null || key == null)
2581 throw new IllegalArgumentException("Null context or key is not accepted");
2584 void setValues(NodeContext context, CacheKey<?> key) {
2585 this.context = context;
2587 if (context == null || key == null)
2588 throw new IllegalArgumentException("Null context or key is not accepted");
2592 public int hashCode() {
2593 return context.hashCode() | key.hashCode();
2597 public boolean equals(Object object) {
2601 else if (object == null)
2604 GECacheKey i = (GECacheKey) object;
2606 return key.equals(i.key) && context.equals(i.context);
2613 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data.
2615 public static class GECache2 implements IGECache {
2617 final HashMap<GECacheKey, IGECacheEntry> entries = new HashMap<GECacheKey, IGECacheEntry>();
2618 final HashMap<GECacheKey, Set<UIElementReference>> treeReferences = new HashMap<GECacheKey, Set<UIElementReference>>();
2619 final HashMap<NodeContext, Set<GECacheKey>> keyRefs = new HashMap<NodeContext, Set<GECacheKey>>();
2620 private TObjectIntHashMap<NodeContext> references = new TObjectIntHashMap<NodeContext>();
2623 * This single instance is used for all get operations from the cache. This
2624 * should work since the GE cache is meant to be single-threaded within the
2625 * current UI thread, what ever that thread is. For put operations which
2626 * store the key, this is not used.
2628 NodeContext getNC = new NodeContext() {
2629 @SuppressWarnings("rawtypes")
2631 public Object getAdapter(Class adapter) {
2636 public <T> T getConstant(ConstantKey<T> key) {
2641 public Set<ConstantKey<?>> getKeys() {
2642 return Collections.emptySet();
2645 CacheKey<?> getCK = new CacheKey<Object>() {
2647 public Object processorIdenfitier() {
2651 GECacheKey getKey = new GECacheKey(getNC, getCK);
2654 private void addKey(GECacheKey key) {
2655 Set<GECacheKey> refs = keyRefs.get(key.context);
2659 refs = new HashSet<GECacheKey>();
2661 keyRefs.put(key.context, refs);
2665 private void removeKey(GECacheKey key) {
2666 Set<GECacheKey> refs = keyRefs.get(key.context);
2672 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key, T value) {
2673 // if (DEBUG) System.out.println("Add entry " + context + " " + key);
2674 IGECacheEntry entry = new GECacheEntry(context, key, value);
2675 GECacheKey gekey = new GECacheKey(context, key);
2676 entries.put(gekey, entry);
2681 @SuppressWarnings("unchecked")
2682 public <T> T get(NodeContext context, CacheKey<T> key) {
2683 getKey.setValues(context, key);
2684 IGECacheEntry entry = entries.get(getKey);
2687 return (T) entry.getValue();
2691 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
2692 assert(context != null);
2693 assert(key != null);
2694 getKey.setValues(context, key);
2695 return entries.get(getKey);
2699 public <T> void remove(NodeContext context, CacheKey<T> key) {
2700 // if (DEBUG) System.out.println("Remove entry " + context + " " + key);
2701 getKey.setValues(context, key);
2702 entries.remove(getKey);
2707 public <T> Set<UIElementReference> getTreeReference(NodeContext context, CacheKey<T> key) {
2708 assert(context != null);
2709 assert(key != null);
2710 getKey.setValues(context, key);
2711 return treeReferences.get(getKey);
2715 public <T> void putTreeReference(NodeContext context, CacheKey<T> key, UIElementReference reference) {
2716 assert(context != null);
2717 assert(key != null);
2718 //if (DEBUG) System.out.println("Add tree reference " + context + " " + key);
2719 getKey.setValues(context, key);
2720 Set<UIElementReference> refs = treeReferences.get(getKey);
2722 refs.add(reference);
2724 refs = new HashSet<UIElementReference>(4);
2725 refs.add(reference);
2726 GECacheKey gekey = new GECacheKey(getKey);
2727 treeReferences.put(gekey, refs);
2733 public <T> Set<UIElementReference> removeTreeReference(NodeContext context, CacheKey<T> key) {
2734 assert(context != null);
2735 assert(key != null);
2736 //if (DEBUG) System.out.println("Remove tree reference " + context + " " + key);
2737 getKey.setValues(context, key);
2739 return treeReferences.remove(getKey);
2743 public boolean isShown(NodeContext context) {
2744 return references.get(context) > 0;
2750 public void incRef(NodeContext context) {
2751 int exist = references.get(context);
2752 references.put(context, exist+1);
2756 public void decRef(NodeContext context) {
2757 int exist = references.get(context);
2758 references.put(context, exist-1);
2760 references.remove(context);
2764 public void dispose() {
2767 treeReferences.clear();
2771 public void dispose(NodeContext context) {
2772 Set<GECacheKey> keys = keyRefs.remove(context);
2774 for (GECacheKey key : keys) {
2775 entries.remove(key);
2776 treeReferences.remove(key);
2784 * Non-functional cache to replace actual cache when GEContext is disposed.
2789 private static class DummyCache extends GECache2 {
2792 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
2797 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key,
2803 public <T> void putTreeReference(NodeContext context, CacheKey<T> key,
2804 UIElementReference reference) {
2808 public <T> T get(NodeContext context, CacheKey<T> key) {
2813 public <T> Set<UIElementReference> getTreeReference(
2814 NodeContext context, CacheKey<T> key) {
2819 public <T> void remove(NodeContext context, CacheKey<T> key) {
2824 public <T> Set<UIElementReference> removeTreeReference(
2825 NodeContext context, CacheKey<T> key) {
2830 public boolean isShown(NodeContext context) {
2835 public void incRef(NodeContext context) {
2840 public void decRef(NodeContext context) {
2845 public void dispose() {
2850 private class NatTableHeaderMenuConfiguration extends AbstractHeaderMenuConfiguration {
2853 public NatTableHeaderMenuConfiguration(NatTable natTable) {
2858 protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) {
2859 return super.createColumnHeaderMenu(natTable)
2860 .withHideColumnMenuItem()
2861 .withShowAllColumnsMenuItem()
2862 .withAutoResizeSelectedColumnsMenuItem();
2866 protected PopupMenuBuilder createCornerMenu(NatTable natTable) {
2867 return super.createCornerMenu(natTable)
2868 .withShowAllColumnsMenuItem();
2871 protected PopupMenuBuilder createRowHeaderMenu(NatTable natTable) {
2872 return super.createRowHeaderMenu(natTable);
2876 private static class RelativeAlternatingRowConfigLabelAccumulator extends AlternatingRowConfigLabelAccumulator {
2879 public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) {
2880 configLabels.addLabel((rowPosition % 2 == 0 ? EVEN_ROW_CONFIG_TYPE : ODD_ROW_CONFIG_TYPE));
2885 public Object getClicked(Object event) {
2886 MouseEvent e = (MouseEvent)event;
2887 final NatTable tree = (NatTable) e.getSource();
2888 Point point = new Point(e.x, e.y);
2889 int y = natTable.getRowPositionByY(point.y);
2890 int x = natTable.getColumnPositionByX(point.x);
2893 return list.get(y-1);