1 package org.simantics.browsing.ui.nattable;
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.Collection;
6 import java.util.Collections;
7 import java.util.Deque;
8 import java.util.HashMap;
9 import java.util.HashSet;
10 import java.util.LinkedList;
11 import java.util.List;
14 import java.util.WeakHashMap;
15 import java.util.concurrent.CopyOnWriteArrayList;
16 import java.util.concurrent.ExecutorService;
17 import java.util.concurrent.ScheduledExecutorService;
18 import java.util.concurrent.Semaphore;
19 import java.util.concurrent.TimeUnit;
20 import java.util.concurrent.atomic.AtomicBoolean;
21 import java.util.concurrent.atomic.AtomicReference;
22 import java.util.function.BiFunction;
23 import java.util.function.Consumer;
25 import org.eclipse.core.runtime.Assert;
26 import org.eclipse.core.runtime.IProgressMonitor;
27 import org.eclipse.core.runtime.IStatus;
28 import org.eclipse.core.runtime.MultiStatus;
29 import org.eclipse.core.runtime.Platform;
30 import org.eclipse.core.runtime.Status;
31 import org.eclipse.core.runtime.jobs.Job;
32 import org.eclipse.jface.resource.ColorDescriptor;
33 import org.eclipse.jface.resource.DeviceResourceException;
34 import org.eclipse.jface.resource.DeviceResourceManager;
35 import org.eclipse.jface.resource.FontDescriptor;
36 import org.eclipse.jface.resource.ImageDescriptor;
37 import org.eclipse.jface.resource.JFaceResources;
38 import org.eclipse.jface.resource.LocalResourceManager;
39 import org.eclipse.jface.viewers.ColumnWeightData;
40 import org.eclipse.jface.viewers.ICellEditorValidator;
41 import org.eclipse.jface.viewers.IPostSelectionProvider;
42 import org.eclipse.jface.viewers.ISelection;
43 import org.eclipse.jface.viewers.ISelectionChangedListener;
44 import org.eclipse.jface.viewers.ISelectionProvider;
45 import org.eclipse.jface.viewers.SelectionChangedEvent;
46 import org.eclipse.jface.viewers.StructuredSelection;
47 import org.eclipse.jface.window.Window;
48 import org.eclipse.nebula.widgets.nattable.NatTable;
49 import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;
50 import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
51 import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
52 import org.eclipse.nebula.widgets.nattable.config.IEditableRule;
53 import org.eclipse.nebula.widgets.nattable.coordinate.Range;
54 import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
55 import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
56 import org.eclipse.nebula.widgets.nattable.data.convert.DefaultDisplayConverter;
57 import org.eclipse.nebula.widgets.nattable.data.validate.IDataValidator;
58 import org.eclipse.nebula.widgets.nattable.data.validate.ValidationFailedException;
59 import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;
60 import org.eclipse.nebula.widgets.nattable.edit.EditConfigHelper;
61 import org.eclipse.nebula.widgets.nattable.edit.ICellEditHandler;
62 import org.eclipse.nebula.widgets.nattable.edit.config.DefaultEditConfiguration;
63 import org.eclipse.nebula.widgets.nattable.edit.config.DialogErrorHandling;
64 import org.eclipse.nebula.widgets.nattable.edit.editor.AbstractCellEditor;
65 import org.eclipse.nebula.widgets.nattable.edit.editor.ComboBoxCellEditor;
66 import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor;
67 import org.eclipse.nebula.widgets.nattable.edit.editor.IEditErrorHandler;
68 import org.eclipse.nebula.widgets.nattable.edit.editor.TextCellEditor;
69 import org.eclipse.nebula.widgets.nattable.edit.gui.AbstractDialogCellEditor;
70 import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
71 import org.eclipse.nebula.widgets.nattable.grid.cell.AlternatingRowConfigLabelAccumulator;
72 import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
73 import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;
74 import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
75 import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
76 import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultColumnHeaderDataLayer;
77 import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;
78 import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
79 import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
80 import org.eclipse.nebula.widgets.nattable.hideshow.ColumnHideShowLayer;
81 import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent;
82 import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent;
83 import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
84 import org.eclipse.nebula.widgets.nattable.layer.ILayerListener;
85 import org.eclipse.nebula.widgets.nattable.layer.LabelStack;
86 import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator;
87 import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
88 import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;
89 import org.eclipse.nebula.widgets.nattable.painter.NatTableBorderOverlayPainter;
90 import org.eclipse.nebula.widgets.nattable.reorder.ColumnReorderLayer;
91 import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
92 import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum;
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.MapList;
194 import org.simantics.utils.datastructures.disposable.AbstractDisposable;
195 import org.simantics.utils.datastructures.hints.IHintContext;
196 import org.simantics.utils.threads.IThreadWorkQueue;
197 import org.simantics.utils.threads.SWTThread;
198 import org.simantics.utils.threads.ThreadUtils;
199 import org.simantics.utils.ui.AdaptionUtils;
200 import org.simantics.utils.ui.ISelectionUtils;
201 import org.simantics.utils.ui.jface.BasePostSelectionProvider;
203 import gnu.trove.map.hash.THashMap;
204 import gnu.trove.map.hash.TObjectIntHashMap;
207 * NatTable based GraphExplorer
209 * This GraphExplorer is not fully compatible with the other implementations, since it is not based on SWT.Tree.
211 * 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.
214 * TODO: ability to hide headers
215 * TODO: code cleanup (copied from GraphExplorerImpl2)
217 * @author Marko Luukkainen <marko.luukkainen@vtt.fi>
220 public class NatTableGraphExplorer extends GraphExplorerImplBase implements GraphExplorer{
221 public static final int DEFAULT_MAX_CHILDREN = 10000;
222 private static final boolean DEBUG_SELECTION_LISTENERS = false;
223 private static final boolean DEBUG = false;
225 private Composite composite;
226 private NatTable natTable;
228 private GETreeLayer treeLayer;
229 private DataLayer dataLayer;
230 private ViewportLayer viewportLayer;
231 private SelectionLayer selectionLayer;
232 private GEColumnHeaderDataProvider columnHeaderDataProvider;
233 private GEColumnAccessor columnAccessor;
234 private DefaultRowHeaderDataLayer rowHeaderDataLayer;
235 private DataLayer columnHeaderDataLayer;
236 private DataLayer cornerDataLayer;
238 private List<TreeNode> list = new ArrayList<>();
240 private NatTableSelectionAdaptor selectionAdaptor;
241 private NatTableColumnLayout layout;
243 LocalResourceManager localResourceManager;
244 DeviceResourceManager resourceManager;
247 private IThreadWorkQueue thread;
249 @SuppressWarnings({ "rawtypes" })
250 final HashMap<CacheKey<?>, NodeQueryProcessor> processors = new HashMap<CacheKey<?>, NodeQueryProcessor>();
251 @SuppressWarnings({ "rawtypes" })
252 final HashMap<Object, PrimitiveQueryProcessor> primitiveProcessors = new HashMap<Object, PrimitiveQueryProcessor>();
253 @SuppressWarnings({ "rawtypes" })
254 final HashMap<Class, DataSource> dataSources = new HashMap<Class, DataSource>();
256 FontDescriptor originalFont;
257 protected ColorDescriptor originalForeground;
258 protected ColorDescriptor originalBackground;
259 private Color invalidModificationColor;
261 private Column[] columns;
262 private Map<String,Integer> columnKeyToIndex;
263 private boolean columnsAreVisible = true;
265 private NodeContext rootContext;
266 private TreeNode rootNode;
267 private StatePersistor persistor = null;
269 private boolean editable = true;
271 private boolean disposed = false;
273 private final CopyOnWriteArrayList<FocusListener> focusListeners = new CopyOnWriteArrayList<FocusListener>();
274 private final CopyOnWriteArrayList<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();
275 private final CopyOnWriteArrayList<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>();
277 private int autoExpandLevel = 0;
278 private IServiceLocator serviceLocator;
279 private IContextService contextService = null;
280 private IFocusService focusService = null;
281 private IContextActivation editingContext = null;
283 GeViewerContext explorerContext = new GeViewerContext(this);
285 private GraphExplorerPostSelectionProvider postSelectionProvider = new GraphExplorerPostSelectionProvider(this);
286 private BasePostSelectionProvider selectionProvider = new BasePostSelectionProvider();
287 private SelectionDataResolver selectionDataResolver;
288 private SelectionFilter selectionFilter;
290 private MapList<NodeContext, TreeNode> contextToNodeMap;
292 private ModificationContext modificationContext = null;
294 private boolean filterSelectionEdit = true;
296 private boolean expand;
297 private boolean verticalBarVisible = false;
299 private BiFunction<GraphExplorer, Object[], Object[]> selectionTransformation = new BiFunction<GraphExplorer, Object[], Object[]>() {
302 public Object[] apply(GraphExplorer explorer, Object[] objects) {
303 Object[] result = new Object[objects.length];
304 for (int i = 0; i < objects.length; i++) {
305 IHintContext context = new AdaptableHintContext(SelectionHints.KEY_MAIN);
306 context.setHint(SelectionHints.KEY_MAIN, objects[i]);
314 static class TransientStateImpl implements TransientExplorerState {
316 private Integer activeColumn = null;
319 public synchronized Integer getActiveColumn() {
323 public synchronized void setActiveColumn(Integer column) {
324 activeColumn = column;
329 private TransientStateImpl transientState = new TransientStateImpl();
331 public NatTableGraphExplorer(Composite parent) {
332 this(parent, SWT.BORDER | SWT.MULTI );
335 public NatTableGraphExplorer(Composite parent, int style) {
336 this.composite = parent;
339 this.localResourceManager = new LocalResourceManager(JFaceResources.getResources());
340 this.resourceManager = new DeviceResourceManager(parent.getDisplay());
342 this.imageLoaderJob = new ImageLoaderJob(this);
343 this.imageLoaderJob.setPriority(Job.DECORATE);
344 contextToNodeMap = new MapList<NodeContext, TreeNode>();
346 invalidModificationColor = (Color) localResourceManager.get(ColorDescriptor.createFrom(new RGB(255, 128, 128)));
348 this.thread = SWTThread.getThreadAccess(parent);
350 for (int i = 0; i < 10; i++)
351 explorerContext.activity.push(0);
353 originalFont = JFaceResources.getDefaultFontDescriptor();
355 columns = new Column[0];
356 createNatTable(style);
357 layout = new NatTableColumnLayout(natTable, columnHeaderDataProvider, rowHeaderDataLayer);
358 this.composite.setLayout(layout);
361 setDefaultProcessors();
363 natTable.addDisposeListener(new DisposeListener() {
366 public void widgetDisposed(DisposeEvent e) {
372 Listener listener = new Listener() {
375 public void handleEvent(Event event) {
377 switch (event.type) {
393 natTable.addListener(SWT.Activate, listener);
394 natTable.addListener(SWT.Deactivate, listener);
395 natTable.addListener(SWT.Show, listener);
396 natTable.addListener(SWT.Hide, listener);
397 natTable.addListener(SWT.Paint,listener);
399 setColumns( new Column[] { new Column(ColumnKeys.SINGLE) });
403 private long focusGainedAt = 0L;
404 private boolean visible = false;
405 private Collection<TreeNode> selectedNodes = new ArrayList<TreeNode>();
407 protected void setBasicListeners() {
409 natTable.addFocusListener(new FocusListener() {
411 public void focusGained(FocusEvent e) {
412 focusGainedAt = ((long) e.time) & 0xFFFFFFFFL;
413 for (FocusListener listener : focusListeners)
414 listener.focusGained(e);
417 public void focusLost(FocusEvent e) {
418 for (FocusListener listener : focusListeners)
419 listener.focusLost(e);
422 natTable.addMouseListener(new MouseListener() {
424 public void mouseDoubleClick(MouseEvent e) {
425 for (MouseListener listener : mouseListeners) {
426 listener.mouseDoubleClick(e);
430 public void mouseDown(MouseEvent e) {
431 for (MouseListener listener : mouseListeners) {
432 listener.mouseDown(e);
436 public void mouseUp(MouseEvent e) {
437 for (MouseListener listener : mouseListeners) {
442 natTable.addKeyListener(new KeyListener() {
444 public void keyPressed(KeyEvent e) {
445 for (KeyListener listener : keyListeners) {
446 listener.keyPressed(e);
450 public void keyReleased(KeyEvent e) {
451 for (KeyListener listener : keyListeners) {
452 listener.keyReleased(e);
457 selectionAdaptor.addSelectionChangedListener(new ISelectionChangedListener() {
460 public void selectionChanged(SelectionChangedEvent event) {
461 //System.out.println("GraphExplorerImpl2.fireSelection");
462 selectedNodes = AdaptionUtils.adaptToCollection(event.getSelection(), TreeNode.class);
463 Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);
464 selectionProvider.setAndFireSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));
468 selectionAdaptor.addPostSelectionChangedListener(new ISelectionChangedListener() {
471 public void selectionChanged(SelectionChangedEvent event) {
472 //System.out.println("GraphExplorerImpl2.firePostSelection");
473 Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);
474 selectionProvider.firePostSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));
481 private NodeContext pendingRoot;
483 private void activate() {
484 if (pendingRoot != null && !expand) {
485 doSetRoot(pendingRoot);
491 * Invoke only from SWT thread to reset the root of the graph explorer tree.
495 private void doSetRoot(NodeContext root) {
496 Display display = composite.getDisplay();
497 if (display.getThread() != Thread.currentThread()) {
498 throw new RuntimeException("Invoke from SWT thread only");
500 // System.out.println("doSetRoot " + root);
503 if (natTable.isDisposed())
505 if (root.getConstant(BuiltinKeys.INPUT) == null) {
506 ErrorLogger.defaultLogError("root node context does not contain BuiltinKeys.INPUT key. Node = " + root, new Exception("trace"));
512 // Empty caches, release queries.
513 if (rootNode != null) {
516 GeViewerContext oldContext = explorerContext;
517 GeViewerContext newContext = new GeViewerContext(this);
518 this.explorerContext = newContext;
519 oldContext.safeDispose();
521 // Need to empty these or otherwise they won't be emptied until the
522 // explorer is disposed which would mean that many unwanted references
523 // will be held by this map.
524 clearPrimitiveProcessors();
526 this.rootContext = root.getConstant(BuiltinKeys.IS_ROOT) != null ? root
527 : NodeContextUtil.withConstant(root, BuiltinKeys.IS_ROOT, Boolean.TRUE);
529 explorerContext.getCache().incRef(this.rootContext);
535 //refreshColumnSizes();
536 rootNode = new TreeNode(rootContext, explorerContext);
537 if (DEBUG) System.out.println("setRoot " + rootNode);
539 // Delay content reading.
541 // This is required for cases when GEImpl2 is attached to selection view. Reading content
542 // instantly could stagnate SWT thread under rapid changes in selection. By delaying the
543 // content reading we give the system a change to dispose the GEImpl2 before the content is read.
544 display.asyncExec(new Runnable() {
548 if (rootNode != null) {
549 rootNode.updateChildren();
550 rootNode.setExpanded(true);
558 private synchronized void listReIndex() {
559 for (TreeNode n : list) {
563 for (TreeNode c : rootNode.getChildren())
568 private void _insertToList(TreeNode n) {
569 n.setListIndex(list.size());
571 for (TreeNode c : n.getChildren()) {
576 public List<TreeNode> getItems() {
577 return Collections.unmodifiableList(list);
580 private void initializeState() {
581 if (persistor == null)
584 ExplorerState state = persistor.deserialize(
585 Platform.getStateLocation(Activator.getDefault().getBundle()).toFile(),
589 Object processor = getPrimitiveProcessor(BuiltinKeys.IS_EXPANDED);
590 if (processor instanceof DefaultIsExpandedProcessor) {
591 DefaultIsExpandedProcessor isExpandedProcessor = (DefaultIsExpandedProcessor)processor;
592 for(NodeContext expanded : state.expandedNodes) {
593 isExpandedProcessor.setExpanded(expanded, true);
599 public NodeContext getRoot() {
604 public IThreadWorkQueue getThread() {
609 public NodeContext getParentContext(NodeContext context) {
611 throw new IllegalStateException("disposed");
612 if (!thread.currentThreadAccess())
613 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
615 List<TreeNode> nodes = contextToNodeMap.getValuesUnsafe(context);
616 for (int i = 0; i < nodes.size(); i++) {
617 if (nodes.get(i).getParent() != null)
618 return nodes.get(i).getParent().getContext();
625 @SuppressWarnings("unchecked")
627 public <T> T getAdapter(Class<T> adapter) {
628 if(ISelectionProvider.class == adapter) return (T) postSelectionProvider;
629 else if(IPostSelectionProvider.class == adapter) return (T) postSelectionProvider;
634 protected void setDefaultProcessors() {
635 // Add a simple IMAGER query processor that always returns null.
636 // With this processor no images will ever be shown.
637 // setPrimitiveProcessor(new StaticImagerProcessor(null));
639 setProcessor(new DefaultComparableChildrenProcessor());
640 setProcessor(new DefaultLabelDecoratorsProcessor());
641 setProcessor(new DefaultImageDecoratorsProcessor());
642 setProcessor(new DefaultSelectedLabelerProcessor());
643 setProcessor(new DefaultLabelerFactoriesProcessor());
644 setProcessor(new DefaultSelectedImagerProcessor());
645 setProcessor(new DefaultImagerFactoriesProcessor());
646 setPrimitiveProcessor(new DefaultLabelerProcessor());
647 setPrimitiveProcessor(new DefaultCheckedStateProcessor());
648 setPrimitiveProcessor(new DefaultImagerProcessor());
649 setPrimitiveProcessor(new DefaultLabelDecoratorProcessor());
650 setPrimitiveProcessor(new DefaultImageDecoratorProcessor());
651 setPrimitiveProcessor(new NoSelectionRequestProcessor());
653 setProcessor(new DefaultFinalChildrenProcessor(this));
655 setProcessor(new DefaultPrunedChildrenProcessor());
656 setProcessor(new DefaultSelectedViewpointProcessor());
657 setProcessor(new DefaultSelectedLabelDecoratorFactoriesProcessor());
658 setProcessor(new DefaultSelectedImageDecoratorFactoriesProcessor());
659 setProcessor(new DefaultViewpointContributionsProcessor());
661 setPrimitiveProcessor(new DefaultViewpointProcessor());
662 setPrimitiveProcessor(new DefaultViewpointContributionProcessor());
663 setPrimitiveProcessor(new DefaultSelectedViewpointFactoryProcessor());
664 setPrimitiveProcessor(new TreeNodeIsExpandedProcessor());
665 setPrimitiveProcessor(new DefaultShowMaxChildrenProcessor());
669 public Column[] getColumns() {
670 return Arrays.copyOf(columns, columns.length);
674 public void setColumnsVisible(boolean visible) {
675 columnsAreVisible = visible;
676 //FIXME if(natTable != null) this.columnHeaderDataLayer.setHeaderVisible(columnsAreVisible);
680 public void setColumns(final Column[] columns) {
681 setColumns(columns, null);
685 public void setColumns(final Column[] columns, Consumer<Map<Column, Object>> callback) {
687 checkUniqueColumnKeys(columns);
689 Display d = composite.getDisplay();
690 if (d.getThread() == Thread.currentThread()) {
691 doSetColumns(columns, callback);
692 natTable.refresh(true);
694 d.asyncExec(new Runnable() {
697 if (natTable == null)
699 if (natTable.isDisposed())
701 doSetColumns(columns, callback);
703 natTable.getParent().layout();
708 private void checkUniqueColumnKeys(Column[] cols) {
709 Set<String> usedColumnKeys = new HashSet<String>();
710 List<Column> duplicateColumns = new ArrayList<Column>();
711 for (Column c : cols) {
712 if (!usedColumnKeys.add(c.getKey()))
713 duplicateColumns.add(c);
715 if (!duplicateColumns.isEmpty()) {
716 throw new IllegalArgumentException("All columns do not have unique keys: " + cols + ", overlapping: " + duplicateColumns);
720 private void doSetColumns(Column[] cols, Consumer<Map<Column, Object>> callback) {
722 HashMap<String, Integer> keyToIndex = new HashMap<String, Integer>();
723 for (int i = 0; i < cols.length; ++i) {
724 keyToIndex.put(cols[i].getKey(), i);
727 this.columns = Arrays.copyOf(cols, cols.length);
728 //this.columns[cols.length] = FILLER_COLUMN;
729 this.columnKeyToIndex = keyToIndex;
731 columnHeaderDataProvider.updateColumnSizes();
733 Map<Column, Object> map = new HashMap<Column, Object>();
735 // FIXME : temporary workaround for ModelBrowser.
736 // natTable.setHeaderVisible(columns.length == 1 ? false : columnsAreVisible);
740 for (Column column : columns) {
741 int width = column.getWidth();
742 if(column.hasGrab()) {
745 layout.setColumnData(columnIndex, new ColumnWeightData(column.getWeight(), width));
750 layout.setColumnData(columnIndex, new ColumnWeightData(columns.length > 1 ? 0 : 1, width));
758 if(callback != null) callback.accept(map);
761 int toSWT(Align alignment) {
763 case LEFT: return SWT.LEFT;
764 case CENTER: return SWT.CENTER;
765 case RIGHT: return SWT.RIGHT;
766 default: throw new Error("unhandled alignment: " + alignment);
771 public <T> void setProcessor(NodeQueryProcessor<T> processor) {
773 if (processor == null)
774 throw new IllegalArgumentException("null processor");
776 processors.put(processor.getIdentifier(), processor);
780 public <T> void setPrimitiveProcessor(PrimitiveQueryProcessor<T> processor) {
782 if (processor == null)
783 throw new IllegalArgumentException("null processor");
785 PrimitiveQueryProcessor<?> oldProcessor = primitiveProcessors.put(
786 processor.getIdentifier(), processor);
788 if (oldProcessor instanceof ProcessorLifecycle)
789 ((ProcessorLifecycle) oldProcessor).detached(this);
790 if (processor instanceof ProcessorLifecycle)
791 ((ProcessorLifecycle) processor).attached(this);
795 public <T> void setDataSource(DataSource<T> provider) {
797 if (provider == null)
798 throw new IllegalArgumentException("null provider");
799 dataSources.put(provider.getProvidedClass(), provider);
802 @SuppressWarnings("unchecked")
804 public <T> DataSource<T> removeDataSource(Class<T> forProvidedClass) {
806 if (forProvidedClass == null)
807 throw new IllegalArgumentException("null class");
808 return dataSources.remove(forProvidedClass);
812 public void setPersistor(StatePersistor persistor) {
813 this.persistor = persistor;
817 public SelectionDataResolver getSelectionDataResolver() {
818 return selectionDataResolver;
822 public void setSelectionDataResolver(SelectionDataResolver r) {
823 this.selectionDataResolver = r;
827 public SelectionFilter getSelectionFilter() {
828 return selectionFilter;
832 public void setSelectionFilter(SelectionFilter f) {
833 this.selectionFilter = f;
834 // TODO: re-filter current selection?
837 protected ISelection constructSelection(NodeContext... contexts) {
838 if (contexts == null)
839 throw new IllegalArgumentException("null contexts");
840 if (contexts.length == 0)
841 return StructuredSelection.EMPTY;
842 if (selectionFilter == null)
843 return new StructuredSelection(transformSelection(contexts));
844 return new StructuredSelection( transformSelection(filter(selectionFilter, contexts)) );
847 protected Object[] transformSelection(Object[] objects) {
848 return selectionTransformation.apply(this, objects);
851 protected static Object[] filter(SelectionFilter filter, NodeContext[] contexts) {
852 int len = contexts.length;
853 Object[] objects = new Object[len];
854 for (int i = 0; i < len; ++i)
855 objects[i] = filter.filter(contexts[i]);
860 public void setSelectionTransformation(
861 BiFunction<GraphExplorer, Object[], Object[]> f) {
862 this.selectionTransformation = f;
865 public ISelection getWidgetSelection() {
866 return selectionAdaptor.getSelection();
870 public <T> void addListener(T listener) {
871 if (listener instanceof FocusListener) {
872 focusListeners.add((FocusListener) listener);
873 } else if (listener instanceof MouseListener) {
874 mouseListeners.add((MouseListener) listener);
875 } else if (listener instanceof KeyListener) {
876 keyListeners.add((KeyListener) listener);
881 public <T> void removeListener(T listener) {
882 if (listener instanceof FocusListener) {
883 focusListeners.remove(listener);
884 } else if (listener instanceof MouseListener) {
885 mouseListeners.remove(listener);
886 } else if (listener instanceof KeyListener) {
887 keyListeners.remove(listener);
891 public void addSelectionListener(SelectionListener listener) {
892 selectionAdaptor.addSelectionListener(listener);
895 public void removeSelectionListener(SelectionListener listener) {
896 selectionAdaptor.removeSelectionListener(listener);
899 private Set<String> uiContexts;
902 public void setUIContexts(Set<String> contexts) {
903 this.uiContexts = contexts;
907 public void setRoot(final Object root) {
908 if(uiContexts != null && uiContexts.size() == 1)
909 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root, BuiltinKeys.UI_CONTEXT, uiContexts.iterator().next()));
911 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root));
915 public void setRootContext(final NodeContext context) {
916 setRootContext0(context);
919 private void setRoot(NodeContext context) {
921 pendingRoot = context;
922 Display.getDefault().asyncExec(new Runnable() {
925 if (natTable!= null && !natTable.isDisposed())
934 private void setRootContext0(final NodeContext context) {
935 Assert.isNotNull(context, "root must not be null");
936 if (isDisposed() || natTable.isDisposed())
938 Display display = natTable.getDisplay();
939 if (display.getThread() == Thread.currentThread()) {
942 display.asyncExec(new Runnable() {
952 public void setFocus() {
956 @SuppressWarnings("unchecked")
958 public <T> T getControl() {
964 public boolean isDisposed() {
968 protected void assertNotDisposed() {
970 throw new IllegalStateException("disposed");
974 public boolean isEditable() {
979 public void setEditable(boolean editable) {
980 if (!thread.currentThreadAccess())
981 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
983 this.editable = editable;
984 Display display = natTable.getDisplay();
985 natTable.setBackground(editable ? null : display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
988 private void doDispose() {
992 // TODO: Since GENodeQueryManager is cached in QueryChache and it refers to this class
993 // we have to remove all references here to reduce memory consumption.
995 // Proper fix would be to remove references between QueryCache and GENodeQueryManagers.
996 if (rootNode != null) {
1000 explorerContext.dispose();
1001 explorerContext = null;
1003 detachPrimitiveProcessors();
1004 primitiveProcessors.clear();
1005 dataSources.clear();
1006 pendingItems.clear();
1008 mouseListeners.clear();
1009 selectionProvider.clearListeners();
1010 selectionProvider = null;
1011 selectionDataResolver = null;
1012 selectedNodes.clear();
1013 selectedNodes = null;
1014 selectionTransformation = null;
1015 originalFont = null;
1016 localResourceManager.dispose();
1017 localResourceManager = null;
1018 // Must shutdown image loader job before disposing its ResourceManager
1019 imageLoaderJob.dispose();
1020 imageLoaderJob.cancel();
1022 imageLoaderJob.join();
1023 imageLoaderJob = null;
1024 } catch (InterruptedException e) {
1025 ErrorLogger.defaultLogError(e);
1027 resourceManager.dispose();
1028 resourceManager = null;
1030 contextToNodeMap.clear(); // should be empty at this point.
1031 contextToNodeMap = null;
1032 if (postSelectionProvider != null) {
1033 postSelectionProvider.dispose();
1034 postSelectionProvider = null;
1037 modificationContext = null;
1038 focusService = null;
1039 contextService = null;
1040 serviceLocator = null;
1042 columnKeyToIndex.clear();
1043 columnKeyToIndex = null;
1044 // if (natTable != null) {
1045 // natTable.dispose();
1050 viewportLayer = null;
1051 selectionLayer = null;
1052 columnHeaderDataProvider = null;
1053 columnAccessor = null;
1054 rowHeaderDataLayer = null;
1055 columnHeaderDataLayer = null;
1056 cornerDataLayer = null;
1061 public boolean select(NodeContext context) {
1063 assertNotDisposed();
1065 if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) {
1066 StructuredSelection s = new StructuredSelection();
1067 selectionAdaptor.setSelection(s);
1068 selectionProvider.setAndFireNonEqualSelection(s);
1072 selectionAdaptor.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0)));
1078 public boolean select(TreeNode node) {
1079 assertNotDisposed();
1081 if (!list.contains(node)) {
1082 StructuredSelection s = new StructuredSelection();
1083 selectionAdaptor.setSelection(s);
1084 selectionProvider.setAndFireNonEqualSelection(s);
1087 selectionAdaptor.setSelection(new StructuredSelection(node));
1091 public void show(TreeNode node) {
1092 int index = node.getListIndex();
1094 int position = treeLayer.getRowPositionByIndex(index);
1096 treeLayer.expandToTreeRow(index);
1097 position = treeLayer.getRowPositionByIndex(index);
1099 viewportLayer.moveRowPositionIntoViewport(position);
1103 public boolean selectPath(Collection<NodeContext> contexts) {
1105 if(contexts == null) throw new IllegalArgumentException("Null list is not allowed");
1106 if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed");
1108 return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0);
1112 private boolean selectPathInternal(NodeContext[] contexts, int position) {
1114 NodeContext head = contexts[position];
1116 if(position == contexts.length-1) {
1117 return select(head);
1121 setExpanded(head, true);
1122 if(!waitVisible(contexts[position+1])) return false;
1124 return selectPathInternal(contexts, position+1);
1128 private boolean waitVisible(NodeContext context) {
1129 long start = System.nanoTime();
1130 while(!isVisible(context)) {
1131 Display.getCurrent().readAndDispatch();
1132 long duration = System.nanoTime() - start;
1133 if(duration > 10e9) return false;
1139 public boolean isVisible(NodeContext context) {
1140 if (contextToNodeMap.getValuesUnsafe(context).size() == 0)
1143 return true; //FIXME
1144 // Object elements[] = viewer.getVisibleExpandedElements();
1145 // return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0));
1151 public TransientExplorerState getTransientState() {
1152 if (!thread.currentThreadAccess())
1153 throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread());
1154 return transientState;
1158 public <T> T query(NodeContext context, CacheKey<T> key) {
1159 return this.explorerContext.cache.get(context, key);
1163 * For setting a more local service locator for the explorer than the global
1164 * workbench service locator. Sometimes required to give this implementation
1165 * access to local workbench services like IFocusService.
1168 * Must be invoked during right after construction.
1170 * @param serviceLocator
1171 * a specific service locator or <code>null</code> to use the
1172 * workbench global service locator
1174 public void setServiceLocator(IServiceLocator serviceLocator) {
1175 if (serviceLocator == null && PlatformUI.isWorkbenchRunning())
1176 serviceLocator = PlatformUI.getWorkbench();
1177 this.serviceLocator = serviceLocator;
1178 if (serviceLocator != null) {
1179 this.contextService = (IContextService) serviceLocator.getService(IContextService.class);
1180 this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class);
1184 private void detachPrimitiveProcessors() {
1185 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
1186 if (p instanceof ProcessorLifecycle) {
1187 ((ProcessorLifecycle) p).detached(this);
1192 private void clearPrimitiveProcessors() {
1193 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
1194 if (p instanceof ProcessorLifecycle) {
1195 ((ProcessorLifecycle) p).clear();
1201 public void setExpanded(NodeContext context, boolean expanded) {
1202 for (TreeNode n : contextToNodeMap.getValues(context)) {
1204 treeLayer.expandTreeRow(n.getListIndex());
1206 treeLayer.collapseTreeRow(n.getListIndex());
1212 public void setAutoExpandLevel(int level) {
1213 this.autoExpandLevel = level;
1214 treeLayer.expandAllToLevel(level);
1217 int maxChildren = DEFAULT_MAX_CHILDREN;
1220 public int getMaxChildren() {
1225 public void setMaxChildren(int maxChildren) {
1226 this.maxChildren = maxChildren;
1231 public int getMaxChildren(NodeQueryManager manager, NodeContext context) {
1232 Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN);
1233 //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result);
1234 if (result != null) {
1236 throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result);
1243 public <T> NodeQueryProcessor<T> getProcessor(QueryKey<T> key) {
1244 return explorerContext.getProcessor(key);
1248 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(PrimitiveQueryKey<T> key) {
1249 return explorerContext.getPrimitiveProcessor(key);
1252 private HashSet<UpdateItem> pendingItems = new HashSet<UpdateItem>();
1253 private boolean updating = false;
1254 private int updateCounter = 0;
1255 final ScheduledExecutorService uiUpdateScheduler = ThreadUtils.getNonBlockingWorkExecutor();
1257 private class UpdateItem {
1261 public UpdateItem(TreeNode element) {
1265 public UpdateItem(TreeNode element, int columnIndex) {
1266 this.element = element;
1267 this.columnIndex = columnIndex;
1268 if (element != null && element.isDisposed()) {
1269 throw new IllegalArgumentException("Node is disposed. " + element);
1273 public void update(NatTable natTable) {
1274 if (element != null) {
1276 if (element.isDisposed()) {
1279 if (element.updateChildren()) {
1281 System.out.println("Update Item updateChildren " + element.listIndex + " " + element);
1282 printDebug(NatTableGraphExplorer.this);
1285 if (!element.isHidden()) {
1286 if (!element.isExpanded()) {
1287 if (element.listIndex >= 0)
1288 treeLayer.collapseTreeRow(element.listIndex);
1290 System.out.println("Update Item collapse " + element.listIndex);
1291 printDebug(NatTableGraphExplorer.this);
1294 for (TreeNode c : element.getChildren())
1298 TreeNode p = element.getCollapsedAncestor();
1300 if (element.listIndex >= 0)
1301 treeLayer.collapseTreeRow(element.listIndex);
1302 if (p.listIndex >= 0)
1303 treeLayer.collapseTreeRow(p.listIndex);
1305 System.out.println("Update Item ancetor collapse " + p.listIndex);
1306 printDebug(NatTableGraphExplorer.this);
1311 // if (columnIndex >= 0) {
1312 // viewer.update(element, new String[]{columns[columnIndex].getKey()});
1314 // viewer.refresh(element,true);
1319 if (!element.autoExpanded && !element.isDisposed() && autoExpandLevel > 1 && !element.isExpanded() && element.getDepth() <= autoExpandLevel) {
1321 element.autoExpanded = true;
1323 if (DEBUG) System.out.println("Update Item expand " + element.listIndex);
1324 treeLayer.expandTreeRow(element.getListIndex());
1325 //viewer.setExpandedState(element, true);
1329 if (rootNode.updateChildren()) {
1336 public boolean equals(Object obj) {
1339 if (obj.getClass() != getClass())
1341 UpdateItem other = (UpdateItem)obj;
1342 if (columnIndex != other.columnIndex)
1344 if (element != null)
1345 return element.equals(other.element);
1346 return other.element == null;
1350 public int hashCode() {
1351 if (element != null)
1352 return element.hashCode() + columnIndex;
1357 private void update(final TreeNode element, final int columnIndex) {
1358 if (natTable.isDisposed())
1360 if (element != null && element.isDisposed())
1362 if (DEBUG) System.out.println("update " + element + " " + columnIndex);
1363 synchronized (pendingItems) {
1364 pendingItems.add(new UpdateItem(element, columnIndex));
1365 if (updating) return;
1371 private void update(final TreeNode element) {
1373 if (natTable.isDisposed())
1375 if (element != null && element.isDisposed())
1377 if (DEBUG) System.out.println("update " + element);
1378 synchronized (pendingItems) {
1379 pendingItems.add(new UpdateItem(element));
1380 if (updating) return;
1386 boolean scheduleUpdater() {
1388 if (natTable.isDisposed())
1391 if (!pendingItems.isEmpty()) {
1393 int activity = explorerContext.activityInt;
1395 if (activity < 100) {
1396 //System.out.println("Scheduling update immediately.");
1397 } else if (activity < 1000) {
1398 //System.out.println("Scheduling update after 500ms.");
1401 //System.out.println("Scheduling update after 3000ms.");
1407 //System.out.println("Scheduling UI update after " + delay + " ms.");
1408 uiUpdateScheduler.schedule(new Runnable() {
1412 if (natTable == null || natTable.isDisposed())
1415 if (updateCounter > 0) {
1417 uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS);
1419 natTable.getDisplay().asyncExec(new UpdateRunner(NatTableGraphExplorer.this, NatTableGraphExplorer.this.explorerContext));
1423 }, delay, TimeUnit.MILLISECONDS);
1433 public String startEditing(NodeContext context, String columnKey) {
1434 assertNotDisposed();
1435 if (!thread.currentThreadAccess())
1436 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
1438 if(columnKey.startsWith("#")) {
1439 columnKey = columnKey.substring(1);
1442 Integer columnIndex = columnKeyToIndex.get(columnKey);
1443 if (columnIndex == null)
1444 return "Rename not supported for selection";
1446 // viewer.editElement(context, columnIndex);
1447 // if(viewer.isCellEditorActive()) return null;
1448 return "Rename not supported for selection";
1452 public String startEditing(String columnKey) {
1453 ISelection selection = postSelectionProvider.getSelection();
1454 if(selection == null) return "Rename not supported for selection";
1455 NodeContext context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class);
1456 if(context == null) return "Rename not supported for selection";
1458 return startEditing(context, columnKey);
1462 public void setSelection(final ISelection selection, boolean forceControlUpdate) {
1463 assertNotDisposed();
1464 boolean equalsOld = selectionProvider.selectionEquals(selection);
1465 if (equalsOld && !forceControlUpdate) {
1466 // Just set the selection object instance, fire no events nor update
1467 // the viewer selection.
1468 selectionProvider.setSelection(selection);
1470 Collection<NodeContext> coll = AdaptionUtils.adaptToCollection(selection, NodeContext.class);
1471 Collection<TreeNode> nodes = new ArrayList<TreeNode>();
1472 for (NodeContext c : coll) {
1473 List<TreeNode> match = contextToNodeMap.getValuesUnsafe(c);
1474 if(match.size() > 0)
1475 nodes.add(match.get(0));
1477 final ISelection sel = new StructuredSelection(nodes.toArray());
1478 if (coll.size() == 0)
1480 // Schedule viewer and selection update if necessary.
1481 if (natTable.isDisposed())
1483 Display d = natTable.getDisplay();
1484 if (d.getThread() == Thread.currentThread()) {
1485 selectionAdaptor.setSelection(sel);
1487 d.asyncExec(new Runnable() {
1490 if (natTable.isDisposed())
1492 selectionAdaptor.setSelection(sel);
1500 public void setModificationContext(ModificationContext modificationContext) {
1501 this.modificationContext = modificationContext;
1505 final ExecutorService queryUpdateScheduler = Threads.getExecutor();
1508 private double getDisplayScale() {
1509 Point dpi = Display.getCurrent().getDPI();
1510 return (double)dpi.x/96.0;
1513 private void createNatTable(int style) {
1514 GETreeData treeData = new GETreeData(list);
1515 GETreeRowModel<TreeNode> treeRowModel = new GETreeRowModel<TreeNode>(treeData);
1516 columnAccessor = new GEColumnAccessor(this);
1518 IDataProvider dataProvider = new ListDataProvider<TreeNode>(list, columnAccessor);
1520 // FIXME: NatTable 1.0 required help to work with custom display scaling (Windows 7 display scaling).
1521 // It seems that NatTable 1.4 breaks with the same code in Windows 7, so now the code is disabled.
1522 // More testing with different hardware is required...
1523 // int defaultFontSize = 12;
1524 // int height = (int)Math.ceil(((double)(defaultFontSize))*getDisplayScale()) + DataLayer.DEFAULT_ROW_HEIGHT-defaultFontSize;
1525 // dataLayer = new DataLayer(dataProvider, DataLayer.DEFAULT_COLUMN_WIDTH, height);
1526 dataLayer = new DataLayer(dataProvider);
1528 // resizable rows are unnecessary in Sulca report.
1529 dataLayer.setRowsResizableByDefault(false);
1532 DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(dataProvider);
1533 rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider);
1535 // adjust row header column width so that row numbers fit into the column.
1536 //adjustRowHeaderWidth(list.size());
1538 // Column header layer
1539 columnHeaderDataProvider = new GEColumnHeaderDataProvider(this, dataLayer);
1540 columnHeaderDataLayer = new DefaultColumnHeaderDataLayer(columnHeaderDataProvider);
1541 //columnHeaderDataLayer.setDefaultRowHeight(height);
1542 columnHeaderDataProvider.updateColumnSizes();
1544 //ISortModel sortModel = new EcoSortModel(this, generator,dataLayer);
1546 // Column re-order + hide
1547 ColumnReorderLayer columnReorderLayer = new ColumnReorderLayer(dataLayer);
1548 ColumnHideShowLayer columnHideShowLayer = new ColumnHideShowLayer(columnReorderLayer);
1551 treeLayer = new GETreeLayer(columnHideShowLayer, treeRowModel, false);
1553 selectionLayer = new SelectionLayer(treeLayer);
1555 viewportLayer = new ViewportLayer(selectionLayer);
1557 ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, viewportLayer, selectionLayer);
1558 // Note: The column header layer is wrapped in a filter row composite.
1559 // This plugs in the filter row functionality
1561 ColumnOverrideLabelAccumulator labelAccumulator = new ColumnOverrideLabelAccumulator(columnHeaderDataLayer);
1562 columnHeaderDataLayer.setConfigLabelAccumulator(labelAccumulator);
1565 //SortHeaderLayer<TreeNode> sortHeaderLayer = new SortHeaderLayer<TreeNode>(columnHeaderLayer, sortModel, false);
1567 RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, viewportLayer, selectionLayer);
1570 DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider);
1571 cornerDataLayer = new DataLayer(cornerDataProvider);
1572 //CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, sortHeaderLayer);
1573 CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, columnHeaderLayer);
1576 //GridLayer gridLayer = new GridLayer(viewportLayer,sortHeaderLayer,rowHeaderLayer, cornerLayer);
1577 GridLayer gridLayer = new GridLayer(viewportLayer, columnHeaderLayer,rowHeaderLayer, cornerLayer, false);
1579 /* Since 1.4.0, alternative row rendering uses row indexes in the original data list.
1580 When combined with collapsed tree rows, rows with odd or even index may end up next to each other,
1581 which defeats the purpose of alternating colors. This overrides that and returns the functionality
1582 that we had with 1.0.1. */
1583 gridLayer.setConfigLabelAccumulatorForRegion(GridRegion.BODY, new RelativeAlternatingRowConfigLabelAccumulator());
1584 gridLayer.addConfiguration(new DefaultEditConfiguration());
1585 //gridLayer.addConfiguration(new DefaultEditBindings());
1586 gridLayer.addConfiguration(new GEEditBindings());
1588 natTable = new NatTable(composite,gridLayer,false);
1590 //selectionLayer.registerCommandHandler(new EcoCopyDataCommandHandler(selectionLayer,columnHeaderDataLayer,columnAccessor, columnHeaderDataProvider));
1592 natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable));
1593 natTable.addConfiguration(new DefaultTreeLayerConfiguration2(treeLayer));
1594 natTable.addConfiguration(new SingleClickSortConfiguration());
1595 //natTable.addLayerListener(this);
1597 natTable.addConfiguration(new GENatTableThemeConfiguration(treeData, style));
1598 natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable));
1600 natTable.addConfiguration(new AbstractRegistryConfiguration() {
1603 public void configureRegistry(IConfigRegistry configRegistry) {
1604 configRegistry.registerConfigAttribute(
1605 EditConfigAttributes.CELL_EDITABLE_RULE,
1606 new IEditableRule() {
1609 public boolean isEditable(ILayerCell cell,
1610 IConfigRegistry configRegistry) {
1611 int col = cell.getColumnIndex();
1612 int row = cell.getRowIndex();
1613 TreeNode node = list.get(row);
1614 Modifier modifier = getModifier(node,col);
1615 return modifier != null;
1620 public boolean isEditable(int columnIndex, int rowIndex) {
1621 // there are no callers?
1626 configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, new AdaptableCellEditor());
1627 configRegistry.registerConfigAttribute(EditConfigAttributes.CONVERSION_ERROR_HANDLER, new DialogErrorHandling(), DisplayMode.EDIT);
1628 configRegistry.registerConfigAttribute(EditConfigAttributes.VALIDATION_ERROR_HANDLER, new DialogErrorHandling(), DisplayMode.EDIT);
1629 configRegistry.registerConfigAttribute(EditConfigAttributes.DATA_VALIDATOR, new AdaptableDataValidator(),DisplayMode.EDIT);
1631 Style conversionErrorStyle = new Style();
1632 conversionErrorStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, GUIHelper.COLOR_RED);
1633 conversionErrorStyle.setAttributeValue(CellStyleAttributes.FOREGROUND_COLOR, GUIHelper.COLOR_WHITE);
1634 configRegistry.registerConfigAttribute(EditConfigAttributes.CONVERSION_ERROR_STYLE, conversionErrorStyle, DisplayMode.EDIT);
1636 configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, new DefaultDisplayConverter(),DisplayMode.EDIT);
1642 if ((style & SWT.BORDER) > 0) {
1643 natTable.addOverlayPainter(new NatTableBorderOverlayPainter());
1646 natTable.configure();
1648 // natTable.addListener(SWT.MenuDetect, new NatTableMenuListener());
1650 // DefaultToolTip toolTip = new EcoCellToolTip(natTable, columnAccessor);
1651 // toolTip.setBackgroundColor(natTable.getDisplay().getSystemColor(SWT.COLOR_WHITE));
1652 // toolTip.setPopupDelay(500);
1653 // toolTip.activate();
1654 // toolTip.setShift(new Point(10, 10));
1657 // menuManager.createContextMenu(composite);
1658 // natTable.setMenu(getMenuManager().getMenu());
1660 selectionAdaptor = new NatTableSelectionAdaptor(natTable, selectionLayer, treeData);
1663 Modifier getModifier(TreeNode element, int columnIndex) {
1664 GENodeQueryManager manager = element.getManager();
1665 final NodeContext context = element.getContext();
1666 Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
1667 if (labeler == null)
1669 Column column = columns[columnIndex];
1671 return labeler.getModifier(modificationContext, column.getKey());
1675 private class AdaptableCellEditor implements ICellEditor {
1679 public Control activateCell(Composite parent, Object originalCanonicalValue, EditModeEnum editMode,
1680 ICellEditHandler editHandler, ILayerCell cell, IConfigRegistry configRegistry) {
1681 int col = cell.getColumnIndex();
1682 int row = cell.getRowIndex();
1683 TreeNode node = list.get(row);
1684 Modifier modifier = getModifier(node, col);
1685 if (modifier == null)
1689 if (modifier instanceof DialogModifier) {
1690 DialogModifier mod = (DialogModifier)modifier;
1691 editor = new DialogCellEditor(node, col, mod);
1692 } else if (modifier instanceof CustomModifier) {
1693 CustomModifier mod = (CustomModifier)modifier;
1694 editor = new CustomCellEditor(node, col, mod);
1695 } else if (modifier instanceof EnumerationModifier) {
1696 EnumerationModifier mod = (EnumerationModifier)modifier;
1697 editor = new ComboBoxCellEditor(mod.getValues());
1699 editor = new TextCellEditor();
1702 return editor.activateCell(parent, originalCanonicalValue, editMode, editHandler, cell, configRegistry);
1706 public int getColumnIndex() {
1707 return editor.getColumnIndex();
1711 public int getRowIndex() {
1712 return editor.getRowIndex();
1716 public int getColumnPosition() {
1717 return editor.getColumnPosition();
1721 public int getRowPosition() {
1722 return editor.getRowPosition();
1726 public Object getEditorValue() {
1727 return editor.getEditorValue();
1731 public void setEditorValue(Object value) {
1732 editor.setEditorValue(value);
1737 public Object getCanonicalValue() {
1738 return editor.getCanonicalValue();
1742 public Object getCanonicalValue(IEditErrorHandler conversionErrorHandler) {
1743 return editor.getCanonicalValue();
1747 public void setCanonicalValue(Object canonicalValue) {
1748 editor.setCanonicalValue(canonicalValue);
1753 public boolean validateCanonicalValue(Object canonicalValue) {
1754 return editor.validateCanonicalValue(canonicalValue);
1758 public boolean validateCanonicalValue(Object canonicalValue, IEditErrorHandler validationErrorHandler) {
1759 return editor.validateCanonicalValue(canonicalValue, validationErrorHandler);
1763 public boolean commit(MoveDirectionEnum direction) {
1764 return editor.commit(direction);
1768 public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit) {
1769 return editor.commit(direction, closeAfterCommit);
1773 public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit, boolean skipValidation) {
1774 return editor.commit(direction, closeAfterCommit, skipValidation);
1778 public void close() {
1784 public boolean isClosed() {
1785 return editor.isClosed();
1789 public Control getEditorControl() {
1790 return editor.getEditorControl();
1794 public Control createEditorControl(Composite parent) {
1795 return editor.createEditorControl(parent);
1799 public boolean openInline(IConfigRegistry configRegistry, List<String> configLabels) {
1800 return EditConfigHelper.openInline(configRegistry, configLabels);
1804 public boolean supportMultiEdit(IConfigRegistry configRegistry, List<String> configLabels) {
1805 return editor.supportMultiEdit(configRegistry, configLabels);
1809 public boolean openMultiEditDialog() {
1810 return editor.openMultiEditDialog();
1814 public boolean openAdjacentEditor() {
1815 return editor.openAdjacentEditor();
1819 public boolean activateAtAnyPosition() {
1824 public boolean activateOnTraversal(IConfigRegistry configRegistry, List<String> configLabels) {
1825 return editor.activateOnTraversal(configRegistry, configLabels);
1829 public void addEditorControlListeners() {
1830 editor.addEditorControlListeners();
1835 public void removeEditorControlListeners() {
1836 editor.removeEditorControlListeners();
1841 public Rectangle calculateControlBounds(Rectangle cellBounds) {
1842 return editor.calculateControlBounds(cellBounds);
1846 private class AdaptableDataValidator implements IDataValidator {
1848 public boolean validate(ILayerCell cell, IConfigRegistry configRegistry, Object newValue) {
1849 int col = cell.getColumnIndex();
1850 int row = cell.getRowIndex();
1851 return validate(col, row, newValue);
1855 public boolean validate(int col, int row, Object newValue) {
1856 TreeNode node = list.get(row);
1857 Modifier modifier = getModifier(node, col);
1858 if (modifier == null)
1861 String err = modifier.isValid(newValue != null ? newValue.toString() : "");
1864 throw new ValidationFailedException(err);
1868 private class CustomCellEditor extends AbstractCellEditor {
1870 CustomModifier customModifier;
1874 public CustomCellEditor(TreeNode node, int column, CustomModifier customModifier) {
1875 this.customModifier = customModifier;
1877 this.column = column;
1881 public Object getEditorValue() {
1882 return customModifier.getValue();
1886 public void setEditorValue(Object value) {
1887 customModifier.modify(value.toString());
1892 public Control getEditorControl() {
1897 public Control createEditorControl(Composite parent) {
1898 return (Control)customModifier.createControl(parent, null, column, node.getContext());
1903 protected Control activateCell(Composite parent, Object originalCanonicalValue) {
1904 this.control = createEditorControl(parent);
1911 private class DialogCellEditor extends AbstractDialogCellEditor {
1913 DialogModifier dialogModifier;
1919 boolean closed = false;
1921 public DialogCellEditor(TreeNode node, int column, DialogModifier dialogModifier) {
1922 this.dialogModifier = dialogModifier;
1924 this.column = column;
1929 sem = new Semaphore(1);
1930 Consumer<String> callback = result -> {
1934 String status = dialogModifier.query(this.parent.getShell(), null, column, node.getContext(), callback);
1935 if (status != null) {
1937 return Window.CANCEL;
1942 } catch (InterruptedException e) {
1943 e.printStackTrace();
1950 public DialogModifier createDialogInstance() {
1952 return dialogModifier;
1956 public Object getDialogInstance() {
1957 return (DialogModifier)this.dialog;
1961 public void close() {
1966 public Object getEditorValue() {
1971 public void setEditorValue(Object value) {
1972 // dialog modifier handles this internally
1976 public boolean isClosed() {
1984 * The job that is used for off-loading image loading tasks (see
1985 * {@link ImageTask} to a worker thread from the main UI thread.
1987 ImageLoaderJob imageLoaderJob;
1989 // Map<NodeContext, ImageTask> imageTasks = new THashMap<NodeContext, ImageTask>();
1990 Map<TreeNode, ImageTask> imageTasks = new THashMap<TreeNode, ImageTask>();
1992 void queueImageTask(TreeNode node, ImageTask task) {
1993 synchronized (imageTasks) {
1994 imageTasks.put(node, task);
1996 imageLoaderJob.scheduleIfNecessary(100);
2000 * Invoked in a job worker thread.
2005 protected IStatus setPendingImages(IProgressMonitor monitor) {
2006 ImageTask[] tasks = null;
2007 synchronized (imageTasks) {
2008 tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]);
2012 MultiStatus status = null;
2014 // Load missing images
2015 for (ImageTask task : tasks) {
2016 Object desc = task.descsOrImage;
2017 if (desc instanceof ImageDescriptor) {
2019 desc = resourceManager.get((ImageDescriptor) desc);
2020 task.descsOrImage = desc;
2021 } catch (DeviceResourceException e) {
2023 status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null);
2024 status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e));
2030 // Perform final UI updates in the UI thread.
2031 final ImageTask[] _tasks = tasks;
2032 thread.asyncExec(new Runnable() {
2039 return status != null ? status : Status.OK_STATUS;
2043 void setImages(ImageTask[] tasks) {
2044 for (ImageTask task : tasks)
2049 void setImage(ImageTask task) {
2050 if (!task.node.isDisposed())
2051 update(task.node, 0);
2054 private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider {
2056 private NatTableGraphExplorer ge;
2058 GraphExplorerPostSelectionProvider(NatTableGraphExplorer ge) {
2067 public void setSelection(final ISelection selection) {
2068 if(ge == null) return;
2069 ge.setSelection(selection, false);
2075 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
2076 if(ge == null) return;
2077 if(ge.isDisposed()) {
2078 if (DEBUG_SELECTION_LISTENERS)
2079 System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener);
2082 ge.selectionProvider.removeSelectionChangedListener(listener);
2086 public void addPostSelectionChangedListener(ISelectionChangedListener listener) {
2087 if(ge == null) return;
2088 if (!ge.thread.currentThreadAccess())
2089 throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2090 if(ge.isDisposed()) {
2091 System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener);
2094 ge.selectionProvider.addPostSelectionChangedListener(listener);
2098 public void removePostSelectionChangedListener(ISelectionChangedListener listener) {
2099 if(ge == null) return;
2100 if(ge.isDisposed()) {
2101 if (DEBUG_SELECTION_LISTENERS)
2102 System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener);
2105 ge.selectionProvider.removePostSelectionChangedListener(listener);
2110 public void addSelectionChangedListener(ISelectionChangedListener listener) {
2111 if(ge == null) return;
2112 if (!ge.thread.currentThreadAccess())
2113 throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2114 if (ge.natTable.isDisposed() || ge.selectionProvider == null) {
2115 System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener);
2119 ge.selectionProvider.addSelectionChangedListener(listener);
2124 public ISelection getSelection() {
2125 if(ge == null) return StructuredSelection.EMPTY;
2126 if (!ge.thread.currentThreadAccess())
2127 throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread());
2128 if (ge.natTable.isDisposed() || ge.selectionProvider == null)
2129 return StructuredSelection.EMPTY;
2130 return ge.selectionProvider.getSelection();
2135 static class ModifierValidator implements ICellEditorValidator {
2136 private Modifier modifier;
2137 public ModifierValidator(Modifier modifier) {
2138 this.modifier = modifier;
2142 public String isValid(Object value) {
2143 return modifier.isValid((String)value);
2147 static class UpdateRunner implements Runnable {
2149 final NatTableGraphExplorer ge;
2151 UpdateRunner(NatTableGraphExplorer ge, IGraphExplorerContext geContext) {
2158 } catch (Throwable t) {
2159 t.printStackTrace();
2163 public void doRun() {
2165 if (ge.isDisposed())
2168 HashSet<UpdateItem> items;
2170 ScrollBar verticalBar = ge.natTable.getVerticalBar();
2173 synchronized (ge.pendingItems) {
2174 items = ge.pendingItems;
2175 ge.pendingItems = new HashSet<UpdateItem>();
2177 if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size());
2179 //ge.natTable.setRedraw(false);
2180 for (UpdateItem item : items) {
2181 item.update(ge.natTable);
2184 // check if vertical scroll bar has become visible and refresh layout.
2185 boolean currentlyVerticalBarVisible = verticalBar.isVisible();
2186 if (ge.verticalBarVisible != currentlyVerticalBarVisible) {
2187 ge.verticalBarVisible = currentlyVerticalBarVisible;
2188 ge.natTable.getParent().layout();
2191 //ge.natTable.setRedraw(true);
2193 synchronized (ge.pendingItems) {
2194 if (!ge.scheduleUpdater()) {
2195 ge.updating = false;
2207 private static void printDebug(NatTableGraphExplorer ge) {
2208 ge.printTree(ge.rootNode, 0);
2209 System.out.println("Expanded");
2210 for (TreeNode n : ge.treeLayer.expanded)
2211 System.out.println(n);
2212 System.out.println("Expanded end");
2213 System.out.println("Hidden ");
2214 for (int i : ge.treeLayer.getHiddenRowIndexes()) {
2215 System.out.print(i + " ");
2217 System.out.println();
2218 // Display.getCurrent().timerExec(1000, new Runnable() {
2221 // public void run() {
2222 // System.out.println("Hidden delayed ");
2223 // for (int i : ge.treeLayer.getHiddenRowIndexes()) {
2224 // System.out.print(i + " ");
2226 // System.out.println();
2232 public static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {
2233 // This is for query debugging only.
2235 private NatTableGraphExplorer ge;
2236 int queryIndent = 0;
2238 GECache2 cache = new GECache2();
2239 AtomicBoolean propagating = new AtomicBoolean(false);
2240 Object propagateList = new Object();
2241 Object propagate = new Object();
2242 List<Runnable> scheduleList = new ArrayList<Runnable>();
2243 final Deque<Integer> activity = new LinkedList<Integer>();
2244 int activityInt = 0;
2246 AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();
2249 * Keeps track of nodes that have already been auto-expanded. After
2250 * being inserted into this set, nodes will not be forced to stay in an
2251 * expanded state after that. This makes it possible for the user to
2252 * close auto-expanded nodes.
2254 Map<NodeContext, Boolean> autoExpanded = new WeakHashMap<NodeContext, Boolean>();
2256 public GeViewerContext(NatTableGraphExplorer ge) {
2260 public MapList<NodeContext,TreeNode> getContextToNodeMap() {
2263 return ge.contextToNodeMap;
2266 public NatTableGraphExplorer getGe() {
2271 protected void doDispose() {
2273 autoExpanded.clear();
2277 public IGECache getCache() {
2282 public int queryIndent() {
2287 public int queryIndent(int offset) {
2288 queryIndent += offset;
2293 @SuppressWarnings("unchecked")
2294 public <T> NodeQueryProcessor<T> getProcessor(Object o) {
2297 return ge.processors.get(o);
2301 @SuppressWarnings("unchecked")
2302 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {
2303 return ge.primitiveProcessors.get(o);
2306 @SuppressWarnings("unchecked")
2308 public <T> DataSource<T> getDataSource(Class<T> clazz) {
2309 return ge.dataSources.get(clazz);
2313 public void update(UIElementReference ref) {
2314 if (ref instanceof ViewerCellReference) {
2315 ViewerCellReference tiref = (ViewerCellReference) ref;
2316 Object element = tiref.getElement();
2317 int columnIndex = tiref.getColumn();
2318 // NOTE: must be called regardless of the the item value.
2319 // A null item is currently used to indicate a tree root update.
2320 ge.update((TreeNode)element,columnIndex);
2321 } else if (ref instanceof ViewerRowReference) {
2322 ViewerRowReference rref = (ViewerRowReference)ref;
2323 Object element = rref.getElement();
2324 ge.update((TreeNode)element);
2326 throw new IllegalArgumentException("Ui Reference is unknkown " + ref);
2331 public Object getPropagateLock() {
2336 public Object getPropagateListLock() {
2337 return propagateList;
2341 public boolean isPropagating() {
2342 return propagating.get();
2346 public void setPropagating(boolean b) {
2347 this.propagating.set(b);
2351 public List<Runnable> getScheduleList() {
2352 return scheduleList;
2356 public void setScheduleList(List<Runnable> list) {
2357 this.scheduleList = list;
2361 public Deque<Integer> getActivity() {
2366 public void setActivityInt(int i) {
2367 this.activityInt = i;
2371 public int getActivityInt() {
2376 public void scheduleQueryUpdate(Runnable r) {
2379 if (ge.isDisposed())
2381 if (currentQueryUpdater.compareAndSet(null, r)) {
2382 ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER);
2386 Runnable QUERY_UPDATE_SCHEDULER = new Runnable() {
2389 Runnable r = currentQueryUpdater.getAndSet(null);
2397 public void dispose() {
2399 cache = new DummyCache();
2400 scheduleList.clear();
2401 autoExpanded.clear();
2402 autoExpanded = null;
2408 private class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor<Boolean> implements
2409 IsExpandedProcessor, ProcessorLifecycle {
2411 * The set of currently expanded node contexts.
2413 private final HashSet<NodeContext> expanded = new HashSet<NodeContext>();
2414 private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();
2416 private NatTable natTable;
2417 private List<TreeNode> list;
2419 public TreeNodeIsExpandedProcessor() {
2423 public Object getIdentifier() {
2424 return BuiltinKeys.IS_EXPANDED;
2428 public String toString() {
2429 return "IsExpandedProcessor";
2433 public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Boolean> key) {
2434 boolean isExpanded = expanded.contains(context);
2435 expandedQueries.put(context, updater);
2436 return Boolean.valueOf(isExpanded);
2440 public Collection<NodeContext> getExpanded() {
2441 return new HashSet<NodeContext>(expanded);
2445 public boolean getExpanded(NodeContext context) {
2446 return this.expanded.contains(context);
2450 public boolean setExpanded(NodeContext context, boolean expanded) {
2451 return _setExpanded(context, expanded);
2455 public boolean replaceExpanded(NodeContext context, boolean expanded) {
2456 return nodeStatusChanged(context, expanded);
2459 private boolean _setExpanded(NodeContext context, boolean expanded) {
2461 return this.expanded.add(context);
2463 return this.expanded.remove(context);
2467 ILayerListener treeListener = new ILayerListener() {
2470 public void handleLayerEvent(ILayerEvent event) {
2471 if (event instanceof ShowRowPositionsEvent) {
2472 ShowRowPositionsEvent e = (ShowRowPositionsEvent)event;
2473 for (Range r : e.getRowPositionRanges()) {
2474 int expanded = viewportLayer.getRowIndexByPosition(r.start-2)+1;
2475 if (DEBUG)System.out.println("IsExpandedProcessor expand " + expanded);
2476 if (expanded < 0 || expanded >= list.size()) {
2479 nodeStatusChanged(list.get(expanded).getContext(), true);
2481 } else if (event instanceof HideRowPositionsEvent) {
2482 HideRowPositionsEvent e = (HideRowPositionsEvent)event;
2483 for (Range r : e.getRowPositionRanges()) {
2484 int collapsed = viewportLayer.getRowIndexByPosition(r.start-2);
2485 if (DEBUG)System.out.println("IsExpandedProcessor collapse " + collapsed);
2486 if (collapsed < 0 || collapsed >= list.size()) {
2489 nodeStatusChanged(list.get(collapsed).getContext(), false);
2496 protected boolean nodeStatusChanged(NodeContext context, boolean expanded) {
2497 boolean result = _setExpanded(context, expanded);
2498 PrimitiveQueryUpdater updater = expandedQueries.get(context);
2499 if (updater != null)
2500 updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded);
2505 public void attached(GraphExplorer explorer) {
2506 Object control = explorer.getControl();
2507 if (control instanceof NatTable) {
2508 this.natTable = (NatTable) control;
2509 this.list = ((NatTableGraphExplorer)explorer).list;
2510 natTable.addLayerListener(treeListener);
2513 System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control);
2518 public void clear() {
2520 expandedQueries.clear();
2524 public void detached(GraphExplorer explorer) {
2526 if (natTable != null) {
2527 natTable.removeLayerListener(treeListener);
2528 // natTable.removeListener(SWT.Expand, treeListener);
2529 // natTable.removeListener(SWT.Collapse, treeListener);
2535 private void printTree(TreeNode node, int depth) {
2537 for (int i = 0; i < depth; i++) {
2541 System.out.println(s);
2543 for (TreeNode n : node.getChildren()) {
2550 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used)
2552 final private static class GECacheKey {
2554 private NodeContext context;
2555 private CacheKey<?> key;
2557 GECacheKey(NodeContext context, CacheKey<?> key) {
2558 this.context = context;
2560 if (context == null || key == null)
2561 throw new IllegalArgumentException("Null context or key is not accepted");
2564 GECacheKey(GECacheKey other) {
2565 this.context = other.context;
2566 this.key = other.key;
2567 if (context == null || key == null)
2568 throw new IllegalArgumentException("Null context or key is not accepted");
2571 void setValues(NodeContext context, CacheKey<?> key) {
2572 this.context = context;
2574 if (context == null || key == null)
2575 throw new IllegalArgumentException("Null context or key is not accepted");
2579 public int hashCode() {
2580 return context.hashCode() | key.hashCode();
2584 public boolean equals(Object object) {
2588 else if (object == null)
2591 GECacheKey i = (GECacheKey) object;
2593 return key.equals(i.key) && context.equals(i.context);
2600 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data.
2602 public static class GECache2 implements IGECache {
2604 final HashMap<GECacheKey, IGECacheEntry> entries = new HashMap<GECacheKey, IGECacheEntry>();
2605 final HashMap<GECacheKey, Set<UIElementReference>> treeReferences = new HashMap<GECacheKey, Set<UIElementReference>>();
2606 final HashMap<NodeContext, Set<GECacheKey>> keyRefs = new HashMap<NodeContext, Set<GECacheKey>>();
2609 * This single instance is used for all get operations from the cache. This
2610 * should work since the GE cache is meant to be single-threaded within the
2611 * current UI thread, what ever that thread is. For put operations which
2612 * store the key, this is not used.
2614 NodeContext getNC = new NodeContext() {
2615 @SuppressWarnings("rawtypes")
2617 public Object getAdapter(Class adapter) {
2622 public <T> T getConstant(ConstantKey<T> key) {
2627 public Set<ConstantKey<?>> getKeys() {
2628 return Collections.emptySet();
2631 CacheKey<?> getCK = new CacheKey<Object>() {
2633 public Object processorIdenfitier() {
2637 GECacheKey getKey = new GECacheKey(getNC, getCK);
2640 private void addKey(GECacheKey key) {
2641 Set<GECacheKey> refs = keyRefs.get(key.context);
2645 refs = new HashSet<GECacheKey>();
2647 keyRefs.put(key.context, refs);
2651 private void removeKey(GECacheKey key) {
2652 Set<GECacheKey> refs = keyRefs.get(key.context);
2658 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key, T value) {
2659 // if (DEBUG) System.out.println("Add entry " + context + " " + key);
2660 IGECacheEntry entry = new GECacheEntry(context, key, value);
2661 GECacheKey gekey = new GECacheKey(context, key);
2662 entries.put(gekey, entry);
2667 @SuppressWarnings("unchecked")
2668 public <T> T get(NodeContext context, CacheKey<T> key) {
2669 getKey.setValues(context, key);
2670 IGECacheEntry entry = entries.get(getKey);
2673 return (T) entry.getValue();
2677 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
2678 assert(context != null);
2679 assert(key != null);
2680 getKey.setValues(context, key);
2681 return entries.get(getKey);
2685 public <T> void remove(NodeContext context, CacheKey<T> key) {
2686 // if (DEBUG) System.out.println("Remove entry " + context + " " + key);
2687 getKey.setValues(context, key);
2688 entries.remove(getKey);
2693 public <T> Set<UIElementReference> getTreeReference(NodeContext context, CacheKey<T> key) {
2694 assert(context != null);
2695 assert(key != null);
2696 getKey.setValues(context, key);
2697 return treeReferences.get(getKey);
2701 public <T> void putTreeReference(NodeContext context, CacheKey<T> key, UIElementReference reference) {
2702 assert(context != null);
2703 assert(key != null);
2704 //if (DEBUG) System.out.println("Add tree reference " + context + " " + key);
2705 getKey.setValues(context, key);
2706 Set<UIElementReference> refs = treeReferences.get(getKey);
2708 refs.add(reference);
2710 refs = new HashSet<UIElementReference>(4);
2711 refs.add(reference);
2712 GECacheKey gekey = new GECacheKey(getKey);
2713 treeReferences.put(gekey, refs);
2719 public <T> Set<UIElementReference> removeTreeReference(NodeContext context, CacheKey<T> key) {
2720 assert(context != null);
2721 assert(key != null);
2722 //if (DEBUG) System.out.println("Remove tree reference " + context + " " + key);
2723 getKey.setValues(context, key);
2725 return treeReferences.remove(getKey);
2729 public boolean isShown(NodeContext context) {
2730 return references.get(context) > 0;
2733 private TObjectIntHashMap<NodeContext> references = new TObjectIntHashMap<NodeContext>();
2736 public void incRef(NodeContext context) {
2737 int exist = references.get(context);
2738 references.put(context, exist+1);
2742 public void decRef(NodeContext context) {
2743 int exist = references.get(context);
2744 references.put(context, exist-1);
2746 references.remove(context);
2750 public void dispose() {
2753 treeReferences.clear();
2757 public void dispose(NodeContext context) {
2758 Set<GECacheKey> keys = keyRefs.remove(context);
2760 for (GECacheKey key : keys) {
2761 entries.remove(key);
2762 treeReferences.remove(key);
2770 * Non-functional cache to replace actual cache when GEContext is disposed.
2775 private static class DummyCache extends GECache2 {
2778 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
2783 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key,
2789 public <T> void putTreeReference(NodeContext context, CacheKey<T> key,
2790 UIElementReference reference) {
2794 public <T> T get(NodeContext context, CacheKey<T> key) {
2799 public <T> Set<UIElementReference> getTreeReference(
2800 NodeContext context, CacheKey<T> key) {
2805 public <T> void remove(NodeContext context, CacheKey<T> key) {
2810 public <T> Set<UIElementReference> removeTreeReference(
2811 NodeContext context, CacheKey<T> key) {
2816 public boolean isShown(NodeContext context) {
2821 public void incRef(NodeContext context) {
2826 public void decRef(NodeContext context) {
2831 public void dispose() {
2836 private class NatTableHeaderMenuConfiguration extends AbstractHeaderMenuConfiguration {
2839 public NatTableHeaderMenuConfiguration(NatTable natTable) {
2844 protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) {
2845 return super.createColumnHeaderMenu(natTable)
2846 .withHideColumnMenuItem()
2847 .withShowAllColumnsMenuItem()
2848 .withAutoResizeSelectedColumnsMenuItem();
2852 protected PopupMenuBuilder createCornerMenu(NatTable natTable) {
2853 return super.createCornerMenu(natTable)
2854 .withShowAllColumnsMenuItem();
2857 protected PopupMenuBuilder createRowHeaderMenu(NatTable natTable) {
2858 return super.createRowHeaderMenu(natTable);
2862 private static class RelativeAlternatingRowConfigLabelAccumulator extends AlternatingRowConfigLabelAccumulator {
2865 public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) {
2866 configLabels.addLabel((rowPosition % 2 == 0 ? EVEN_ROW_CONFIG_TYPE : ODD_ROW_CONFIG_TYPE));
2871 public Object getClicked(Object event) {
2872 MouseEvent e = (MouseEvent)event;
2873 final NatTable tree = (NatTable) e.getSource();
2874 Point point = new Point(e.x, e.y);
2875 int y = natTable.getRowPositionByY(point.y);
2876 int x = natTable.getColumnPositionByX(point.x);
2879 return list.get(y-1);