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.Consumer;
24 import org.eclipse.core.runtime.Assert;
25 import org.eclipse.core.runtime.IProgressMonitor;
26 import org.eclipse.core.runtime.IStatus;
27 import org.eclipse.core.runtime.MultiStatus;
28 import org.eclipse.core.runtime.Platform;
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.selection.command.SelectCellCommand;
93 import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration;
94 import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;
95 import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
96 import org.eclipse.nebula.widgets.nattable.style.Style;
97 import org.eclipse.nebula.widgets.nattable.ui.menu.AbstractHeaderMenuConfiguration;
98 import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder;
99 import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
100 import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
101 import org.eclipse.nebula.widgets.nattable.widget.EditModeEnum;
102 import org.eclipse.swt.SWT;
103 import org.eclipse.swt.events.DisposeEvent;
104 import org.eclipse.swt.events.DisposeListener;
105 import org.eclipse.swt.events.FocusEvent;
106 import org.eclipse.swt.events.FocusListener;
107 import org.eclipse.swt.events.KeyEvent;
108 import org.eclipse.swt.events.KeyListener;
109 import org.eclipse.swt.events.MouseEvent;
110 import org.eclipse.swt.events.MouseListener;
111 import org.eclipse.swt.events.SelectionListener;
112 import org.eclipse.swt.graphics.Color;
113 import org.eclipse.swt.graphics.Point;
114 import org.eclipse.swt.graphics.RGB;
115 import org.eclipse.swt.graphics.Rectangle;
116 import org.eclipse.swt.widgets.Composite;
117 import org.eclipse.swt.widgets.Control;
118 import org.eclipse.swt.widgets.Display;
119 import org.eclipse.swt.widgets.Event;
120 import org.eclipse.swt.widgets.Listener;
121 import org.eclipse.swt.widgets.ScrollBar;
122 import org.eclipse.ui.PlatformUI;
123 import org.eclipse.ui.contexts.IContextActivation;
124 import org.eclipse.ui.contexts.IContextService;
125 import org.eclipse.ui.services.IServiceLocator;
126 import org.eclipse.ui.swt.IFocusService;
127 import org.simantics.browsing.ui.BuiltinKeys;
128 import org.simantics.browsing.ui.Column;
129 import org.simantics.browsing.ui.Column.Align;
130 import org.simantics.browsing.ui.DataSource;
131 import org.simantics.browsing.ui.ExplorerState;
132 import org.simantics.browsing.ui.GraphExplorer;
133 import org.simantics.browsing.ui.NodeContext;
134 import org.simantics.browsing.ui.NodeContext.CacheKey;
135 import org.simantics.browsing.ui.NodeContext.PrimitiveQueryKey;
136 import org.simantics.browsing.ui.NodeContext.QueryKey;
137 import org.simantics.browsing.ui.NodeQueryManager;
138 import org.simantics.browsing.ui.NodeQueryProcessor;
139 import org.simantics.browsing.ui.PrimitiveQueryProcessor;
140 import org.simantics.browsing.ui.PrimitiveQueryUpdater;
141 import org.simantics.browsing.ui.SelectionDataResolver;
142 import org.simantics.browsing.ui.SelectionFilter;
143 import org.simantics.browsing.ui.StatePersistor;
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.content.Labeler;
175 import org.simantics.browsing.ui.content.Labeler.CustomModifier;
176 import org.simantics.browsing.ui.content.Labeler.DialogModifier;
177 import org.simantics.browsing.ui.content.Labeler.EnumerationModifier;
178 import org.simantics.browsing.ui.content.Labeler.Modifier;
179 import org.simantics.browsing.ui.nattable.override.DefaultTreeLayerConfiguration2;
180 import org.simantics.browsing.ui.swt.Activator;
181 import org.simantics.browsing.ui.swt.AdaptableHintContext;
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.BinaryFunction;
194 import org.simantics.utils.datastructures.MapList;
195 import org.simantics.utils.datastructures.disposable.AbstractDisposable;
196 import org.simantics.utils.datastructures.hints.IHintContext;
197 import org.simantics.utils.threads.IThreadWorkQueue;
198 import org.simantics.utils.threads.SWTThread;
199 import org.simantics.utils.threads.ThreadUtils;
200 import org.simantics.utils.ui.AdaptionUtils;
201 import org.simantics.utils.ui.ISelectionUtils;
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 BinaryFunction<Object[], GraphExplorer, Object[]> selectionTransformation = new BinaryFunction<Object[], GraphExplorer, Object[]>() {
303 public Object[] call(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)
585 ExplorerState state = persistor.deserialize(
586 Platform.getStateLocation(Activator.getDefault().getBundle()).toFile(),
590 Object processor = getPrimitiveProcessor(BuiltinKeys.IS_EXPANDED);
591 if (processor instanceof DefaultIsExpandedProcessor) {
592 DefaultIsExpandedProcessor isExpandedProcessor = (DefaultIsExpandedProcessor)processor;
593 for(NodeContext expanded : state.expandedNodes) {
594 isExpandedProcessor.setExpanded(expanded, true);
600 public NodeContext getRoot() {
605 public IThreadWorkQueue getThread() {
610 public NodeContext getParentContext(NodeContext context) {
612 throw new IllegalStateException("disposed");
613 if (!thread.currentThreadAccess())
614 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
616 List<TreeNode> nodes = contextToNodeMap.getValuesUnsafe(context);
617 for (int i = 0; i < nodes.size(); i++) {
618 if (nodes.get(i).getParent() != null)
619 return nodes.get(i).getParent().getContext();
626 @SuppressWarnings("unchecked")
628 public <T> T getAdapter(Class<T> adapter) {
629 if(ISelectionProvider.class == adapter) return (T) postSelectionProvider;
630 else if(IPostSelectionProvider.class == adapter) return (T) postSelectionProvider;
635 protected void setDefaultProcessors() {
636 // Add a simple IMAGER query processor that always returns null.
637 // With this processor no images will ever be shown.
638 // setPrimitiveProcessor(new StaticImagerProcessor(null));
640 setProcessor(new DefaultComparableChildrenProcessor());
641 setProcessor(new DefaultLabelDecoratorsProcessor());
642 setProcessor(new DefaultImageDecoratorsProcessor());
643 setProcessor(new DefaultSelectedLabelerProcessor());
644 setProcessor(new DefaultLabelerFactoriesProcessor());
645 setProcessor(new DefaultSelectedImagerProcessor());
646 setProcessor(new DefaultImagerFactoriesProcessor());
647 setPrimitiveProcessor(new DefaultLabelerProcessor());
648 setPrimitiveProcessor(new DefaultCheckedStateProcessor());
649 setPrimitiveProcessor(new DefaultImagerProcessor());
650 setPrimitiveProcessor(new DefaultLabelDecoratorProcessor());
651 setPrimitiveProcessor(new DefaultImageDecoratorProcessor());
652 setPrimitiveProcessor(new NoSelectionRequestProcessor());
654 setProcessor(new DefaultFinalChildrenProcessor(this));
656 setProcessor(new DefaultPrunedChildrenProcessor());
657 setProcessor(new DefaultSelectedViewpointProcessor());
658 setProcessor(new DefaultSelectedLabelDecoratorFactoriesProcessor());
659 setProcessor(new DefaultSelectedImageDecoratorFactoriesProcessor());
660 setProcessor(new DefaultViewpointContributionsProcessor());
662 setPrimitiveProcessor(new DefaultViewpointProcessor());
663 setPrimitiveProcessor(new DefaultViewpointContributionProcessor());
664 setPrimitiveProcessor(new DefaultSelectedViewpointFactoryProcessor());
665 setPrimitiveProcessor(new TreeNodeIsExpandedProcessor());
666 setPrimitiveProcessor(new DefaultShowMaxChildrenProcessor());
670 public Column[] getColumns() {
671 return Arrays.copyOf(columns, columns.length);
675 public void setColumnsVisible(boolean visible) {
676 columnsAreVisible = visible;
677 //FIXME if(natTable != null) this.columnHeaderDataLayer.setHeaderVisible(columnsAreVisible);
681 public void setColumns(final Column[] columns) {
682 setColumns(columns, null);
686 public void setColumns(final Column[] columns, Consumer<Map<Column, Object>> callback) {
688 checkUniqueColumnKeys(columns);
690 Display d = composite.getDisplay();
691 if (d.getThread() == Thread.currentThread()) {
692 doSetColumns(columns, callback);
693 natTable.refresh(true);
695 d.asyncExec(new Runnable() {
698 if (natTable == null)
700 if (natTable.isDisposed())
702 doSetColumns(columns, callback);
704 natTable.getParent().layout();
709 private void checkUniqueColumnKeys(Column[] cols) {
710 Set<String> usedColumnKeys = new HashSet<String>();
711 List<Column> duplicateColumns = new ArrayList<Column>();
712 for (Column c : cols) {
713 if (!usedColumnKeys.add(c.getKey()))
714 duplicateColumns.add(c);
716 if (!duplicateColumns.isEmpty()) {
717 throw new IllegalArgumentException("All columns do not have unique keys: " + cols + ", overlapping: " + duplicateColumns);
721 private void doSetColumns(Column[] cols, Consumer<Map<Column, Object>> callback) {
723 HashMap<String, Integer> keyToIndex = new HashMap<String, Integer>();
724 for (int i = 0; i < cols.length; ++i) {
725 keyToIndex.put(cols[i].getKey(), i);
728 this.columns = Arrays.copyOf(cols, cols.length);
729 //this.columns[cols.length] = FILLER_COLUMN;
730 this.columnKeyToIndex = keyToIndex;
732 columnHeaderDataProvider.updateColumnSizes();
734 Map<Column, Object> map = new HashMap<Column, Object>();
736 // FIXME : temporary workaround for ModelBrowser.
737 // natTable.setHeaderVisible(columns.length == 1 ? false : columnsAreVisible);
741 for (Column column : columns) {
742 int width = column.getWidth();
743 if(column.hasGrab()) {
746 layout.setColumnData(columnIndex, new ColumnWeightData(column.getWeight(), width));
751 layout.setColumnData(columnIndex, new ColumnWeightData(columns.length > 1 ? 0 : 1, width));
759 if(callback != null) callback.accept(map);
762 int toSWT(Align alignment) {
764 case LEFT: return SWT.LEFT;
765 case CENTER: return SWT.CENTER;
766 case RIGHT: return SWT.RIGHT;
767 default: throw new Error("unhandled alignment: " + alignment);
772 public <T> void setProcessor(NodeQueryProcessor<T> processor) {
774 if (processor == null)
775 throw new IllegalArgumentException("null processor");
777 processors.put(processor.getIdentifier(), processor);
781 public <T> void setPrimitiveProcessor(PrimitiveQueryProcessor<T> processor) {
783 if (processor == null)
784 throw new IllegalArgumentException("null processor");
786 PrimitiveQueryProcessor<?> oldProcessor = primitiveProcessors.put(
787 processor.getIdentifier(), processor);
789 if (oldProcessor instanceof ProcessorLifecycle)
790 ((ProcessorLifecycle) oldProcessor).detached(this);
791 if (processor instanceof ProcessorLifecycle)
792 ((ProcessorLifecycle) processor).attached(this);
796 public <T> void setDataSource(DataSource<T> provider) {
798 if (provider == null)
799 throw new IllegalArgumentException("null provider");
800 dataSources.put(provider.getProvidedClass(), provider);
803 @SuppressWarnings("unchecked")
805 public <T> DataSource<T> removeDataSource(Class<T> forProvidedClass) {
807 if (forProvidedClass == null)
808 throw new IllegalArgumentException("null class");
809 return dataSources.remove(forProvidedClass);
813 public void setPersistor(StatePersistor persistor) {
814 this.persistor = persistor;
818 public SelectionDataResolver getSelectionDataResolver() {
819 return selectionDataResolver;
823 public void setSelectionDataResolver(SelectionDataResolver r) {
824 this.selectionDataResolver = r;
828 public SelectionFilter getSelectionFilter() {
829 return selectionFilter;
833 public void setSelectionFilter(SelectionFilter f) {
834 this.selectionFilter = f;
835 // TODO: re-filter current selection?
838 protected ISelection constructSelection(NodeContext... contexts) {
839 if (contexts == null)
840 throw new IllegalArgumentException("null contexts");
841 if (contexts.length == 0)
842 return StructuredSelection.EMPTY;
843 if (selectionFilter == null)
844 return new StructuredSelection(transformSelection(contexts));
845 return new StructuredSelection( transformSelection(filter(selectionFilter, contexts)) );
848 protected Object[] transformSelection(Object[] objects) {
849 return selectionTransformation.call(this, objects);
852 protected static Object[] filter(SelectionFilter filter, NodeContext[] contexts) {
853 int len = contexts.length;
854 Object[] objects = new Object[len];
855 for (int i = 0; i < len; ++i)
856 objects[i] = filter.filter(contexts[i]);
861 public void setSelectionTransformation(
862 BinaryFunction<Object[], GraphExplorer, Object[]> f) {
863 this.selectionTransformation = f;
866 public ISelection getWidgetSelection() {
867 return selectionAdaptor.getSelection();
871 public <T> void addListener(T listener) {
872 if (listener instanceof FocusListener) {
873 focusListeners.add((FocusListener) listener);
874 } else if (listener instanceof MouseListener) {
875 mouseListeners.add((MouseListener) listener);
876 } else if (listener instanceof KeyListener) {
877 keyListeners.add((KeyListener) listener);
882 public <T> void removeListener(T listener) {
883 if (listener instanceof FocusListener) {
884 focusListeners.remove(listener);
885 } else if (listener instanceof MouseListener) {
886 mouseListeners.remove(listener);
887 } else if (listener instanceof KeyListener) {
888 keyListeners.remove(listener);
892 public void addSelectionListener(SelectionListener listener) {
893 selectionAdaptor.addSelectionListener(listener);
896 public void removeSelectionListener(SelectionListener listener) {
897 selectionAdaptor.removeSelectionListener(listener);
900 private Set<String> uiContexts;
903 public void setUIContexts(Set<String> contexts) {
904 this.uiContexts = contexts;
908 public void setRoot(final Object root) {
909 if(uiContexts != null && uiContexts.size() == 1)
910 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root, BuiltinKeys.UI_CONTEXT, uiContexts.iterator().next()));
912 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root));
916 public void setRootContext(final NodeContext context) {
917 setRootContext0(context);
920 private void setRoot(NodeContext context) {
922 pendingRoot = context;
923 Display.getDefault().asyncExec(new Runnable() {
926 if (natTable!= null && !natTable.isDisposed())
935 private void setRootContext0(final NodeContext context) {
936 Assert.isNotNull(context, "root must not be null");
937 if (isDisposed() || natTable.isDisposed())
939 Display display = natTable.getDisplay();
940 if (display.getThread() == Thread.currentThread()) {
943 display.asyncExec(new Runnable() {
953 public void setFocus() {
957 @SuppressWarnings("unchecked")
959 public <T> T getControl() {
965 public boolean isDisposed() {
969 protected void assertNotDisposed() {
971 throw new IllegalStateException("disposed");
975 public boolean isEditable() {
980 public void setEditable(boolean editable) {
981 if (!thread.currentThreadAccess())
982 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
984 this.editable = editable;
985 Display display = natTable.getDisplay();
986 natTable.setBackground(editable ? null : display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
989 private void doDispose() {
993 // TODO: Since GENodeQueryManager is cached in QueryChache and it refers to this class
994 // we have to remove all references here to reduce memory consumption.
996 // Proper fix would be to remove references between QueryCache and GENodeQueryManagers.
997 if (rootNode != null) {
1001 explorerContext.dispose();
1002 explorerContext = null;
1004 detachPrimitiveProcessors();
1005 primitiveProcessors.clear();
1006 dataSources.clear();
1007 pendingItems.clear();
1009 mouseListeners.clear();
1010 selectionProvider.clearListeners();
1011 selectionProvider = null;
1012 selectionDataResolver = null;
1013 selectedNodes.clear();
1014 selectedNodes = null;
1015 selectionTransformation = null;
1016 originalFont = null;
1017 localResourceManager.dispose();
1018 localResourceManager = null;
1019 // Must shutdown image loader job before disposing its ResourceManager
1020 imageLoaderJob.dispose();
1021 imageLoaderJob.cancel();
1023 imageLoaderJob.join();
1024 imageLoaderJob = null;
1025 } catch (InterruptedException e) {
1026 ErrorLogger.defaultLogError(e);
1028 resourceManager.dispose();
1029 resourceManager = null;
1031 contextToNodeMap.clear(); // should be empty at this point.
1032 contextToNodeMap = null;
1033 if (postSelectionProvider != null) {
1034 postSelectionProvider.dispose();
1035 postSelectionProvider = null;
1038 modificationContext = null;
1039 focusService = null;
1040 contextService = null;
1041 serviceLocator = null;
1043 columnKeyToIndex.clear();
1044 columnKeyToIndex = null;
1045 // if (natTable != null) {
1046 // natTable.dispose();
1051 viewportLayer = null;
1052 selectionLayer = null;
1053 columnHeaderDataProvider = null;
1054 columnAccessor = null;
1055 rowHeaderDataLayer = null;
1056 columnHeaderDataLayer = null;
1057 cornerDataLayer = null;
1062 public boolean select(NodeContext context) {
1064 assertNotDisposed();
1066 if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) {
1067 StructuredSelection s = new StructuredSelection();
1068 selectionAdaptor.setSelection(s);
1069 selectionProvider.setAndFireNonEqualSelection(s);
1073 selectionAdaptor.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0)));
1079 public boolean select(TreeNode node) {
1080 assertNotDisposed();
1082 if (!list.contains(node)) {
1083 StructuredSelection s = new StructuredSelection();
1084 selectionAdaptor.setSelection(s);
1085 selectionProvider.setAndFireNonEqualSelection(s);
1088 selectionAdaptor.setSelection(new StructuredSelection(node));
1092 public void show(TreeNode node) {
1093 int index = node.getListIndex();
1095 int position = treeLayer.getRowPositionByIndex(index);
1097 treeLayer.expandToTreeRow(index);
1098 position = treeLayer.getRowPositionByIndex(index);
1100 viewportLayer.moveRowPositionIntoViewport(position);
1104 public boolean selectPath(Collection<NodeContext> contexts) {
1106 if(contexts == null) throw new IllegalArgumentException("Null list is not allowed");
1107 if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed");
1109 return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0);
1113 private boolean selectPathInternal(NodeContext[] contexts, int position) {
1115 NodeContext head = contexts[position];
1117 if(position == contexts.length-1) {
1118 return select(head);
1122 setExpanded(head, true);
1123 if(!waitVisible(contexts[position+1])) return false;
1125 return selectPathInternal(contexts, position+1);
1129 private boolean waitVisible(NodeContext context) {
1130 long start = System.nanoTime();
1131 while(!isVisible(context)) {
1132 Display.getCurrent().readAndDispatch();
1133 long duration = System.nanoTime() - start;
1134 if(duration > 10e9) return false;
1140 public boolean isVisible(NodeContext context) {
1141 if (contextToNodeMap.getValuesUnsafe(context).size() == 0)
1144 return true; //FIXME
1145 // Object elements[] = viewer.getVisibleExpandedElements();
1146 // return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0));
1152 public TransientExplorerState getTransientState() {
1153 if (!thread.currentThreadAccess())
1154 throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread());
1155 return transientState;
1159 public <T> T query(NodeContext context, CacheKey<T> key) {
1160 return this.explorerContext.cache.get(context, key);
1164 * For setting a more local service locator for the explorer than the global
1165 * workbench service locator. Sometimes required to give this implementation
1166 * access to local workbench services like IFocusService.
1169 * Must be invoked during right after construction.
1171 * @param serviceLocator
1172 * a specific service locator or <code>null</code> to use the
1173 * workbench global service locator
1175 public void setServiceLocator(IServiceLocator serviceLocator) {
1176 if (serviceLocator == null && PlatformUI.isWorkbenchRunning())
1177 serviceLocator = PlatformUI.getWorkbench();
1178 this.serviceLocator = serviceLocator;
1179 if (serviceLocator != null) {
1180 this.contextService = (IContextService) serviceLocator.getService(IContextService.class);
1181 this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class);
1185 private void detachPrimitiveProcessors() {
1186 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
1187 if (p instanceof ProcessorLifecycle) {
1188 ((ProcessorLifecycle) p).detached(this);
1193 private void clearPrimitiveProcessors() {
1194 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
1195 if (p instanceof ProcessorLifecycle) {
1196 ((ProcessorLifecycle) p).clear();
1202 public void setExpanded(NodeContext context, boolean expanded) {
1203 for (TreeNode n : contextToNodeMap.getValues(context)) {
1205 treeLayer.expandTreeRow(n.getListIndex());
1207 treeLayer.collapseTreeRow(n.getListIndex());
1213 public void setAutoExpandLevel(int level) {
1214 this.autoExpandLevel = level;
1215 treeLayer.expandAllToLevel(level);
1218 int maxChildren = DEFAULT_MAX_CHILDREN;
1221 public int getMaxChildren() {
1226 public void setMaxChildren(int maxChildren) {
1227 this.maxChildren = maxChildren;
1232 public int getMaxChildren(NodeQueryManager manager, NodeContext context) {
1233 Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN);
1234 //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result);
1235 if (result != null) {
1237 throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result);
1244 public <T> NodeQueryProcessor<T> getProcessor(QueryKey<T> key) {
1245 return explorerContext.getProcessor(key);
1249 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(PrimitiveQueryKey<T> key) {
1250 return explorerContext.getPrimitiveProcessor(key);
1253 private HashSet<UpdateItem> pendingItems = new HashSet<UpdateItem>();
1254 private boolean updating = false;
1255 private int updateCounter = 0;
1256 final ScheduledExecutorService uiUpdateScheduler = ThreadUtils.getNonBlockingWorkExecutor();
1258 private class UpdateItem {
1262 public UpdateItem(TreeNode element) {
1266 public UpdateItem(TreeNode element, int columnIndex) {
1267 this.element = element;
1268 this.columnIndex = columnIndex;
1269 if (element != null && element.isDisposed()) {
1270 throw new IllegalArgumentException("Node is disposed. " + element);
1274 public void update(NatTable natTable) {
1275 if (element != null) {
1277 if (element.isDisposed()) {
1280 if (element.updateChildren()) {
1282 System.out.println("Update Item updateChildren " + element.listIndex + " " + element);
1283 printDebug(NatTableGraphExplorer.this);
1286 if (!element.isHidden()) {
1287 if (!element.isExpanded()) {
1288 if (element.listIndex >= 0)
1289 treeLayer.collapseTreeRow(element.listIndex);
1291 System.out.println("Update Item collapse " + element.listIndex);
1292 printDebug(NatTableGraphExplorer.this);
1295 for (TreeNode c : element.getChildren())
1299 TreeNode p = element.getCollapsedAncestor();
1301 if (element.listIndex >= 0)
1302 treeLayer.collapseTreeRow(element.listIndex);
1303 if (p.listIndex >= 0)
1304 treeLayer.collapseTreeRow(p.listIndex);
1306 System.out.println("Update Item ancetor collapse " + p.listIndex);
1307 printDebug(NatTableGraphExplorer.this);
1312 // if (columnIndex >= 0) {
1313 // viewer.update(element, new String[]{columns[columnIndex].getKey()});
1315 // 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 private 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 int defaultFontSize = 12;
1522 int height = (int)Math.ceil(((double)(defaultFontSize))*getDisplayScale()) + DataLayer.DEFAULT_ROW_HEIGHT-defaultFontSize;
1523 dataLayer = new DataLayer(dataProvider, DataLayer.DEFAULT_COLUMN_WIDTH, height);
1525 // resizable rows are unnecessary in Sulca report.
1526 dataLayer.setRowsResizableByDefault(false);
1529 DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(dataProvider);
1530 rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider);
1532 // adjust row header column width so that row numbers fit into the column.
1533 //adjustRowHeaderWidth(list.size());
1535 // Column header layer
1536 columnHeaderDataProvider = new GEColumnHeaderDataProvider(this, dataLayer);
1537 columnHeaderDataLayer = new DefaultColumnHeaderDataLayer(columnHeaderDataProvider);
1538 columnHeaderDataLayer.setDefaultRowHeight(height);
1539 columnHeaderDataProvider.updateColumnSizes();
1541 //ISortModel sortModel = new EcoSortModel(this, generator,dataLayer);
1543 // Column re-order + hide
1544 ColumnReorderLayer columnReorderLayer = new ColumnReorderLayer(dataLayer);
1545 ColumnHideShowLayer columnHideShowLayer = new ColumnHideShowLayer(columnReorderLayer);
1548 treeLayer = new GETreeLayer(columnHideShowLayer, treeRowModel, false);
1550 selectionLayer = new SelectionLayer(treeLayer);
1552 viewportLayer = new ViewportLayer(selectionLayer);
1554 ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, viewportLayer, selectionLayer);
1555 // Note: The column header layer is wrapped in a filter row composite.
1556 // This plugs in the filter row functionality
1558 ColumnOverrideLabelAccumulator labelAccumulator = new ColumnOverrideLabelAccumulator(columnHeaderDataLayer);
1559 columnHeaderDataLayer.setConfigLabelAccumulator(labelAccumulator);
1562 //SortHeaderLayer<TreeNode> sortHeaderLayer = new SortHeaderLayer<TreeNode>(columnHeaderLayer, sortModel, false);
1564 RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, viewportLayer, selectionLayer);
1567 DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider);
1568 cornerDataLayer = new DataLayer(cornerDataProvider);
1569 //CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, sortHeaderLayer);
1570 CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, columnHeaderLayer);
1573 //GridLayer gridLayer = new GridLayer(viewportLayer,sortHeaderLayer,rowHeaderLayer, cornerLayer);
1574 GridLayer gridLayer = new GridLayer(viewportLayer, columnHeaderLayer,rowHeaderLayer, cornerLayer, false);
1576 /* Since 1.4.0, alternative row rendering uses row indexes in the original data list.
1577 When combined with collapsed tree rows, rows with odd or even index may end up next to each other,
1578 which defeats the purpose of alternating colors. This overrides that and returns the functionality
1579 that we had with 1.0.1. */
1580 gridLayer.setConfigLabelAccumulatorForRegion(GridRegion.BODY, new RelativeAlternatingRowConfigLabelAccumulator());
1581 gridLayer.addConfiguration(new DefaultEditConfiguration());
1582 //gridLayer.addConfiguration(new DefaultEditBindings());
1583 gridLayer.addConfiguration(new GEEditBindings());
1585 natTable = new NatTable(composite,gridLayer,false);
1587 //selectionLayer.registerCommandHandler(new EcoCopyDataCommandHandler(selectionLayer,columnHeaderDataLayer,columnAccessor, columnHeaderDataProvider));
1589 natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable));
1590 natTable.addConfiguration(new DefaultTreeLayerConfiguration2(treeLayer));
1591 natTable.addConfiguration(new SingleClickSortConfiguration());
1592 //natTable.addLayerListener(this);
1594 natTable.addConfiguration(new GENatTableThemeConfiguration(treeData, style));
1595 natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable));
1597 natTable.addConfiguration(new AbstractRegistryConfiguration() {
1600 public void configureRegistry(IConfigRegistry configRegistry) {
1601 configRegistry.registerConfigAttribute(
1602 EditConfigAttributes.CELL_EDITABLE_RULE,
1603 new IEditableRule() {
1606 public boolean isEditable(ILayerCell cell,
1607 IConfigRegistry configRegistry) {
1608 int col = cell.getColumnIndex();
1609 int row = cell.getRowIndex();
1610 TreeNode node = list.get(row);
1611 Modifier modifier = getModifier(node,col);
1612 return modifier != null;
1617 public boolean isEditable(int columnIndex, int rowIndex) {
1618 // there are no callers?
1623 configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, new AdaptableCellEditor());
1624 configRegistry.registerConfigAttribute(EditConfigAttributes.CONVERSION_ERROR_HANDLER, new DialogErrorHandling(), DisplayMode.EDIT);
1625 configRegistry.registerConfigAttribute(EditConfigAttributes.VALIDATION_ERROR_HANDLER, new DialogErrorHandling(), DisplayMode.EDIT);
1626 configRegistry.registerConfigAttribute(EditConfigAttributes.DATA_VALIDATOR, new AdaptableDataValidator(),DisplayMode.EDIT);
1628 Style conversionErrorStyle = new Style();
1629 conversionErrorStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, GUIHelper.COLOR_RED);
1630 conversionErrorStyle.setAttributeValue(CellStyleAttributes.FOREGROUND_COLOR, GUIHelper.COLOR_WHITE);
1631 configRegistry.registerConfigAttribute(EditConfigAttributes.CONVERSION_ERROR_STYLE, conversionErrorStyle, DisplayMode.EDIT);
1633 configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, new DefaultDisplayConverter(),DisplayMode.EDIT);
1639 if ((style & SWT.BORDER) > 0) {
1640 natTable.addOverlayPainter(new NatTableBorderOverlayPainter());
1643 natTable.configure();
1645 // natTable.addListener(SWT.MenuDetect, new NatTableMenuListener());
1647 // DefaultToolTip toolTip = new EcoCellToolTip(natTable, columnAccessor);
1648 // toolTip.setBackgroundColor(natTable.getDisplay().getSystemColor(SWT.COLOR_WHITE));
1649 // toolTip.setPopupDelay(500);
1650 // toolTip.activate();
1651 // toolTip.setShift(new Point(10, 10));
1654 // menuManager.createContextMenu(composite);
1655 // natTable.setMenu(getMenuManager().getMenu());
1657 selectionAdaptor = new NatTableSelectionAdaptor(natTable, selectionLayer, treeData);
1660 Modifier getModifier(TreeNode element, int columnIndex) {
1661 GENodeQueryManager manager = element.getManager();
1662 final NodeContext context = element.getContext();
1663 Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
1664 if (labeler == null)
1666 Column column = columns[columnIndex];
1668 return labeler.getModifier(modificationContext, column.getKey());
1672 private class AdaptableCellEditor implements ICellEditor {
1676 public Control activateCell(Composite parent, Object originalCanonicalValue, EditModeEnum editMode,
1677 ICellEditHandler editHandler, ILayerCell cell, IConfigRegistry configRegistry) {
1678 int col = cell.getColumnIndex();
1679 int row = cell.getRowIndex();
1680 TreeNode node = list.get(row);
1681 Modifier modifier = getModifier(node, col);
1682 if (modifier == null)
1686 if (modifier instanceof DialogModifier) {
1687 DialogModifier mod = (DialogModifier)modifier;
1688 editor = new DialogCellEditor(node, col, mod);
1689 } else if (modifier instanceof CustomModifier) {
1690 CustomModifier mod = (CustomModifier)modifier;
1691 editor = new CustomCellEditor(node, col, mod);
1692 } else if (modifier instanceof EnumerationModifier) {
1693 EnumerationModifier mod = (EnumerationModifier)modifier;
1694 editor = new ComboBoxCellEditor(mod.getValues());
1696 editor = new TextCellEditor();
1699 return editor.activateCell(parent, originalCanonicalValue, editMode, editHandler, cell, configRegistry);
1703 public int getColumnIndex() {
1704 return editor.getColumnIndex();
1708 public int getRowIndex() {
1709 return editor.getRowIndex();
1713 public int getColumnPosition() {
1714 return editor.getColumnPosition();
1718 public int getRowPosition() {
1719 return editor.getRowPosition();
1723 public Object getEditorValue() {
1724 return editor.getEditorValue();
1728 public void setEditorValue(Object value) {
1729 editor.setEditorValue(value);
1734 public Object getCanonicalValue() {
1735 return editor.getCanonicalValue();
1739 public Object getCanonicalValue(IEditErrorHandler conversionErrorHandler) {
1740 return editor.getCanonicalValue();
1744 public void setCanonicalValue(Object canonicalValue) {
1745 editor.setCanonicalValue(canonicalValue);
1750 public boolean validateCanonicalValue(Object canonicalValue) {
1751 return editor.validateCanonicalValue(canonicalValue);
1755 public boolean validateCanonicalValue(Object canonicalValue, IEditErrorHandler validationErrorHandler) {
1756 return editor.validateCanonicalValue(canonicalValue, validationErrorHandler);
1760 public boolean commit(MoveDirectionEnum direction) {
1761 return editor.commit(direction);
1765 public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit) {
1766 return editor.commit(direction, closeAfterCommit);
1770 public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit, boolean skipValidation) {
1771 return editor.commit(direction, closeAfterCommit, skipValidation);
1775 public void close() {
1781 public boolean isClosed() {
1782 return editor.isClosed();
1786 public Control getEditorControl() {
1787 return editor.getEditorControl();
1791 public Control createEditorControl(Composite parent) {
1792 return editor.createEditorControl(parent);
1796 public boolean openInline(IConfigRegistry configRegistry, List<String> configLabels) {
1797 return EditConfigHelper.openInline(configRegistry, configLabels);
1801 public boolean supportMultiEdit(IConfigRegistry configRegistry, List<String> configLabels) {
1802 return editor.supportMultiEdit(configRegistry, configLabels);
1806 public boolean openMultiEditDialog() {
1807 return editor.openMultiEditDialog();
1811 public boolean openAdjacentEditor() {
1812 return editor.openAdjacentEditor();
1816 public boolean activateAtAnyPosition() {
1821 public boolean activateOnTraversal(IConfigRegistry configRegistry, List<String> configLabels) {
1822 return editor.activateOnTraversal(configRegistry, configLabels);
1826 public void addEditorControlListeners() {
1827 editor.addEditorControlListeners();
1832 public void removeEditorControlListeners() {
1833 editor.removeEditorControlListeners();
1838 public Rectangle calculateControlBounds(Rectangle cellBounds) {
1839 return editor.calculateControlBounds(cellBounds);
1843 private class AdaptableDataValidator implements IDataValidator {
1845 public boolean validate(ILayerCell cell, IConfigRegistry configRegistry, Object newValue) {
1846 int col = cell.getColumnIndex();
1847 int row = cell.getRowIndex();
1848 return validate(col, row, newValue);
1852 public boolean validate(int col, int row, Object newValue) {
1853 TreeNode node = list.get(row);
1854 Modifier modifier = getModifier(node, col);
1855 if (modifier == null)
1858 String err = modifier.isValid(newValue.toString());
1861 modifier.isValid(newValue.toString());
1862 throw new ValidationFailedException(err);
1866 private class CustomCellEditor extends AbstractCellEditor {
1868 CustomModifier customModifier;
1872 public CustomCellEditor(TreeNode node, int column, CustomModifier customModifier) {
1873 this.customModifier = customModifier;
1875 this.column = column;
1879 public Object getEditorValue() {
1880 return customModifier.getValue();
1884 public void setEditorValue(Object value) {
1885 customModifier.modify(value.toString());
1890 public Control getEditorControl() {
1895 public Control createEditorControl(Composite parent) {
1896 return (Control)customModifier.createControl(parent, null, column, node.getContext());
1901 protected Control activateCell(Composite parent, Object originalCanonicalValue) {
1902 this.control = createEditorControl(parent);
1909 private class DialogCellEditor extends AbstractDialogCellEditor {
1911 DialogModifier dialogModifier;
1917 boolean closed = false;
1919 public DialogCellEditor(TreeNode node, int column, DialogModifier dialogModifier) {
1920 this.dialogModifier = dialogModifier;
1922 this.column = column;
1927 sem = new Semaphore(1);
1928 Consumer<String> callback = result -> {
1932 String status = dialogModifier.query(this.parent.getShell(), null, column, node.getContext(), callback);
1933 if (status != null) {
1935 return Window.CANCEL;
1940 } catch (InterruptedException e) {
1941 e.printStackTrace();
1948 public DialogModifier createDialogInstance() {
1950 return dialogModifier;
1954 public Object getDialogInstance() {
1955 return (DialogModifier)this.dialog;
1959 public void close() {
1964 public Object getEditorValue() {
1969 public void setEditorValue(Object value) {
1970 // dialog modifier handles this internally
1974 public boolean isClosed() {
1982 * The job that is used for off-loading image loading tasks (see
1983 * {@link ImageTask} to a worker thread from the main UI thread.
1985 ImageLoaderJob imageLoaderJob;
1987 // Map<NodeContext, ImageTask> imageTasks = new THashMap<NodeContext, ImageTask>();
1988 Map<TreeNode, ImageTask> imageTasks = new THashMap<TreeNode, ImageTask>();
1990 void queueImageTask(TreeNode node, ImageTask task) {
1991 synchronized (imageTasks) {
1992 imageTasks.put(node, task);
1994 imageLoaderJob.scheduleIfNecessary(100);
1998 * Invoked in a job worker thread.
2003 protected IStatus setPendingImages(IProgressMonitor monitor) {
2004 ImageTask[] tasks = null;
2005 synchronized (imageTasks) {
2006 tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]);
2010 MultiStatus status = null;
2012 // Load missing images
2013 for (ImageTask task : tasks) {
2014 Object desc = task.descsOrImage;
2015 if (desc instanceof ImageDescriptor) {
2017 desc = resourceManager.get((ImageDescriptor) desc);
2018 task.descsOrImage = desc;
2019 } catch (DeviceResourceException e) {
2021 status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null);
2022 status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e));
2028 // Perform final UI updates in the UI thread.
2029 final ImageTask[] _tasks = tasks;
2030 thread.asyncExec(new Runnable() {
2037 return status != null ? status : Status.OK_STATUS;
2041 void setImages(ImageTask[] tasks) {
2042 for (ImageTask task : tasks)
2047 void setImage(ImageTask task) {
2048 if (!task.node.isDisposed())
2049 update(task.node, 0);
2052 private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider {
2054 private NatTableGraphExplorer ge;
2056 GraphExplorerPostSelectionProvider(NatTableGraphExplorer ge) {
2065 public void setSelection(final ISelection selection) {
2066 if(ge == null) return;
2067 ge.setSelection(selection, false);
2073 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
2074 if(ge == null) return;
2075 if(ge.isDisposed()) {
2076 if (DEBUG_SELECTION_LISTENERS)
2077 System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener);
2080 ge.selectionProvider.removeSelectionChangedListener(listener);
2084 public void addPostSelectionChangedListener(ISelectionChangedListener listener) {
2085 if(ge == null) return;
2086 if (!ge.thread.currentThreadAccess())
2087 throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2088 if(ge.isDisposed()) {
2089 System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener);
2092 ge.selectionProvider.addPostSelectionChangedListener(listener);
2096 public void removePostSelectionChangedListener(ISelectionChangedListener listener) {
2097 if(ge == null) return;
2098 if(ge.isDisposed()) {
2099 if (DEBUG_SELECTION_LISTENERS)
2100 System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener);
2103 ge.selectionProvider.removePostSelectionChangedListener(listener);
2108 public void addSelectionChangedListener(ISelectionChangedListener listener) {
2109 if(ge == null) return;
2110 if (!ge.thread.currentThreadAccess())
2111 throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2112 if (ge.natTable.isDisposed() || ge.selectionProvider == null) {
2113 System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener);
2117 ge.selectionProvider.addSelectionChangedListener(listener);
2122 public ISelection getSelection() {
2123 if(ge == null) return StructuredSelection.EMPTY;
2124 if (!ge.thread.currentThreadAccess())
2125 throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread());
2126 if (ge.natTable.isDisposed() || ge.selectionProvider == null)
2127 return StructuredSelection.EMPTY;
2128 return ge.selectionProvider.getSelection();
2133 static class ModifierValidator implements ICellEditorValidator {
2134 private Modifier modifier;
2135 public ModifierValidator(Modifier modifier) {
2136 this.modifier = modifier;
2140 public String isValid(Object value) {
2141 return modifier.isValid((String)value);
2145 static class UpdateRunner implements Runnable {
2147 final NatTableGraphExplorer ge;
2149 UpdateRunner(NatTableGraphExplorer ge, IGraphExplorerContext geContext) {
2156 } catch (Throwable t) {
2157 t.printStackTrace();
2161 public void doRun() {
2163 if (ge.isDisposed())
2166 HashSet<UpdateItem> items;
2168 ScrollBar verticalBar = ge.natTable.getVerticalBar();
2171 synchronized (ge.pendingItems) {
2172 items = ge.pendingItems;
2173 ge.pendingItems = new HashSet<UpdateItem>();
2175 if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size());
2177 //ge.natTable.setRedraw(false);
2178 for (UpdateItem item : items) {
2179 item.update(ge.natTable);
2182 // check if vertical scroll bar has become visible and refresh layout.
2183 boolean currentlyVerticalBarVisible = verticalBar.isVisible();
2184 if (ge.verticalBarVisible != currentlyVerticalBarVisible) {
2185 ge.verticalBarVisible = currentlyVerticalBarVisible;
2186 ge.natTable.getParent().layout();
2189 //ge.natTable.setRedraw(true);
2191 synchronized (ge.pendingItems) {
2192 if (!ge.scheduleUpdater()) {
2193 ge.updating = false;
2205 private static void printDebug(NatTableGraphExplorer ge) {
2206 ge.printTree(ge.rootNode, 0);
2207 System.out.println("Expanded");
2208 for (TreeNode n : ge.treeLayer.expanded)
2209 System.out.println(n);
2210 System.out.println("Expanded end");
2211 System.out.println("Hidden ");
2212 for (int i : ge.treeLayer.getHiddenRowIndexes()) {
2213 System.out.print(i + " ");
2215 System.out.println();
2216 // Display.getCurrent().timerExec(1000, new Runnable() {
2219 // public void run() {
2220 // System.out.println("Hidden delayed ");
2221 // for (int i : ge.treeLayer.getHiddenRowIndexes()) {
2222 // System.out.print(i + " ");
2224 // System.out.println();
2230 public static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {
2231 // This is for query debugging only.
2233 private NatTableGraphExplorer ge;
2234 int queryIndent = 0;
2236 GECache2 cache = new GECache2();
2237 AtomicBoolean propagating = new AtomicBoolean(false);
2238 Object propagateList = new Object();
2239 Object propagate = new Object();
2240 List<Runnable> scheduleList = new ArrayList<Runnable>();
2241 final Deque<Integer> activity = new LinkedList<Integer>();
2242 int activityInt = 0;
2244 AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();
2247 * Keeps track of nodes that have already been auto-expanded. After
2248 * being inserted into this set, nodes will not be forced to stay in an
2249 * expanded state after that. This makes it possible for the user to
2250 * close auto-expanded nodes.
2252 Map<NodeContext, Boolean> autoExpanded = new WeakHashMap<NodeContext, Boolean>();
2254 public GeViewerContext(NatTableGraphExplorer ge) {
2258 public MapList<NodeContext,TreeNode> getContextToNodeMap() {
2261 return ge.contextToNodeMap;
2264 public NatTableGraphExplorer getGe() {
2269 protected void doDispose() {
2271 autoExpanded.clear();
2275 public IGECache getCache() {
2280 public int queryIndent() {
2285 public int queryIndent(int offset) {
2286 queryIndent += offset;
2291 @SuppressWarnings("unchecked")
2292 public <T> NodeQueryProcessor<T> getProcessor(Object o) {
2295 return ge.processors.get(o);
2299 @SuppressWarnings("unchecked")
2300 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {
2301 return ge.primitiveProcessors.get(o);
2304 @SuppressWarnings("unchecked")
2306 public <T> DataSource<T> getDataSource(Class<T> clazz) {
2307 return ge.dataSources.get(clazz);
2311 public void update(UIElementReference ref) {
2312 if (ref instanceof ViewerCellReference) {
2313 ViewerCellReference tiref = (ViewerCellReference) ref;
2314 Object element = tiref.getElement();
2315 int columnIndex = tiref.getColumn();
2316 // NOTE: must be called regardless of the the item value.
2317 // A null item is currently used to indicate a tree root update.
2318 ge.update((TreeNode)element,columnIndex);
2319 } else if (ref instanceof ViewerRowReference) {
2320 ViewerRowReference rref = (ViewerRowReference)ref;
2321 Object element = rref.getElement();
2322 ge.update((TreeNode)element);
2324 throw new IllegalArgumentException("Ui Reference is unknkown " + ref);
2329 public Object getPropagateLock() {
2334 public Object getPropagateListLock() {
2335 return propagateList;
2339 public boolean isPropagating() {
2340 return propagating.get();
2344 public void setPropagating(boolean b) {
2345 this.propagating.set(b);
2349 public List<Runnable> getScheduleList() {
2350 return scheduleList;
2354 public void setScheduleList(List<Runnable> list) {
2355 this.scheduleList = list;
2359 public Deque<Integer> getActivity() {
2364 public void setActivityInt(int i) {
2365 this.activityInt = i;
2369 public int getActivityInt() {
2374 public void scheduleQueryUpdate(Runnable r) {
2377 if (ge.isDisposed())
2379 if (currentQueryUpdater.compareAndSet(null, r)) {
2380 ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER);
2384 Runnable QUERY_UPDATE_SCHEDULER = new Runnable() {
2387 Runnable r = currentQueryUpdater.getAndSet(null);
2395 public void dispose() {
2397 cache = new DummyCache();
2398 scheduleList.clear();
2399 autoExpanded.clear();
2400 autoExpanded = null;
2406 private class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor<Boolean> implements
2407 IsExpandedProcessor, ProcessorLifecycle {
2409 * The set of currently expanded node contexts.
2411 private final HashSet<NodeContext> expanded = new HashSet<NodeContext>();
2412 private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();
2414 private NatTable natTable;
2415 private List<TreeNode> list;
2417 public TreeNodeIsExpandedProcessor() {
2421 public Object getIdentifier() {
2422 return BuiltinKeys.IS_EXPANDED;
2426 public String toString() {
2427 return "IsExpandedProcessor";
2431 public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Boolean> key) {
2432 boolean isExpanded = expanded.contains(context);
2433 expandedQueries.put(context, updater);
2434 return Boolean.valueOf(isExpanded);
2438 public Collection<NodeContext> getExpanded() {
2439 return new HashSet<NodeContext>(expanded);
2443 public boolean getExpanded(NodeContext context) {
2444 return this.expanded.contains(context);
2448 public boolean setExpanded(NodeContext context, boolean expanded) {
2449 return _setExpanded(context, expanded);
2453 public boolean replaceExpanded(NodeContext context, boolean expanded) {
2454 return nodeStatusChanged(context, expanded);
2457 private boolean _setExpanded(NodeContext context, boolean expanded) {
2459 return this.expanded.add(context);
2461 return this.expanded.remove(context);
2465 ILayerListener treeListener = new ILayerListener() {
2468 public void handleLayerEvent(ILayerEvent event) {
2469 if (event instanceof ShowRowPositionsEvent) {
2470 ShowRowPositionsEvent e = (ShowRowPositionsEvent)event;
2471 for (Range r : e.getRowPositionRanges()) {
2472 int expanded = viewportLayer.getRowIndexByPosition(r.start-2)+1;
2473 if (DEBUG)System.out.println("IsExpandedProcessor expand " + expanded);
2474 if (expanded < 0 || expanded >= list.size()) {
2477 nodeStatusChanged(list.get(expanded).getContext(), true);
2479 } else if (event instanceof HideRowPositionsEvent) {
2480 HideRowPositionsEvent e = (HideRowPositionsEvent)event;
2481 for (Range r : e.getRowPositionRanges()) {
2482 int collapsed = viewportLayer.getRowIndexByPosition(r.start-2);
2483 if (DEBUG)System.out.println("IsExpandedProcessor collapse " + collapsed);
2484 if (collapsed < 0 || collapsed >= list.size()) {
2487 nodeStatusChanged(list.get(collapsed).getContext(), false);
2494 protected boolean nodeStatusChanged(NodeContext context, boolean expanded) {
2495 boolean result = _setExpanded(context, expanded);
2496 PrimitiveQueryUpdater updater = expandedQueries.get(context);
2497 if (updater != null)
2498 updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded);
2503 public void attached(GraphExplorer explorer) {
2504 Object control = explorer.getControl();
2505 if (control instanceof NatTable) {
2506 this.natTable = (NatTable) control;
2507 this.list = ((NatTableGraphExplorer)explorer).list;
2508 natTable.addLayerListener(treeListener);
2511 System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control);
2516 public void clear() {
2518 expandedQueries.clear();
2522 public void detached(GraphExplorer explorer) {
2524 if (natTable != null) {
2525 natTable.removeLayerListener(treeListener);
2526 // natTable.removeListener(SWT.Expand, treeListener);
2527 // natTable.removeListener(SWT.Collapse, treeListener);
2533 private void printTree(TreeNode node, int depth) {
2535 for (int i = 0; i < depth; i++) {
2539 System.out.println(s);
2541 for (TreeNode n : node.getChildren()) {
2548 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used)
2550 final private static class GECacheKey {
2552 private NodeContext context;
2553 private CacheKey<?> key;
2555 GECacheKey(NodeContext context, CacheKey<?> key) {
2556 this.context = context;
2558 if (context == null || key == null)
2559 throw new IllegalArgumentException("Null context or key is not accepted");
2562 GECacheKey(GECacheKey other) {
2563 this.context = other.context;
2564 this.key = other.key;
2565 if (context == null || key == null)
2566 throw new IllegalArgumentException("Null context or key is not accepted");
2569 void setValues(NodeContext context, CacheKey<?> key) {
2570 this.context = context;
2572 if (context == null || key == null)
2573 throw new IllegalArgumentException("Null context or key is not accepted");
2577 public int hashCode() {
2578 return context.hashCode() | key.hashCode();
2582 public boolean equals(Object object) {
2586 else if (object == null)
2589 GECacheKey i = (GECacheKey) object;
2591 return key.equals(i.key) && context.equals(i.context);
2598 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data.
2600 public static class GECache2 implements IGECache {
2602 final HashMap<GECacheKey, IGECacheEntry> entries = new HashMap<GECacheKey, IGECacheEntry>();
2603 final HashMap<GECacheKey, Set<UIElementReference>> treeReferences = new HashMap<GECacheKey, Set<UIElementReference>>();
2604 final HashMap<NodeContext, Set<GECacheKey>> keyRefs = new HashMap<NodeContext, Set<GECacheKey>>();
2607 * This single instance is used for all get operations from the cache. This
2608 * should work since the GE cache is meant to be single-threaded within the
2609 * current UI thread, what ever that thread is. For put operations which
2610 * store the key, this is not used.
2612 NodeContext getNC = new NodeContext() {
2613 @SuppressWarnings("rawtypes")
2615 public Object getAdapter(Class adapter) {
2620 public <T> T getConstant(ConstantKey<T> key) {
2625 public Set<ConstantKey<?>> getKeys() {
2626 return Collections.emptySet();
2629 CacheKey<?> getCK = new CacheKey<Object>() {
2631 public Object processorIdenfitier() {
2635 GECacheKey getKey = new GECacheKey(getNC, getCK);
2638 private void addKey(GECacheKey key) {
2639 Set<GECacheKey> refs = keyRefs.get(key.context);
2643 refs = new HashSet<GECacheKey>();
2645 keyRefs.put(key.context, refs);
2649 private void removeKey(GECacheKey key) {
2650 Set<GECacheKey> refs = keyRefs.get(key.context);
2656 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key, T value) {
2657 // if (DEBUG) System.out.println("Add entry " + context + " " + key);
2658 IGECacheEntry entry = new GECacheEntry(context, key, value);
2659 GECacheKey gekey = new GECacheKey(context, key);
2660 entries.put(gekey, entry);
2665 @SuppressWarnings("unchecked")
2666 public <T> T get(NodeContext context, CacheKey<T> key) {
2667 getKey.setValues(context, key);
2668 IGECacheEntry entry = entries.get(getKey);
2671 return (T) entry.getValue();
2675 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
2676 assert(context != null);
2677 assert(key != null);
2678 getKey.setValues(context, key);
2679 return entries.get(getKey);
2683 public <T> void remove(NodeContext context, CacheKey<T> key) {
2684 // if (DEBUG) System.out.println("Remove entry " + context + " " + key);
2685 getKey.setValues(context, key);
2686 entries.remove(getKey);
2691 public <T> Set<UIElementReference> getTreeReference(NodeContext context, CacheKey<T> key) {
2692 assert(context != null);
2693 assert(key != null);
2694 getKey.setValues(context, key);
2695 return treeReferences.get(getKey);
2699 public <T> void putTreeReference(NodeContext context, CacheKey<T> key, UIElementReference reference) {
2700 assert(context != null);
2701 assert(key != null);
2702 //if (DEBUG) System.out.println("Add tree reference " + context + " " + key);
2703 getKey.setValues(context, key);
2704 Set<UIElementReference> refs = treeReferences.get(getKey);
2706 refs.add(reference);
2708 refs = new HashSet<UIElementReference>(4);
2709 refs.add(reference);
2710 GECacheKey gekey = new GECacheKey(getKey);
2711 treeReferences.put(gekey, refs);
2717 public <T> Set<UIElementReference> removeTreeReference(NodeContext context, CacheKey<T> key) {
2718 assert(context != null);
2719 assert(key != null);
2720 //if (DEBUG) System.out.println("Remove tree reference " + context + " " + key);
2721 getKey.setValues(context, key);
2723 return treeReferences.remove(getKey);
2727 public boolean isShown(NodeContext context) {
2728 return references.get(context) > 0;
2731 private TObjectIntHashMap<NodeContext> references = new TObjectIntHashMap<NodeContext>();
2734 public void incRef(NodeContext context) {
2735 int exist = references.get(context);
2736 references.put(context, exist+1);
2740 public void decRef(NodeContext context) {
2741 int exist = references.get(context);
2742 references.put(context, exist-1);
2744 references.remove(context);
2748 public void dispose() {
2751 treeReferences.clear();
2755 public void dispose(NodeContext context) {
2756 Set<GECacheKey> keys = keyRefs.remove(context);
2758 for (GECacheKey key : keys) {
2759 entries.remove(key);
2760 treeReferences.remove(key);
2768 * Non-functional cache to replace actual cache when GEContext is disposed.
2773 private static class DummyCache extends GECache2 {
2776 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
2781 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key,
2787 public <T> void putTreeReference(NodeContext context, CacheKey<T> key,
2788 UIElementReference reference) {
2792 public <T> T get(NodeContext context, CacheKey<T> key) {
2797 public <T> Set<UIElementReference> getTreeReference(
2798 NodeContext context, CacheKey<T> key) {
2803 public <T> void remove(NodeContext context, CacheKey<T> key) {
2808 public <T> Set<UIElementReference> removeTreeReference(
2809 NodeContext context, CacheKey<T> key) {
2814 public boolean isShown(NodeContext context) {
2819 public void incRef(NodeContext context) {
2824 public void decRef(NodeContext context) {
2829 public void dispose() {
2834 private class NatTableHeaderMenuConfiguration extends AbstractHeaderMenuConfiguration {
2837 public NatTableHeaderMenuConfiguration(NatTable natTable) {
2842 protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) {
2843 return super.createColumnHeaderMenu(natTable)
2844 .withHideColumnMenuItem()
2845 .withShowAllColumnsMenuItem()
2846 .withAutoResizeSelectedColumnsMenuItem();
2850 protected PopupMenuBuilder createCornerMenu(NatTable natTable) {
2851 return super.createCornerMenu(natTable)
2852 .withShowAllColumnsMenuItem();
2855 protected PopupMenuBuilder createRowHeaderMenu(NatTable natTable) {
2856 return super.createRowHeaderMenu(natTable);
2860 private static class RelativeAlternatingRowConfigLabelAccumulator extends AlternatingRowConfigLabelAccumulator {
2863 public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) {
2864 configLabels.addLabel((rowPosition % 2 == 0 ? EVEN_ROW_CONFIG_TYPE : ODD_ROW_CONFIG_TYPE));
2869 public Object getClicked(Object event) {
2870 MouseEvent e = (MouseEvent)event;
2871 final NatTable tree = (NatTable) e.getSource();
2872 Point point = new Point(e.x, e.y);
2873 int y = natTable.getRowPositionByY(point.y);
2874 int x = natTable.getColumnPositionByX(point.x);
2877 return list.get(y-1);