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.
996 if (rootNode != null) {
1000 explorerContext.dispose();
1001 explorerContext = null;
1003 detachPrimitiveProcessors();
1004 primitiveProcessors.clear();
1005 dataSources.clear();
1006 pendingItems.clear();
1008 mouseListeners.clear();
1009 selectionProvider.clearListeners();
1010 selectionProvider = null;
1011 selectionDataResolver = null;
1012 selectedNodes.clear();
1013 selectedNodes = null;
1014 selectionTransformation = null;
1015 originalFont = null;
1016 localResourceManager.dispose();
1017 localResourceManager = null;
1018 // Must shutdown image loader job before disposing its ResourceManager
1019 imageLoaderJob.dispose();
1020 imageLoaderJob.cancel();
1022 imageLoaderJob.join();
1023 imageLoaderJob = null;
1024 } catch (InterruptedException e) {
1025 ErrorLogger.defaultLogError(e);
1027 resourceManager.dispose();
1028 resourceManager = null;
1030 contextToNodeMap.clear(); // should be empty at this point.
1031 contextToNodeMap = null;
1032 if (postSelectionProvider != null) {
1033 postSelectionProvider.dispose();
1034 postSelectionProvider = null;
1037 modificationContext = null;
1038 focusService = null;
1039 contextService = null;
1040 serviceLocator = null;
1042 columnKeyToIndex.clear();
1043 columnKeyToIndex = null;
1044 // if (natTable != null) {
1045 // natTable.dispose();
1050 viewportLayer = null;
1051 selectionLayer = null;
1052 columnHeaderDataProvider = null;
1053 columnAccessor = null;
1054 rowHeaderDataLayer = null;
1055 columnHeaderDataLayer = null;
1056 cornerDataLayer = null;
1061 public boolean select(NodeContext context) {
1063 assertNotDisposed();
1065 if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) {
1066 StructuredSelection s = new StructuredSelection();
1067 selectionAdaptor.setSelection(s);
1068 selectionProvider.setAndFireNonEqualSelection(s);
1072 selectionAdaptor.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0)));
1078 public boolean select(TreeNode node) {
1079 assertNotDisposed();
1081 if (!list.contains(node)) {
1082 StructuredSelection s = new StructuredSelection();
1083 selectionAdaptor.setSelection(s);
1084 selectionProvider.setAndFireNonEqualSelection(s);
1087 selectionAdaptor.setSelection(new StructuredSelection(node));
1091 public void show(TreeNode node) {
1092 int index = node.getListIndex();
1094 int position = treeLayer.getRowPositionByIndex(index);
1096 treeLayer.expandToTreeRow(index);
1097 position = treeLayer.getRowPositionByIndex(index);
1099 viewportLayer.moveRowPositionIntoViewport(position);
1103 public boolean selectPath(Collection<NodeContext> contexts) {
1105 if(contexts == null) throw new IllegalArgumentException("Null list is not allowed");
1106 if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed");
1108 return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0);
1112 private boolean selectPathInternal(NodeContext[] contexts, int position) {
1114 NodeContext head = contexts[position];
1116 if(position == contexts.length-1) {
1117 return select(head);
1121 setExpanded(head, true);
1122 if(!waitVisible(contexts[position+1])) return false;
1124 return selectPathInternal(contexts, position+1);
1128 private boolean waitVisible(NodeContext context) {
1129 long start = System.nanoTime();
1130 while(!isVisible(context)) {
1131 Display.getCurrent().readAndDispatch();
1132 long duration = System.nanoTime() - start;
1133 if(duration > 10e9) return false;
1139 public boolean isVisible(NodeContext context) {
1140 if (contextToNodeMap.getValuesUnsafe(context).size() == 0)
1143 return true; //FIXME
1144 // Object elements[] = viewer.getVisibleExpandedElements();
1145 // return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0));
1151 public TransientExplorerState getTransientState() {
1152 if (!thread.currentThreadAccess())
1153 throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread());
1154 return transientState;
1158 public <T> T query(NodeContext context, CacheKey<T> key) {
1159 return this.explorerContext.cache.get(context, key);
1163 * For setting a more local service locator for the explorer than the global
1164 * workbench service locator. Sometimes required to give this implementation
1165 * access to local workbench services like IFocusService.
1168 * Must be invoked during right after construction.
1170 * @param serviceLocator
1171 * a specific service locator or <code>null</code> to use the
1172 * workbench global service locator
1174 public void setServiceLocator(IServiceLocator serviceLocator) {
1175 if (serviceLocator == null && PlatformUI.isWorkbenchRunning())
1176 serviceLocator = PlatformUI.getWorkbench();
1177 this.serviceLocator = serviceLocator;
1178 if (serviceLocator != null) {
1179 this.contextService = (IContextService) serviceLocator.getService(IContextService.class);
1180 this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class);
1184 private void detachPrimitiveProcessors() {
1185 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
1186 if (p instanceof ProcessorLifecycle) {
1187 ((ProcessorLifecycle) p).detached(this);
1192 private void clearPrimitiveProcessors() {
1193 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
1194 if (p instanceof ProcessorLifecycle) {
1195 ((ProcessorLifecycle) p).clear();
1201 public void setExpanded(NodeContext context, boolean expanded) {
1202 for (TreeNode n : contextToNodeMap.getValues(context)) {
1204 treeLayer.expandTreeRow(n.getListIndex());
1206 treeLayer.collapseTreeRow(n.getListIndex());
1212 public void setAutoExpandLevel(int level) {
1213 this.autoExpandLevel = level;
1214 treeLayer.expandAllToLevel(level);
1217 int maxChildren = DEFAULT_MAX_CHILDREN;
1220 public int getMaxChildren() {
1225 public void setMaxChildren(int maxChildren) {
1226 this.maxChildren = maxChildren;
1231 public int getMaxChildren(NodeQueryManager manager, NodeContext context) {
1232 Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN);
1233 //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result);
1234 if (result != null) {
1236 throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result);
1243 public <T> NodeQueryProcessor<T> getProcessor(QueryKey<T> key) {
1244 return explorerContext.getProcessor(key);
1248 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(PrimitiveQueryKey<T> key) {
1249 return explorerContext.getPrimitiveProcessor(key);
1252 private HashSet<UpdateItem> pendingItems = new HashSet<UpdateItem>();
1253 private boolean updating = false;
1254 private int updateCounter = 0;
1255 final ScheduledExecutorService uiUpdateScheduler = ThreadUtils.getNonBlockingWorkExecutor();
1257 private class UpdateItem {
1261 public UpdateItem(TreeNode element) {
1265 public UpdateItem(TreeNode element, int columnIndex) {
1266 this.element = element;
1267 this.columnIndex = columnIndex;
1268 if (element != null && element.isDisposed()) {
1269 throw new IllegalArgumentException("Node is disposed. " + element);
1273 public void update(NatTable natTable) {
1274 if (element != null) {
1276 if (element.isDisposed()) {
1279 if (element.updateChildren()) {
1281 System.out.println("Update Item updateChildren " + element.listIndex + " " + element);
1282 printDebug(NatTableGraphExplorer.this);
1285 if (!element.isHidden()) {
1286 if (!element.isExpanded()) {
1287 if (element.listIndex >= 0)
1288 treeLayer.collapseTreeRow(element.listIndex);
1290 System.out.println("Update Item collapse " + element.listIndex);
1291 printDebug(NatTableGraphExplorer.this);
1294 for (TreeNode c : element.getChildren())
1298 TreeNode p = element.getCollapsedAncestor();
1300 if (element.listIndex >= 0)
1301 treeLayer.collapseTreeRow(element.listIndex);
1302 if (p.listIndex >= 0)
1303 treeLayer.collapseTreeRow(p.listIndex);
1305 System.out.println("Update Item ancetor collapse " + p.listIndex);
1306 printDebug(NatTableGraphExplorer.this);
1311 // if (columnIndex >= 0) {
1312 // viewer.update(element, new String[]{columns[columnIndex].getKey()});
1314 // viewer.refresh(element,true);
1320 if (!element.autoExpanded && !element.isDisposed() && autoExpandLevel > 1 && !element.isExpanded() && element.getDepth() <= autoExpandLevel) {
1322 element.autoExpanded = true;
1324 if (DEBUG) System.out.println("Update Item expand " + element.listIndex);
1325 treeLayer.expandTreeRow(element.getListIndex());
1326 //viewer.setExpandedState(element, true);
1330 if (rootNode.updateChildren()) {
1337 public boolean equals(Object obj) {
1340 if (obj.getClass() != getClass())
1342 UpdateItem other = (UpdateItem)obj;
1343 if (columnIndex != other.columnIndex)
1345 if (element != null)
1346 return element.equals(other.element);
1347 return other.element == null;
1351 public int hashCode() {
1352 if (element != null)
1353 return element.hashCode() + columnIndex;
1358 private void update(final TreeNode element, final int columnIndex) {
1359 if (natTable.isDisposed())
1361 if (element != null && element.isDisposed())
1363 if (DEBUG) System.out.println("update " + element + " " + columnIndex);
1364 synchronized (pendingItems) {
1365 pendingItems.add(new UpdateItem(element, columnIndex));
1366 if (updating) return;
1372 private void update(final TreeNode element) {
1374 if (natTable.isDisposed())
1376 if (element != null && element.isDisposed())
1378 if (DEBUG) System.out.println("update " + element);
1379 synchronized (pendingItems) {
1380 pendingItems.add(new UpdateItem(element));
1381 if (updating) return;
1387 boolean scheduleUpdater() {
1389 if (natTable.isDisposed())
1392 if (!pendingItems.isEmpty()) {
1394 int activity = explorerContext.activityInt;
1396 if (activity < 100) {
1397 //System.out.println("Scheduling update immediately.");
1398 } else if (activity < 1000) {
1399 //System.out.println("Scheduling update after 500ms.");
1402 //System.out.println("Scheduling update after 3000ms.");
1408 //System.out.println("Scheduling UI update after " + delay + " ms.");
1409 uiUpdateScheduler.schedule(new Runnable() {
1413 if (natTable == null || natTable.isDisposed())
1416 if (updateCounter > 0) {
1418 uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS);
1420 natTable.getDisplay().asyncExec(new UpdateRunner(NatTableGraphExplorer.this, NatTableGraphExplorer.this.explorerContext));
1424 }, delay, TimeUnit.MILLISECONDS);
1434 public String startEditing(NodeContext context, String columnKey) {
1435 assertNotDisposed();
1436 if (!thread.currentThreadAccess())
1437 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
1439 if(columnKey.startsWith("#")) {
1440 columnKey = columnKey.substring(1);
1443 Integer columnIndex = columnKeyToIndex.get(columnKey);
1444 if (columnIndex == null)
1445 return "Rename not supported for selection";
1447 // viewer.editElement(context, columnIndex);
1448 // if(viewer.isCellEditorActive()) return null;
1449 return "Rename not supported for selection";
1453 public String startEditing(String columnKey) {
1454 ISelection selection = postSelectionProvider.getSelection();
1455 if(selection == null) return "Rename not supported for selection";
1456 NodeContext context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class);
1457 if(context == null) return "Rename not supported for selection";
1459 return startEditing(context, columnKey);
1463 public void setSelection(final ISelection selection, boolean forceControlUpdate) {
1464 assertNotDisposed();
1465 boolean equalsOld = selectionProvider.selectionEquals(selection);
1466 if (equalsOld && !forceControlUpdate) {
1467 // Just set the selection object instance, fire no events nor update
1468 // the viewer selection.
1469 selectionProvider.setSelection(selection);
1471 Collection<NodeContext> coll = AdaptionUtils.adaptToCollection(selection, NodeContext.class);
1472 Collection<TreeNode> nodes = new ArrayList<TreeNode>();
1473 for (NodeContext c : coll) {
1474 List<TreeNode> match = contextToNodeMap.getValuesUnsafe(c);
1475 if(match.size() > 0)
1476 nodes.add(match.get(0));
1478 final ISelection sel = new StructuredSelection(nodes.toArray());
1479 if (coll.size() == 0)
1481 // Schedule viewer and selection update if necessary.
1482 if (natTable.isDisposed())
1484 Display d = natTable.getDisplay();
1485 if (d.getThread() == Thread.currentThread()) {
1486 selectionAdaptor.setSelection(sel);
1488 d.asyncExec(new Runnable() {
1491 if (natTable.isDisposed())
1493 selectionAdaptor.setSelection(sel);
1501 public void setModificationContext(ModificationContext modificationContext) {
1502 this.modificationContext = modificationContext;
1506 final ExecutorService queryUpdateScheduler = Threads.getExecutor();
1509 public static double getDisplayScale() {
1510 Point dpi = Display.getCurrent().getDPI();
1511 return (double)dpi.x/96.0;
1514 private void createNatTable(int style) {
1515 GETreeData treeData = new GETreeData(list);
1516 GETreeRowModel<TreeNode> treeRowModel = new GETreeRowModel<TreeNode>(treeData);
1517 columnAccessor = new GEColumnAccessor(this);
1519 IDataProvider dataProvider = new ListDataProvider<TreeNode>(list, columnAccessor);
1521 // FIXME: NatTable 1.0 required help to work with custom display scaling (Windows 7 display scaling).
1522 // It seems that NatTable 1.4 breaks with the same code in Windows 7, so now the code is disabled.
1523 // More testing with different hardware is required...
1524 // int defaultFontSize = 12;
1525 // int height = (int)Math.ceil(((double)(defaultFontSize))*getDisplayScale()) + DataLayer.DEFAULT_ROW_HEIGHT-defaultFontSize;
1526 // dataLayer = new DataLayer(dataProvider, DataLayer.DEFAULT_COLUMN_WIDTH, height);
1527 dataLayer = new DataLayer(dataProvider);
1529 // resizable rows are unnecessary in Sulca report.
1530 dataLayer.setRowsResizableByDefault(false);
1533 DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(dataProvider);
1534 rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider);
1536 // adjust row header column width so that row numbers fit into the column.
1537 //adjustRowHeaderWidth(list.size());
1539 // Column header layer
1540 columnHeaderDataProvider = new GEColumnHeaderDataProvider(this, dataLayer);
1541 columnHeaderDataLayer = new DefaultColumnHeaderDataLayer(columnHeaderDataProvider);
1542 //columnHeaderDataLayer.setDefaultRowHeight(height);
1543 columnHeaderDataProvider.updateColumnSizes();
1545 //ISortModel sortModel = new EcoSortModel(this, generator,dataLayer);
1547 // Column re-order + hide
1548 ColumnReorderLayer columnReorderLayer = new ColumnReorderLayer(dataLayer);
1549 ColumnHideShowLayer columnHideShowLayer = new ColumnHideShowLayer(columnReorderLayer);
1552 treeLayer = new GETreeLayer(columnHideShowLayer, treeRowModel, false);
1554 selectionLayer = new SelectionLayer(treeLayer);
1556 viewportLayer = new ViewportLayer(selectionLayer);
1558 ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, viewportLayer, selectionLayer);
1559 // Note: The column header layer is wrapped in a filter row composite.
1560 // This plugs in the filter row functionality
1562 ColumnOverrideLabelAccumulator labelAccumulator = new ColumnOverrideLabelAccumulator(columnHeaderDataLayer);
1563 columnHeaderDataLayer.setConfigLabelAccumulator(labelAccumulator);
1566 //SortHeaderLayer<TreeNode> sortHeaderLayer = new SortHeaderLayer<TreeNode>(columnHeaderLayer, sortModel, false);
1568 RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, viewportLayer, selectionLayer);
1571 DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider);
1572 cornerDataLayer = new DataLayer(cornerDataProvider);
1573 //CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, sortHeaderLayer);
1574 CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, columnHeaderLayer);
1577 //GridLayer gridLayer = new GridLayer(viewportLayer,sortHeaderLayer,rowHeaderLayer, cornerLayer);
1578 GridLayer gridLayer = new GridLayer(viewportLayer, columnHeaderLayer,rowHeaderLayer, cornerLayer, false);
1580 /* Since 1.4.0, alternative row rendering uses row indexes in the original data list.
1581 When combined with collapsed tree rows, rows with odd or even index may end up next to each other,
1582 which defeats the purpose of alternating colors. This overrides that and returns the functionality
1583 that we had with 1.0.1. */
1584 gridLayer.setConfigLabelAccumulatorForRegion(GridRegion.BODY, new RelativeAlternatingRowConfigLabelAccumulator());
1585 gridLayer.addConfiguration(new DefaultEditConfiguration());
1586 //gridLayer.addConfiguration(new DefaultEditBindings());
1587 gridLayer.addConfiguration(new GEEditBindings());
1589 natTable = new NatTable(composite,gridLayer,false);
1591 //selectionLayer.registerCommandHandler(new EcoCopyDataCommandHandler(selectionLayer,columnHeaderDataLayer,columnAccessor, columnHeaderDataProvider));
1593 natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable));
1594 natTable.addConfiguration(new DefaultTreeLayerConfiguration2(treeLayer));
1595 natTable.addConfiguration(new SingleClickSortConfiguration());
1596 //natTable.addLayerListener(this);
1598 natTable.addConfiguration(new GENatTableThemeConfiguration(treeData, style));
1599 natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable));
1601 natTable.addConfiguration(new AbstractRegistryConfiguration() {
1604 public void configureRegistry(IConfigRegistry configRegistry) {
1605 configRegistry.registerConfigAttribute(
1606 EditConfigAttributes.CELL_EDITABLE_RULE,
1607 new IEditableRule() {
1610 public boolean isEditable(ILayerCell cell,
1611 IConfigRegistry configRegistry) {
1612 int col = cell.getColumnIndex();
1613 int row = cell.getRowIndex();
1614 TreeNode node = list.get(row);
1615 Modifier modifier = getModifier(node,col);
1616 return modifier != null;
1621 public boolean isEditable(int columnIndex, int rowIndex) {
1622 // there are no callers?
1627 configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, new AdaptableCellEditor());
1628 configRegistry.registerConfigAttribute(EditConfigAttributes.CONVERSION_ERROR_HANDLER, new DialogErrorHandling(), DisplayMode.EDIT);
1629 configRegistry.registerConfigAttribute(EditConfigAttributes.VALIDATION_ERROR_HANDLER, new DialogErrorHandling(), DisplayMode.EDIT);
1630 configRegistry.registerConfigAttribute(EditConfigAttributes.DATA_VALIDATOR, new AdaptableDataValidator(),DisplayMode.EDIT);
1632 Style conversionErrorStyle = new Style();
1633 conversionErrorStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, GUIHelper.COLOR_RED);
1634 conversionErrorStyle.setAttributeValue(CellStyleAttributes.FOREGROUND_COLOR, GUIHelper.COLOR_WHITE);
1635 configRegistry.registerConfigAttribute(EditConfigAttributes.CONVERSION_ERROR_STYLE, conversionErrorStyle, DisplayMode.EDIT);
1637 configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, new DefaultDisplayConverter(),DisplayMode.EDIT);
1643 if ((style & SWT.BORDER) > 0) {
1644 natTable.addOverlayPainter(new NatTableBorderOverlayPainter());
1647 natTable.configure();
1649 // natTable.addListener(SWT.MenuDetect, new NatTableMenuListener());
1651 // DefaultToolTip toolTip = new EcoCellToolTip(natTable, columnAccessor);
1652 // toolTip.setBackgroundColor(natTable.getDisplay().getSystemColor(SWT.COLOR_WHITE));
1653 // toolTip.setPopupDelay(500);
1654 // toolTip.activate();
1655 // toolTip.setShift(new Point(10, 10));
1658 // menuManager.createContextMenu(composite);
1659 // natTable.setMenu(getMenuManager().getMenu());
1661 selectionAdaptor = new NatTableSelectionAdaptor(natTable, selectionLayer, treeData);
1664 Modifier getModifier(TreeNode element, int columnIndex) {
1665 GENodeQueryManager manager = element.getManager();
1666 final NodeContext context = element.getContext();
1667 Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
1668 if (labeler == null)
1670 Column column = columns[columnIndex];
1672 return labeler.getModifier(modificationContext, column.getKey());
1676 private class AdaptableCellEditor implements ICellEditor {
1680 public Control activateCell(Composite parent, Object originalCanonicalValue, EditModeEnum editMode,
1681 ICellEditHandler editHandler, ILayerCell cell, IConfigRegistry configRegistry) {
1682 int col = cell.getColumnIndex();
1683 int row = cell.getRowIndex();
1684 TreeNode node = list.get(row);
1685 Modifier modifier = getModifier(node, col);
1686 if (modifier == null)
1690 if (modifier instanceof DialogModifier) {
1691 DialogModifier mod = (DialogModifier)modifier;
1692 editor = new DialogCellEditor(node, col, mod);
1693 } else if (modifier instanceof CustomModifier) {
1694 CustomModifier mod = (CustomModifier)modifier;
1695 editor = new CustomCellEditor(node, col, mod);
1696 } else if (modifier instanceof EnumerationModifier) {
1697 EnumerationModifier mod = (EnumerationModifier)modifier;
1698 editor = new ComboBoxCellEditor(mod.getValues());
1700 editor = new TextCellEditor();
1703 return editor.activateCell(parent, originalCanonicalValue, editMode, editHandler, cell, configRegistry);
1707 public int getColumnIndex() {
1708 return editor.getColumnIndex();
1712 public int getRowIndex() {
1713 return editor.getRowIndex();
1717 public int getColumnPosition() {
1718 return editor.getColumnPosition();
1722 public int getRowPosition() {
1723 return editor.getRowPosition();
1727 public Object getEditorValue() {
1728 return editor.getEditorValue();
1732 public void setEditorValue(Object value) {
1733 editor.setEditorValue(value);
1738 public Object getCanonicalValue() {
1739 return editor.getCanonicalValue();
1743 public Object getCanonicalValue(IEditErrorHandler conversionErrorHandler) {
1744 return editor.getCanonicalValue();
1748 public void setCanonicalValue(Object canonicalValue) {
1749 editor.setCanonicalValue(canonicalValue);
1754 public boolean validateCanonicalValue(Object canonicalValue) {
1755 return editor.validateCanonicalValue(canonicalValue);
1759 public boolean validateCanonicalValue(Object canonicalValue, IEditErrorHandler validationErrorHandler) {
1760 return editor.validateCanonicalValue(canonicalValue, validationErrorHandler);
1764 public boolean commit(MoveDirectionEnum direction) {
1765 return editor.commit(direction);
1769 public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit) {
1770 return editor.commit(direction, closeAfterCommit);
1774 public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit, boolean skipValidation) {
1775 return editor.commit(direction, closeAfterCommit, skipValidation);
1779 public void close() {
1785 public boolean isClosed() {
1786 return editor.isClosed();
1790 public Control getEditorControl() {
1791 return editor.getEditorControl();
1795 public Control createEditorControl(Composite parent) {
1796 return editor.createEditorControl(parent);
1800 public boolean openInline(IConfigRegistry configRegistry, List<String> configLabels) {
1801 return EditConfigHelper.openInline(configRegistry, configLabels);
1805 public boolean supportMultiEdit(IConfigRegistry configRegistry, List<String> configLabels) {
1806 return editor.supportMultiEdit(configRegistry, configLabels);
1810 public boolean openMultiEditDialog() {
1811 return editor.openMultiEditDialog();
1815 public boolean openAdjacentEditor() {
1816 return editor.openAdjacentEditor();
1820 public boolean activateAtAnyPosition() {
1825 public boolean activateOnTraversal(IConfigRegistry configRegistry, List<String> configLabels) {
1826 return editor.activateOnTraversal(configRegistry, configLabels);
1830 public void addEditorControlListeners() {
1831 editor.addEditorControlListeners();
1836 public void removeEditorControlListeners() {
1837 editor.removeEditorControlListeners();
1842 public Rectangle calculateControlBounds(Rectangle cellBounds) {
1843 return editor.calculateControlBounds(cellBounds);
1847 private class AdaptableDataValidator implements IDataValidator {
1849 public boolean validate(ILayerCell cell, IConfigRegistry configRegistry, Object newValue) {
1850 int col = cell.getColumnIndex();
1851 int row = cell.getRowIndex();
1852 return validate(col, row, newValue);
1856 public boolean validate(int col, int row, Object newValue) {
1857 TreeNode node = list.get(row);
1858 Modifier modifier = getModifier(node, col);
1859 if (modifier == null)
1862 String err = modifier.isValid(newValue != null ? newValue.toString() : "");
1865 throw new ValidationFailedException(err);
1869 private class CustomCellEditor extends AbstractCellEditor {
1871 CustomModifier customModifier;
1875 public CustomCellEditor(TreeNode node, int column, CustomModifier customModifier) {
1876 this.customModifier = customModifier;
1878 this.column = column;
1882 public Object getEditorValue() {
1883 return customModifier.getValue();
1887 public void setEditorValue(Object value) {
1888 customModifier.modify(value.toString());
1893 public Control getEditorControl() {
1898 public Control createEditorControl(Composite parent) {
1899 return (Control)customModifier.createControl(parent, null, column, node.getContext());
1904 protected Control activateCell(Composite parent, Object originalCanonicalValue) {
1905 this.control = createEditorControl(parent);
1912 private class DialogCellEditor extends AbstractDialogCellEditor {
1914 DialogModifier dialogModifier;
1920 boolean closed = false;
1922 public DialogCellEditor(TreeNode node, int column, DialogModifier dialogModifier) {
1923 this.dialogModifier = dialogModifier;
1925 this.column = column;
1930 sem = new Semaphore(1);
1931 Consumer<String> callback = result -> {
1935 String status = dialogModifier.query(this.parent.getShell(), null, column, node.getContext(), callback);
1936 if (status != null) {
1938 return Window.CANCEL;
1943 } catch (InterruptedException e) {
1944 e.printStackTrace();
1951 public DialogModifier createDialogInstance() {
1953 return dialogModifier;
1957 public Object getDialogInstance() {
1958 return (DialogModifier)this.dialog;
1962 public void close() {
1967 public Object getEditorValue() {
1972 public void setEditorValue(Object value) {
1973 // dialog modifier handles this internally
1977 public boolean isClosed() {
1985 * The job that is used for off-loading image loading tasks (see
1986 * {@link ImageTask} to a worker thread from the main UI thread.
1988 ImageLoaderJob imageLoaderJob;
1990 // Map<NodeContext, ImageTask> imageTasks = new THashMap<NodeContext, ImageTask>();
1991 Map<TreeNode, ImageTask> imageTasks = new THashMap<TreeNode, ImageTask>();
1993 void queueImageTask(TreeNode node, ImageTask task) {
1994 synchronized (imageTasks) {
1995 imageTasks.put(node, task);
1997 imageLoaderJob.scheduleIfNecessary(100);
2001 * Invoked in a job worker thread.
2006 protected IStatus setPendingImages(IProgressMonitor monitor) {
2007 ImageTask[] tasks = null;
2008 synchronized (imageTasks) {
2009 tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]);
2013 MultiStatus status = null;
2015 // Load missing images
2016 for (ImageTask task : tasks) {
2017 Object desc = task.descsOrImage;
2018 if (desc instanceof ImageDescriptor) {
2020 desc = resourceManager.get((ImageDescriptor) desc);
2021 task.descsOrImage = desc;
2022 } catch (DeviceResourceException e) {
2024 status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null);
2025 status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e));
2031 // Perform final UI updates in the UI thread.
2032 final ImageTask[] _tasks = tasks;
2033 thread.asyncExec(new Runnable() {
2040 return status != null ? status : Status.OK_STATUS;
2044 void setImages(ImageTask[] tasks) {
2045 for (ImageTask task : tasks)
2050 void setImage(ImageTask task) {
2051 if (!task.node.isDisposed())
2052 update(task.node, 0);
2055 private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider {
2057 private NatTableGraphExplorer ge;
2059 GraphExplorerPostSelectionProvider(NatTableGraphExplorer ge) {
2068 public void setSelection(final ISelection selection) {
2069 if(ge == null) return;
2070 ge.setSelection(selection, false);
2076 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
2077 if(ge == null) return;
2078 if(ge.isDisposed()) {
2079 if (DEBUG_SELECTION_LISTENERS)
2080 System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener);
2083 ge.selectionProvider.removeSelectionChangedListener(listener);
2087 public void addPostSelectionChangedListener(ISelectionChangedListener listener) {
2088 if(ge == null) return;
2089 if (!ge.thread.currentThreadAccess())
2090 throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2091 if(ge.isDisposed()) {
2092 System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener);
2095 ge.selectionProvider.addPostSelectionChangedListener(listener);
2099 public void removePostSelectionChangedListener(ISelectionChangedListener listener) {
2100 if(ge == null) return;
2101 if(ge.isDisposed()) {
2102 if (DEBUG_SELECTION_LISTENERS)
2103 System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener);
2106 ge.selectionProvider.removePostSelectionChangedListener(listener);
2111 public void addSelectionChangedListener(ISelectionChangedListener listener) {
2112 if(ge == null) return;
2113 if (!ge.thread.currentThreadAccess())
2114 throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2115 if (ge.natTable.isDisposed() || ge.selectionProvider == null) {
2116 System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener);
2120 ge.selectionProvider.addSelectionChangedListener(listener);
2125 public ISelection getSelection() {
2126 if(ge == null) return StructuredSelection.EMPTY;
2127 if (!ge.thread.currentThreadAccess())
2128 throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread());
2129 if (ge.natTable.isDisposed() || ge.selectionProvider == null)
2130 return StructuredSelection.EMPTY;
2131 return ge.selectionProvider.getSelection();
2136 static class ModifierValidator implements ICellEditorValidator {
2137 private Modifier modifier;
2138 public ModifierValidator(Modifier modifier) {
2139 this.modifier = modifier;
2143 public String isValid(Object value) {
2144 return modifier.isValid((String)value);
2148 static class UpdateRunner implements Runnable {
2150 final NatTableGraphExplorer ge;
2152 UpdateRunner(NatTableGraphExplorer ge, IGraphExplorerContext geContext) {
2159 } catch (Throwable t) {
2160 t.printStackTrace();
2164 public void doRun() {
2166 if (ge.isDisposed())
2169 HashSet<UpdateItem> items;
2171 ScrollBar verticalBar = ge.natTable.getVerticalBar();
2174 synchronized (ge.pendingItems) {
2175 items = ge.pendingItems;
2176 ge.pendingItems = new HashSet<UpdateItem>();
2178 if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size());
2180 //ge.natTable.setRedraw(false);
2181 for (UpdateItem item : items) {
2182 item.update(ge.natTable);
2185 // check if vertical scroll bar has become visible and refresh layout.
2186 boolean currentlyVerticalBarVisible = verticalBar.isVisible();
2187 if (ge.verticalBarVisible != currentlyVerticalBarVisible) {
2188 ge.verticalBarVisible = currentlyVerticalBarVisible;
2189 ge.natTable.getParent().layout();
2192 //ge.natTable.setRedraw(true);
2194 synchronized (ge.pendingItems) {
2195 if (!ge.scheduleUpdater()) {
2196 ge.updating = false;
2208 private static void printDebug(NatTableGraphExplorer ge) {
2209 ge.printTree(ge.rootNode, 0);
2210 System.out.println("Expanded");
2211 for (TreeNode n : ge.treeLayer.expanded)
2212 System.out.println(n);
2213 System.out.println("Expanded end");
2214 System.out.println("Hidden ");
2215 for (int i : ge.treeLayer.getHiddenRowIndexes()) {
2216 System.out.print(i + " ");
2218 System.out.println();
2219 // Display.getCurrent().timerExec(1000, new Runnable() {
2222 // public void run() {
2223 // System.out.println("Hidden delayed ");
2224 // for (int i : ge.treeLayer.getHiddenRowIndexes()) {
2225 // System.out.print(i + " ");
2227 // System.out.println();
2233 public static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {
2234 // This is for query debugging only.
2236 private NatTableGraphExplorer ge;
2237 int queryIndent = 0;
2239 GECache2 cache = new GECache2();
2240 AtomicBoolean propagating = new AtomicBoolean(false);
2241 Object propagateList = new Object();
2242 Object propagate = new Object();
2243 List<Runnable> scheduleList = new ArrayList<Runnable>();
2244 final Deque<Integer> activity = new LinkedList<Integer>();
2245 int activityInt = 0;
2247 AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();
2250 * Keeps track of nodes that have already been auto-expanded. After
2251 * being inserted into this set, nodes will not be forced to stay in an
2252 * expanded state after that. This makes it possible for the user to
2253 * close auto-expanded nodes.
2255 Map<NodeContext, Boolean> autoExpanded = new WeakHashMap<NodeContext, Boolean>();
2257 public GeViewerContext(NatTableGraphExplorer ge) {
2261 public MapList<NodeContext,TreeNode> getContextToNodeMap() {
2264 return ge.contextToNodeMap;
2267 public NatTableGraphExplorer getGe() {
2272 protected void doDispose() {
2274 autoExpanded.clear();
2278 public IGECache getCache() {
2283 public int queryIndent() {
2288 public int queryIndent(int offset) {
2289 queryIndent += offset;
2294 @SuppressWarnings("unchecked")
2295 public <T> NodeQueryProcessor<T> getProcessor(Object o) {
2298 return ge.processors.get(o);
2302 @SuppressWarnings("unchecked")
2303 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {
2304 return ge.primitiveProcessors.get(o);
2307 @SuppressWarnings("unchecked")
2309 public <T> DataSource<T> getDataSource(Class<T> clazz) {
2310 return ge.dataSources.get(clazz);
2314 public void update(UIElementReference ref) {
2315 if (ref instanceof ViewerCellReference) {
2316 ViewerCellReference tiref = (ViewerCellReference) ref;
2317 Object element = tiref.getElement();
2318 int columnIndex = tiref.getColumn();
2319 // NOTE: must be called regardless of the the item value.
2320 // A null item is currently used to indicate a tree root update.
2321 ge.update((TreeNode)element,columnIndex);
2322 } else if (ref instanceof ViewerRowReference) {
2323 ViewerRowReference rref = (ViewerRowReference)ref;
2324 Object element = rref.getElement();
2325 ge.update((TreeNode)element);
2327 throw new IllegalArgumentException("Ui Reference is unknkown " + ref);
2332 public Object getPropagateLock() {
2337 public Object getPropagateListLock() {
2338 return propagateList;
2342 public boolean isPropagating() {
2343 return propagating.get();
2347 public void setPropagating(boolean b) {
2348 this.propagating.set(b);
2352 public List<Runnable> getScheduleList() {
2353 return scheduleList;
2357 public void setScheduleList(List<Runnable> list) {
2358 this.scheduleList = list;
2362 public Deque<Integer> getActivity() {
2367 public void setActivityInt(int i) {
2368 this.activityInt = i;
2372 public int getActivityInt() {
2377 public void scheduleQueryUpdate(Runnable r) {
2380 if (ge.isDisposed())
2382 if (currentQueryUpdater.compareAndSet(null, r)) {
2383 ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER);
2387 Runnable QUERY_UPDATE_SCHEDULER = new Runnable() {
2390 Runnable r = currentQueryUpdater.getAndSet(null);
2398 public void dispose() {
2400 cache = new DummyCache();
2401 scheduleList.clear();
2402 autoExpanded.clear();
2403 autoExpanded = null;
2409 private class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor<Boolean> implements
2410 IsExpandedProcessor, ProcessorLifecycle {
2412 * The set of currently expanded node contexts.
2414 private final HashSet<NodeContext> expanded = new HashSet<NodeContext>();
2415 private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();
2417 private NatTable natTable;
2418 private List<TreeNode> list;
2420 public TreeNodeIsExpandedProcessor() {
2424 public Object getIdentifier() {
2425 return BuiltinKeys.IS_EXPANDED;
2429 public String toString() {
2430 return "IsExpandedProcessor";
2434 public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Boolean> key) {
2435 boolean isExpanded = expanded.contains(context);
2436 expandedQueries.put(context, updater);
2437 return Boolean.valueOf(isExpanded);
2441 public Collection<NodeContext> getExpanded() {
2442 return new HashSet<NodeContext>(expanded);
2446 public boolean getExpanded(NodeContext context) {
2447 return this.expanded.contains(context);
2451 public boolean setExpanded(NodeContext context, boolean expanded) {
2452 return _setExpanded(context, expanded);
2456 public boolean replaceExpanded(NodeContext context, boolean expanded) {
2457 return nodeStatusChanged(context, expanded);
2460 private boolean _setExpanded(NodeContext context, boolean expanded) {
2462 return this.expanded.add(context);
2464 return this.expanded.remove(context);
2468 ILayerListener treeListener = new ILayerListener() {
2471 public void handleLayerEvent(ILayerEvent event) {
2472 if (event instanceof ShowRowPositionsEvent) {
2473 ShowRowPositionsEvent e = (ShowRowPositionsEvent)event;
2474 for (Range r : e.getRowPositionRanges()) {
2475 int expanded = viewportLayer.getRowIndexByPosition(r.start-2)+1;
2476 if (DEBUG)System.out.println("IsExpandedProcessor expand " + expanded);
2477 if (expanded < 0 || expanded >= list.size()) {
2480 nodeStatusChanged(list.get(expanded).getContext(), true);
2482 } else if (event instanceof HideRowPositionsEvent) {
2483 HideRowPositionsEvent e = (HideRowPositionsEvent)event;
2484 for (Range r : e.getRowPositionRanges()) {
2485 int collapsed = viewportLayer.getRowIndexByPosition(r.start-2);
2486 if (DEBUG)System.out.println("IsExpandedProcessor collapse " + collapsed);
2487 if (collapsed < 0 || collapsed >= list.size()) {
2490 nodeStatusChanged(list.get(collapsed).getContext(), false);
2497 protected boolean nodeStatusChanged(NodeContext context, boolean expanded) {
2498 boolean result = _setExpanded(context, expanded);
2499 PrimitiveQueryUpdater updater = expandedQueries.get(context);
2500 if (updater != null)
2501 updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded);
2506 public void attached(GraphExplorer explorer) {
2507 Object control = explorer.getControl();
2508 if (control instanceof NatTable) {
2509 this.natTable = (NatTable) control;
2510 this.list = ((NatTableGraphExplorer)explorer).list;
2511 natTable.addLayerListener(treeListener);
2514 System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control);
2519 public void clear() {
2521 expandedQueries.clear();
2525 public void detached(GraphExplorer explorer) {
2527 if (natTable != null) {
2528 natTable.removeLayerListener(treeListener);
2529 // natTable.removeListener(SWT.Expand, treeListener);
2530 // natTable.removeListener(SWT.Collapse, treeListener);
2536 private void printTree(TreeNode node, int depth) {
2538 for (int i = 0; i < depth; i++) {
2542 System.out.println(s);
2544 for (TreeNode n : node.getChildren()) {
2551 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used)
2553 final private static class GECacheKey {
2555 private NodeContext context;
2556 private CacheKey<?> key;
2558 GECacheKey(NodeContext context, CacheKey<?> key) {
2559 this.context = context;
2561 if (context == null || key == null)
2562 throw new IllegalArgumentException("Null context or key is not accepted");
2565 GECacheKey(GECacheKey other) {
2566 this.context = other.context;
2567 this.key = other.key;
2568 if (context == null || key == null)
2569 throw new IllegalArgumentException("Null context or key is not accepted");
2572 void setValues(NodeContext context, CacheKey<?> key) {
2573 this.context = context;
2575 if (context == null || key == null)
2576 throw new IllegalArgumentException("Null context or key is not accepted");
2580 public int hashCode() {
2581 return context.hashCode() | key.hashCode();
2585 public boolean equals(Object object) {
2589 else if (object == null)
2592 GECacheKey i = (GECacheKey) object;
2594 return key.equals(i.key) && context.equals(i.context);
2601 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data.
2603 public static class GECache2 implements IGECache {
2605 final HashMap<GECacheKey, IGECacheEntry> entries = new HashMap<GECacheKey, IGECacheEntry>();
2606 final HashMap<GECacheKey, Set<UIElementReference>> treeReferences = new HashMap<GECacheKey, Set<UIElementReference>>();
2607 final HashMap<NodeContext, Set<GECacheKey>> keyRefs = new HashMap<NodeContext, Set<GECacheKey>>();
2610 * This single instance is used for all get operations from the cache. This
2611 * should work since the GE cache is meant to be single-threaded within the
2612 * current UI thread, what ever that thread is. For put operations which
2613 * store the key, this is not used.
2615 NodeContext getNC = new NodeContext() {
2616 @SuppressWarnings("rawtypes")
2618 public Object getAdapter(Class adapter) {
2623 public <T> T getConstant(ConstantKey<T> key) {
2628 public Set<ConstantKey<?>> getKeys() {
2629 return Collections.emptySet();
2632 CacheKey<?> getCK = new CacheKey<Object>() {
2634 public Object processorIdenfitier() {
2638 GECacheKey getKey = new GECacheKey(getNC, getCK);
2641 private void addKey(GECacheKey key) {
2642 Set<GECacheKey> refs = keyRefs.get(key.context);
2646 refs = new HashSet<GECacheKey>();
2648 keyRefs.put(key.context, refs);
2652 private void removeKey(GECacheKey key) {
2653 Set<GECacheKey> refs = keyRefs.get(key.context);
2659 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key, T value) {
2660 // if (DEBUG) System.out.println("Add entry " + context + " " + key);
2661 IGECacheEntry entry = new GECacheEntry(context, key, value);
2662 GECacheKey gekey = new GECacheKey(context, key);
2663 entries.put(gekey, entry);
2668 @SuppressWarnings("unchecked")
2669 public <T> T get(NodeContext context, CacheKey<T> key) {
2670 getKey.setValues(context, key);
2671 IGECacheEntry entry = entries.get(getKey);
2674 return (T) entry.getValue();
2678 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
2679 assert(context != null);
2680 assert(key != null);
2681 getKey.setValues(context, key);
2682 return entries.get(getKey);
2686 public <T> void remove(NodeContext context, CacheKey<T> key) {
2687 // if (DEBUG) System.out.println("Remove entry " + context + " " + key);
2688 getKey.setValues(context, key);
2689 entries.remove(getKey);
2694 public <T> Set<UIElementReference> getTreeReference(NodeContext context, CacheKey<T> key) {
2695 assert(context != null);
2696 assert(key != null);
2697 getKey.setValues(context, key);
2698 return treeReferences.get(getKey);
2702 public <T> void putTreeReference(NodeContext context, CacheKey<T> key, UIElementReference reference) {
2703 assert(context != null);
2704 assert(key != null);
2705 //if (DEBUG) System.out.println("Add tree reference " + context + " " + key);
2706 getKey.setValues(context, key);
2707 Set<UIElementReference> refs = treeReferences.get(getKey);
2709 refs.add(reference);
2711 refs = new HashSet<UIElementReference>(4);
2712 refs.add(reference);
2713 GECacheKey gekey = new GECacheKey(getKey);
2714 treeReferences.put(gekey, refs);
2720 public <T> Set<UIElementReference> removeTreeReference(NodeContext context, CacheKey<T> key) {
2721 assert(context != null);
2722 assert(key != null);
2723 //if (DEBUG) System.out.println("Remove tree reference " + context + " " + key);
2724 getKey.setValues(context, key);
2726 return treeReferences.remove(getKey);
2730 public boolean isShown(NodeContext context) {
2731 return references.get(context) > 0;
2734 private TObjectIntHashMap<NodeContext> references = new TObjectIntHashMap<NodeContext>();
2737 public void incRef(NodeContext context) {
2738 int exist = references.get(context);
2739 references.put(context, exist+1);
2743 public void decRef(NodeContext context) {
2744 int exist = references.get(context);
2745 references.put(context, exist-1);
2747 references.remove(context);
2751 public void dispose() {
2754 treeReferences.clear();
2758 public void dispose(NodeContext context) {
2759 Set<GECacheKey> keys = keyRefs.remove(context);
2761 for (GECacheKey key : keys) {
2762 entries.remove(key);
2763 treeReferences.remove(key);
2771 * Non-functional cache to replace actual cache when GEContext is disposed.
2776 private static class DummyCache extends GECache2 {
2779 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
2784 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key,
2790 public <T> void putTreeReference(NodeContext context, CacheKey<T> key,
2791 UIElementReference reference) {
2795 public <T> T get(NodeContext context, CacheKey<T> key) {
2800 public <T> Set<UIElementReference> getTreeReference(
2801 NodeContext context, CacheKey<T> key) {
2806 public <T> void remove(NodeContext context, CacheKey<T> key) {
2811 public <T> Set<UIElementReference> removeTreeReference(
2812 NodeContext context, CacheKey<T> key) {
2817 public boolean isShown(NodeContext context) {
2822 public void incRef(NodeContext context) {
2827 public void decRef(NodeContext context) {
2832 public void dispose() {
2837 private class NatTableHeaderMenuConfiguration extends AbstractHeaderMenuConfiguration {
2840 public NatTableHeaderMenuConfiguration(NatTable natTable) {
2845 protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) {
2846 return super.createColumnHeaderMenu(natTable)
2847 .withHideColumnMenuItem()
2848 .withShowAllColumnsMenuItem()
2849 .withAutoResizeSelectedColumnsMenuItem();
2853 protected PopupMenuBuilder createCornerMenu(NatTable natTable) {
2854 return super.createCornerMenu(natTable)
2855 .withShowAllColumnsMenuItem();
2858 protected PopupMenuBuilder createRowHeaderMenu(NatTable natTable) {
2859 return super.createRowHeaderMenu(natTable);
2863 private static class RelativeAlternatingRowConfigLabelAccumulator extends AlternatingRowConfigLabelAccumulator {
2866 public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) {
2867 configLabels.addLabel((rowPosition % 2 == 0 ? EVEN_ROW_CONFIG_TYPE : ODD_ROW_CONFIG_TYPE));
2872 public Object getClicked(Object event) {
2873 MouseEvent e = (MouseEvent)event;
2874 final NatTable tree = (NatTable) e.getSource();
2875 Point point = new Point(e.x, e.y);
2876 int y = natTable.getRowPositionByY(point.y);
2877 int x = natTable.getColumnPositionByX(point.x);
2880 return list.get(y-1);