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 // 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.toString());
1865 modifier.isValid(newValue.toString());
1866 throw new ValidationFailedException(err);
1870 private class CustomCellEditor extends AbstractCellEditor {
1872 CustomModifier customModifier;
1876 public CustomCellEditor(TreeNode node, int column, CustomModifier customModifier) {
1877 this.customModifier = customModifier;
1879 this.column = column;
1883 public Object getEditorValue() {
1884 return customModifier.getValue();
1888 public void setEditorValue(Object value) {
1889 customModifier.modify(value.toString());
1894 public Control getEditorControl() {
1899 public Control createEditorControl(Composite parent) {
1900 return (Control)customModifier.createControl(parent, null, column, node.getContext());
1905 protected Control activateCell(Composite parent, Object originalCanonicalValue) {
1906 this.control = createEditorControl(parent);
1913 private class DialogCellEditor extends AbstractDialogCellEditor {
1915 DialogModifier dialogModifier;
1921 boolean closed = false;
1923 public DialogCellEditor(TreeNode node, int column, DialogModifier dialogModifier) {
1924 this.dialogModifier = dialogModifier;
1926 this.column = column;
1931 sem = new Semaphore(1);
1932 Consumer<String> callback = result -> {
1936 String status = dialogModifier.query(this.parent.getShell(), null, column, node.getContext(), callback);
1937 if (status != null) {
1939 return Window.CANCEL;
1944 } catch (InterruptedException e) {
1945 e.printStackTrace();
1952 public DialogModifier createDialogInstance() {
1954 return dialogModifier;
1958 public Object getDialogInstance() {
1959 return (DialogModifier)this.dialog;
1963 public void close() {
1968 public Object getEditorValue() {
1973 public void setEditorValue(Object value) {
1974 // dialog modifier handles this internally
1978 public boolean isClosed() {
1986 * The job that is used for off-loading image loading tasks (see
1987 * {@link ImageTask} to a worker thread from the main UI thread.
1989 ImageLoaderJob imageLoaderJob;
1991 // Map<NodeContext, ImageTask> imageTasks = new THashMap<NodeContext, ImageTask>();
1992 Map<TreeNode, ImageTask> imageTasks = new THashMap<TreeNode, ImageTask>();
1994 void queueImageTask(TreeNode node, ImageTask task) {
1995 synchronized (imageTasks) {
1996 imageTasks.put(node, task);
1998 imageLoaderJob.scheduleIfNecessary(100);
2002 * Invoked in a job worker thread.
2007 protected IStatus setPendingImages(IProgressMonitor monitor) {
2008 ImageTask[] tasks = null;
2009 synchronized (imageTasks) {
2010 tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]);
2014 MultiStatus status = null;
2016 // Load missing images
2017 for (ImageTask task : tasks) {
2018 Object desc = task.descsOrImage;
2019 if (desc instanceof ImageDescriptor) {
2021 desc = resourceManager.get((ImageDescriptor) desc);
2022 task.descsOrImage = desc;
2023 } catch (DeviceResourceException e) {
2025 status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null);
2026 status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e));
2032 // Perform final UI updates in the UI thread.
2033 final ImageTask[] _tasks = tasks;
2034 thread.asyncExec(new Runnable() {
2041 return status != null ? status : Status.OK_STATUS;
2045 void setImages(ImageTask[] tasks) {
2046 for (ImageTask task : tasks)
2051 void setImage(ImageTask task) {
2052 if (!task.node.isDisposed())
2053 update(task.node, 0);
2056 private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider {
2058 private NatTableGraphExplorer ge;
2060 GraphExplorerPostSelectionProvider(NatTableGraphExplorer ge) {
2069 public void setSelection(final ISelection selection) {
2070 if(ge == null) return;
2071 ge.setSelection(selection, false);
2077 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
2078 if(ge == null) return;
2079 if(ge.isDisposed()) {
2080 if (DEBUG_SELECTION_LISTENERS)
2081 System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener);
2084 ge.selectionProvider.removeSelectionChangedListener(listener);
2088 public void addPostSelectionChangedListener(ISelectionChangedListener listener) {
2089 if(ge == null) return;
2090 if (!ge.thread.currentThreadAccess())
2091 throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2092 if(ge.isDisposed()) {
2093 System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener);
2096 ge.selectionProvider.addPostSelectionChangedListener(listener);
2100 public void removePostSelectionChangedListener(ISelectionChangedListener listener) {
2101 if(ge == null) return;
2102 if(ge.isDisposed()) {
2103 if (DEBUG_SELECTION_LISTENERS)
2104 System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener);
2107 ge.selectionProvider.removePostSelectionChangedListener(listener);
2112 public void addSelectionChangedListener(ISelectionChangedListener listener) {
2113 if(ge == null) return;
2114 if (!ge.thread.currentThreadAccess())
2115 throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2116 if (ge.natTable.isDisposed() || ge.selectionProvider == null) {
2117 System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener);
2121 ge.selectionProvider.addSelectionChangedListener(listener);
2126 public ISelection getSelection() {
2127 if(ge == null) return StructuredSelection.EMPTY;
2128 if (!ge.thread.currentThreadAccess())
2129 throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread());
2130 if (ge.natTable.isDisposed() || ge.selectionProvider == null)
2131 return StructuredSelection.EMPTY;
2132 return ge.selectionProvider.getSelection();
2137 static class ModifierValidator implements ICellEditorValidator {
2138 private Modifier modifier;
2139 public ModifierValidator(Modifier modifier) {
2140 this.modifier = modifier;
2144 public String isValid(Object value) {
2145 return modifier.isValid((String)value);
2149 static class UpdateRunner implements Runnable {
2151 final NatTableGraphExplorer ge;
2153 UpdateRunner(NatTableGraphExplorer ge, IGraphExplorerContext geContext) {
2160 } catch (Throwable t) {
2161 t.printStackTrace();
2165 public void doRun() {
2167 if (ge.isDisposed())
2170 HashSet<UpdateItem> items;
2172 ScrollBar verticalBar = ge.natTable.getVerticalBar();
2175 synchronized (ge.pendingItems) {
2176 items = ge.pendingItems;
2177 ge.pendingItems = new HashSet<UpdateItem>();
2179 if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size());
2181 //ge.natTable.setRedraw(false);
2182 for (UpdateItem item : items) {
2183 item.update(ge.natTable);
2186 // check if vertical scroll bar has become visible and refresh layout.
2187 boolean currentlyVerticalBarVisible = verticalBar.isVisible();
2188 if (ge.verticalBarVisible != currentlyVerticalBarVisible) {
2189 ge.verticalBarVisible = currentlyVerticalBarVisible;
2190 ge.natTable.getParent().layout();
2193 //ge.natTable.setRedraw(true);
2195 synchronized (ge.pendingItems) {
2196 if (!ge.scheduleUpdater()) {
2197 ge.updating = false;
2209 private static void printDebug(NatTableGraphExplorer ge) {
2210 ge.printTree(ge.rootNode, 0);
2211 System.out.println("Expanded");
2212 for (TreeNode n : ge.treeLayer.expanded)
2213 System.out.println(n);
2214 System.out.println("Expanded end");
2215 System.out.println("Hidden ");
2216 for (int i : ge.treeLayer.getHiddenRowIndexes()) {
2217 System.out.print(i + " ");
2219 System.out.println();
2220 // Display.getCurrent().timerExec(1000, new Runnable() {
2223 // public void run() {
2224 // System.out.println("Hidden delayed ");
2225 // for (int i : ge.treeLayer.getHiddenRowIndexes()) {
2226 // System.out.print(i + " ");
2228 // System.out.println();
2234 public static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {
2235 // This is for query debugging only.
2237 private NatTableGraphExplorer ge;
2238 int queryIndent = 0;
2240 GECache2 cache = new GECache2();
2241 AtomicBoolean propagating = new AtomicBoolean(false);
2242 Object propagateList = new Object();
2243 Object propagate = new Object();
2244 List<Runnable> scheduleList = new ArrayList<Runnable>();
2245 final Deque<Integer> activity = new LinkedList<Integer>();
2246 int activityInt = 0;
2248 AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();
2251 * Keeps track of nodes that have already been auto-expanded. After
2252 * being inserted into this set, nodes will not be forced to stay in an
2253 * expanded state after that. This makes it possible for the user to
2254 * close auto-expanded nodes.
2256 Map<NodeContext, Boolean> autoExpanded = new WeakHashMap<NodeContext, Boolean>();
2258 public GeViewerContext(NatTableGraphExplorer ge) {
2262 public MapList<NodeContext,TreeNode> getContextToNodeMap() {
2265 return ge.contextToNodeMap;
2268 public NatTableGraphExplorer getGe() {
2273 protected void doDispose() {
2275 autoExpanded.clear();
2279 public IGECache getCache() {
2284 public int queryIndent() {
2289 public int queryIndent(int offset) {
2290 queryIndent += offset;
2295 @SuppressWarnings("unchecked")
2296 public <T> NodeQueryProcessor<T> getProcessor(Object o) {
2299 return ge.processors.get(o);
2303 @SuppressWarnings("unchecked")
2304 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {
2305 return ge.primitiveProcessors.get(o);
2308 @SuppressWarnings("unchecked")
2310 public <T> DataSource<T> getDataSource(Class<T> clazz) {
2311 return ge.dataSources.get(clazz);
2315 public void update(UIElementReference ref) {
2316 if (ref instanceof ViewerCellReference) {
2317 ViewerCellReference tiref = (ViewerCellReference) ref;
2318 Object element = tiref.getElement();
2319 int columnIndex = tiref.getColumn();
2320 // NOTE: must be called regardless of the the item value.
2321 // A null item is currently used to indicate a tree root update.
2322 ge.update((TreeNode)element,columnIndex);
2323 } else if (ref instanceof ViewerRowReference) {
2324 ViewerRowReference rref = (ViewerRowReference)ref;
2325 Object element = rref.getElement();
2326 ge.update((TreeNode)element);
2328 throw new IllegalArgumentException("Ui Reference is unknkown " + ref);
2333 public Object getPropagateLock() {
2338 public Object getPropagateListLock() {
2339 return propagateList;
2343 public boolean isPropagating() {
2344 return propagating.get();
2348 public void setPropagating(boolean b) {
2349 this.propagating.set(b);
2353 public List<Runnable> getScheduleList() {
2354 return scheduleList;
2358 public void setScheduleList(List<Runnable> list) {
2359 this.scheduleList = list;
2363 public Deque<Integer> getActivity() {
2368 public void setActivityInt(int i) {
2369 this.activityInt = i;
2373 public int getActivityInt() {
2378 public void scheduleQueryUpdate(Runnable r) {
2381 if (ge.isDisposed())
2383 if (currentQueryUpdater.compareAndSet(null, r)) {
2384 ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER);
2388 Runnable QUERY_UPDATE_SCHEDULER = new Runnable() {
2391 Runnable r = currentQueryUpdater.getAndSet(null);
2399 public void dispose() {
2401 cache = new DummyCache();
2402 scheduleList.clear();
2403 autoExpanded.clear();
2404 autoExpanded = null;
2410 private class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor<Boolean> implements
2411 IsExpandedProcessor, ProcessorLifecycle {
2413 * The set of currently expanded node contexts.
2415 private final HashSet<NodeContext> expanded = new HashSet<NodeContext>();
2416 private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();
2418 private NatTable natTable;
2419 private List<TreeNode> list;
2421 public TreeNodeIsExpandedProcessor() {
2425 public Object getIdentifier() {
2426 return BuiltinKeys.IS_EXPANDED;
2430 public String toString() {
2431 return "IsExpandedProcessor";
2435 public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Boolean> key) {
2436 boolean isExpanded = expanded.contains(context);
2437 expandedQueries.put(context, updater);
2438 return Boolean.valueOf(isExpanded);
2442 public Collection<NodeContext> getExpanded() {
2443 return new HashSet<NodeContext>(expanded);
2447 public boolean getExpanded(NodeContext context) {
2448 return this.expanded.contains(context);
2452 public boolean setExpanded(NodeContext context, boolean expanded) {
2453 return _setExpanded(context, expanded);
2457 public boolean replaceExpanded(NodeContext context, boolean expanded) {
2458 return nodeStatusChanged(context, expanded);
2461 private boolean _setExpanded(NodeContext context, boolean expanded) {
2463 return this.expanded.add(context);
2465 return this.expanded.remove(context);
2469 ILayerListener treeListener = new ILayerListener() {
2472 public void handleLayerEvent(ILayerEvent event) {
2473 if (event instanceof ShowRowPositionsEvent) {
2474 ShowRowPositionsEvent e = (ShowRowPositionsEvent)event;
2475 for (Range r : e.getRowPositionRanges()) {
2476 int expanded = viewportLayer.getRowIndexByPosition(r.start-2)+1;
2477 if (DEBUG)System.out.println("IsExpandedProcessor expand " + expanded);
2478 if (expanded < 0 || expanded >= list.size()) {
2481 nodeStatusChanged(list.get(expanded).getContext(), true);
2483 } else if (event instanceof HideRowPositionsEvent) {
2484 HideRowPositionsEvent e = (HideRowPositionsEvent)event;
2485 for (Range r : e.getRowPositionRanges()) {
2486 int collapsed = viewportLayer.getRowIndexByPosition(r.start-2);
2487 if (DEBUG)System.out.println("IsExpandedProcessor collapse " + collapsed);
2488 if (collapsed < 0 || collapsed >= list.size()) {
2491 nodeStatusChanged(list.get(collapsed).getContext(), false);
2498 protected boolean nodeStatusChanged(NodeContext context, boolean expanded) {
2499 boolean result = _setExpanded(context, expanded);
2500 PrimitiveQueryUpdater updater = expandedQueries.get(context);
2501 if (updater != null)
2502 updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded);
2507 public void attached(GraphExplorer explorer) {
2508 Object control = explorer.getControl();
2509 if (control instanceof NatTable) {
2510 this.natTable = (NatTable) control;
2511 this.list = ((NatTableGraphExplorer)explorer).list;
2512 natTable.addLayerListener(treeListener);
2515 System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control);
2520 public void clear() {
2522 expandedQueries.clear();
2526 public void detached(GraphExplorer explorer) {
2528 if (natTable != null) {
2529 natTable.removeLayerListener(treeListener);
2530 // natTable.removeListener(SWT.Expand, treeListener);
2531 // natTable.removeListener(SWT.Collapse, treeListener);
2537 private void printTree(TreeNode node, int depth) {
2539 for (int i = 0; i < depth; i++) {
2543 System.out.println(s);
2545 for (TreeNode n : node.getChildren()) {
2552 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used)
2554 final private static class GECacheKey {
2556 private NodeContext context;
2557 private CacheKey<?> key;
2559 GECacheKey(NodeContext context, CacheKey<?> key) {
2560 this.context = context;
2562 if (context == null || key == null)
2563 throw new IllegalArgumentException("Null context or key is not accepted");
2566 GECacheKey(GECacheKey other) {
2567 this.context = other.context;
2568 this.key = other.key;
2569 if (context == null || key == null)
2570 throw new IllegalArgumentException("Null context or key is not accepted");
2573 void setValues(NodeContext context, CacheKey<?> key) {
2574 this.context = context;
2576 if (context == null || key == null)
2577 throw new IllegalArgumentException("Null context or key is not accepted");
2581 public int hashCode() {
2582 return context.hashCode() | key.hashCode();
2586 public boolean equals(Object object) {
2590 else if (object == null)
2593 GECacheKey i = (GECacheKey) object;
2595 return key.equals(i.key) && context.equals(i.context);
2602 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data.
2604 public static class GECache2 implements IGECache {
2606 final HashMap<GECacheKey, IGECacheEntry> entries = new HashMap<GECacheKey, IGECacheEntry>();
2607 final HashMap<GECacheKey, Set<UIElementReference>> treeReferences = new HashMap<GECacheKey, Set<UIElementReference>>();
2608 final HashMap<NodeContext, Set<GECacheKey>> keyRefs = new HashMap<NodeContext, Set<GECacheKey>>();
2611 * This single instance is used for all get operations from the cache. This
2612 * should work since the GE cache is meant to be single-threaded within the
2613 * current UI thread, what ever that thread is. For put operations which
2614 * store the key, this is not used.
2616 NodeContext getNC = new NodeContext() {
2617 @SuppressWarnings("rawtypes")
2619 public Object getAdapter(Class adapter) {
2624 public <T> T getConstant(ConstantKey<T> key) {
2629 public Set<ConstantKey<?>> getKeys() {
2630 return Collections.emptySet();
2633 CacheKey<?> getCK = new CacheKey<Object>() {
2635 public Object processorIdenfitier() {
2639 GECacheKey getKey = new GECacheKey(getNC, getCK);
2642 private void addKey(GECacheKey key) {
2643 Set<GECacheKey> refs = keyRefs.get(key.context);
2647 refs = new HashSet<GECacheKey>();
2649 keyRefs.put(key.context, refs);
2653 private void removeKey(GECacheKey key) {
2654 Set<GECacheKey> refs = keyRefs.get(key.context);
2660 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key, T value) {
2661 // if (DEBUG) System.out.println("Add entry " + context + " " + key);
2662 IGECacheEntry entry = new GECacheEntry(context, key, value);
2663 GECacheKey gekey = new GECacheKey(context, key);
2664 entries.put(gekey, entry);
2669 @SuppressWarnings("unchecked")
2670 public <T> T get(NodeContext context, CacheKey<T> key) {
2671 getKey.setValues(context, key);
2672 IGECacheEntry entry = entries.get(getKey);
2675 return (T) entry.getValue();
2679 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
2680 assert(context != null);
2681 assert(key != null);
2682 getKey.setValues(context, key);
2683 return entries.get(getKey);
2687 public <T> void remove(NodeContext context, CacheKey<T> key) {
2688 // if (DEBUG) System.out.println("Remove entry " + context + " " + key);
2689 getKey.setValues(context, key);
2690 entries.remove(getKey);
2695 public <T> Set<UIElementReference> getTreeReference(NodeContext context, CacheKey<T> key) {
2696 assert(context != null);
2697 assert(key != null);
2698 getKey.setValues(context, key);
2699 return treeReferences.get(getKey);
2703 public <T> void putTreeReference(NodeContext context, CacheKey<T> key, UIElementReference reference) {
2704 assert(context != null);
2705 assert(key != null);
2706 //if (DEBUG) System.out.println("Add tree reference " + context + " " + key);
2707 getKey.setValues(context, key);
2708 Set<UIElementReference> refs = treeReferences.get(getKey);
2710 refs.add(reference);
2712 refs = new HashSet<UIElementReference>(4);
2713 refs.add(reference);
2714 GECacheKey gekey = new GECacheKey(getKey);
2715 treeReferences.put(gekey, refs);
2721 public <T> Set<UIElementReference> removeTreeReference(NodeContext context, CacheKey<T> key) {
2722 assert(context != null);
2723 assert(key != null);
2724 //if (DEBUG) System.out.println("Remove tree reference " + context + " " + key);
2725 getKey.setValues(context, key);
2727 return treeReferences.remove(getKey);
2731 public boolean isShown(NodeContext context) {
2732 return references.get(context) > 0;
2735 private TObjectIntHashMap<NodeContext> references = new TObjectIntHashMap<NodeContext>();
2738 public void incRef(NodeContext context) {
2739 int exist = references.get(context);
2740 references.put(context, exist+1);
2744 public void decRef(NodeContext context) {
2745 int exist = references.get(context);
2746 references.put(context, exist-1);
2748 references.remove(context);
2752 public void dispose() {
2755 treeReferences.clear();
2759 public void dispose(NodeContext context) {
2760 Set<GECacheKey> keys = keyRefs.remove(context);
2762 for (GECacheKey key : keys) {
2763 entries.remove(key);
2764 treeReferences.remove(key);
2772 * Non-functional cache to replace actual cache when GEContext is disposed.
2777 private static class DummyCache extends GECache2 {
2780 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
2785 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key,
2791 public <T> void putTreeReference(NodeContext context, CacheKey<T> key,
2792 UIElementReference reference) {
2796 public <T> T get(NodeContext context, CacheKey<T> key) {
2801 public <T> Set<UIElementReference> getTreeReference(
2802 NodeContext context, CacheKey<T> key) {
2807 public <T> void remove(NodeContext context, CacheKey<T> key) {
2812 public <T> Set<UIElementReference> removeTreeReference(
2813 NodeContext context, CacheKey<T> key) {
2818 public boolean isShown(NodeContext context) {
2823 public void incRef(NodeContext context) {
2828 public void decRef(NodeContext context) {
2833 public void dispose() {
2838 private class NatTableHeaderMenuConfiguration extends AbstractHeaderMenuConfiguration {
2841 public NatTableHeaderMenuConfiguration(NatTable natTable) {
2846 protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) {
2847 return super.createColumnHeaderMenu(natTable)
2848 .withHideColumnMenuItem()
2849 .withShowAllColumnsMenuItem()
2850 .withAutoResizeSelectedColumnsMenuItem();
2854 protected PopupMenuBuilder createCornerMenu(NatTable natTable) {
2855 return super.createCornerMenu(natTable)
2856 .withShowAllColumnsMenuItem();
2859 protected PopupMenuBuilder createRowHeaderMenu(NatTable natTable) {
2860 return super.createRowHeaderMenu(natTable);
2864 private static class RelativeAlternatingRowConfigLabelAccumulator extends AlternatingRowConfigLabelAccumulator {
2867 public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) {
2868 configLabels.addLabel((rowPosition % 2 == 0 ? EVEN_ROW_CONFIG_TYPE : ODD_ROW_CONFIG_TYPE));
2873 public Object getClicked(Object event) {
2874 MouseEvent e = (MouseEvent)event;
2875 final NatTable tree = (NatTable) e.getSource();
2876 Point point = new Point(e.x, e.y);
2877 int y = natTable.getRowPositionByY(point.y);
2878 int x = natTable.getColumnPositionByX(point.x);
2881 return list.get(y-1);