Sync git svn branch with SVN repository r33144.
[simantics/platform.git] / bundles / org.simantics.browsing.ui.nattable / src / org / simantics / browsing / ui / nattable / NatTableGraphExplorer.java
1 package org.simantics.browsing.ui.nattable;\r
2 \r
3 import java.util.ArrayList;\r
4 import java.util.Arrays;\r
5 import java.util.Collection;\r
6 import java.util.Collections;\r
7 import java.util.Deque;\r
8 import java.util.HashMap;\r
9 import java.util.HashSet;\r
10 import java.util.LinkedList;\r
11 import java.util.List;\r
12 import java.util.Map;\r
13 import java.util.Set;\r
14 import java.util.WeakHashMap;\r
15 import java.util.concurrent.CopyOnWriteArrayList;\r
16 import java.util.concurrent.ExecutorService;\r
17 import java.util.concurrent.ScheduledExecutorService;\r
18 import java.util.concurrent.Semaphore;\r
19 import java.util.concurrent.TimeUnit;\r
20 import java.util.concurrent.atomic.AtomicBoolean;\r
21 import java.util.concurrent.atomic.AtomicReference;\r
22 import java.util.function.Consumer;\r
23 \r
24 import org.eclipse.core.runtime.Assert;\r
25 import org.eclipse.core.runtime.IProgressMonitor;\r
26 import org.eclipse.core.runtime.IStatus;\r
27 import org.eclipse.core.runtime.MultiStatus;\r
28 import org.eclipse.core.runtime.Platform;\r
29 import org.eclipse.core.runtime.Status;\r
30 import org.eclipse.core.runtime.jobs.Job;\r
31 import org.eclipse.jface.layout.GridDataFactory;\r
32 import org.eclipse.jface.layout.TreeColumnLayout;\r
33 import org.eclipse.jface.resource.ColorDescriptor;\r
34 import org.eclipse.jface.resource.DeviceResourceException;\r
35 import org.eclipse.jface.resource.DeviceResourceManager;\r
36 import org.eclipse.jface.resource.FontDescriptor;\r
37 import org.eclipse.jface.resource.ImageDescriptor;\r
38 import org.eclipse.jface.resource.JFaceResources;\r
39 import org.eclipse.jface.resource.LocalResourceManager;\r
40 import org.eclipse.jface.viewers.ColumnWeightData;\r
41 import org.eclipse.jface.viewers.ICellEditorValidator;\r
42 import org.eclipse.jface.viewers.IPostSelectionProvider;\r
43 import org.eclipse.jface.viewers.ISelection;\r
44 import org.eclipse.jface.viewers.ISelectionChangedListener;\r
45 import org.eclipse.jface.viewers.ISelectionProvider;\r
46 import org.eclipse.jface.viewers.SelectionChangedEvent;\r
47 import org.eclipse.jface.viewers.StructuredSelection;\r
48 import org.eclipse.jface.window.Window;\r
49 import org.eclipse.nebula.widgets.nattable.NatTable;\r
50 import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;\r
51 import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;\r
52 import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;\r
53 import org.eclipse.nebula.widgets.nattable.config.IEditableRule;\r
54 import org.eclipse.nebula.widgets.nattable.coordinate.Range;\r
55 import org.eclipse.nebula.widgets.nattable.data.IDataProvider;\r
56 import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;\r
57 import org.eclipse.nebula.widgets.nattable.data.convert.DefaultDisplayConverter;\r
58 import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;\r
59 import org.eclipse.nebula.widgets.nattable.edit.EditConfigHelper;\r
60 import org.eclipse.nebula.widgets.nattable.edit.ICellEditHandler;\r
61 import org.eclipse.nebula.widgets.nattable.edit.config.DefaultEditBindings;\r
62 import org.eclipse.nebula.widgets.nattable.edit.config.DefaultEditConfiguration;\r
63 import org.eclipse.nebula.widgets.nattable.edit.editor.AbstractCellEditor;\r
64 import org.eclipse.nebula.widgets.nattable.edit.editor.ComboBoxCellEditor;\r
65 import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor;\r
66 import org.eclipse.nebula.widgets.nattable.edit.editor.IEditErrorHandler;\r
67 import org.eclipse.nebula.widgets.nattable.edit.editor.TextCellEditor;\r
68 import org.eclipse.nebula.widgets.nattable.edit.gui.AbstractDialogCellEditor;\r
69 import org.eclipse.nebula.widgets.nattable.grid.GridRegion;\r
70 import org.eclipse.nebula.widgets.nattable.grid.cell.AlternatingRowConfigLabelAccumulator;\r
71 import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;\r
72 import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;\r
73 import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;\r
74 import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;\r
75 import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultColumnHeaderDataLayer;\r
76 import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;\r
77 import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;\r
78 import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;\r
79 import org.eclipse.nebula.widgets.nattable.hideshow.ColumnHideShowLayer;\r
80 import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent;\r
81 import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent;\r
82 import org.eclipse.nebula.widgets.nattable.layer.DataLayer;\r
83 import org.eclipse.nebula.widgets.nattable.layer.ILayerListener;\r
84 import org.eclipse.nebula.widgets.nattable.layer.LabelStack;\r
85 import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator;\r
86 import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;\r
87 import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;\r
88 import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter;\r
89 import org.eclipse.nebula.widgets.nattable.reorder.ColumnReorderLayer;\r
90 import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;\r
91 import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum;\r
92 import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration;\r
93 import org.eclipse.nebula.widgets.nattable.style.DisplayMode;\r
94 import org.eclipse.nebula.widgets.nattable.ui.menu.AbstractHeaderMenuConfiguration;\r
95 import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder;\r
96 import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;\r
97 import org.eclipse.nebula.widgets.nattable.widget.EditModeEnum;\r
98 import org.eclipse.swt.SWT;\r
99 import org.eclipse.swt.events.DisposeEvent;\r
100 import org.eclipse.swt.events.DisposeListener;\r
101 import org.eclipse.swt.events.FocusEvent;\r
102 import org.eclipse.swt.events.FocusListener;\r
103 import org.eclipse.swt.events.KeyEvent;\r
104 import org.eclipse.swt.events.KeyListener;\r
105 import org.eclipse.swt.events.MouseEvent;\r
106 import org.eclipse.swt.events.MouseListener;\r
107 import org.eclipse.swt.events.SelectionListener;\r
108 import org.eclipse.swt.graphics.Color;\r
109 import org.eclipse.swt.graphics.GC;\r
110 import org.eclipse.swt.graphics.Point;\r
111 import org.eclipse.swt.graphics.RGB;\r
112 import org.eclipse.swt.graphics.Rectangle;\r
113 import org.eclipse.swt.widgets.Composite;\r
114 import org.eclipse.swt.widgets.Control;\r
115 import org.eclipse.swt.widgets.Display;\r
116 import org.eclipse.swt.widgets.Event;\r
117 import org.eclipse.swt.widgets.Listener;\r
118 import org.eclipse.swt.widgets.ScrollBar;\r
119 import org.eclipse.ui.PlatformUI;\r
120 import org.eclipse.ui.contexts.IContextActivation;\r
121 import org.eclipse.ui.contexts.IContextService;\r
122 import org.eclipse.ui.services.IServiceLocator;\r
123 import org.eclipse.ui.swt.IFocusService;\r
124 import org.simantics.browsing.ui.BuiltinKeys;\r
125 import org.simantics.browsing.ui.Column;\r
126 import org.simantics.browsing.ui.Column.Align;\r
127 import org.simantics.browsing.ui.DataSource;\r
128 import org.simantics.browsing.ui.ExplorerState;\r
129 import org.simantics.browsing.ui.GraphExplorer;\r
130 import org.simantics.browsing.ui.NodeContext;\r
131 import org.simantics.browsing.ui.NodeContext.CacheKey;\r
132 import org.simantics.browsing.ui.NodeContext.PrimitiveQueryKey;\r
133 import org.simantics.browsing.ui.NodeContext.QueryKey;\r
134 import org.simantics.browsing.ui.NodeQueryManager;\r
135 import org.simantics.browsing.ui.NodeQueryProcessor;\r
136 import org.simantics.browsing.ui.PrimitiveQueryProcessor;\r
137 import org.simantics.browsing.ui.PrimitiveQueryUpdater;\r
138 import org.simantics.browsing.ui.SelectionDataResolver;\r
139 import org.simantics.browsing.ui.SelectionFilter;\r
140 import org.simantics.browsing.ui.StatePersistor;\r
141 import org.simantics.browsing.ui.common.ColumnKeys;\r
142 import org.simantics.browsing.ui.common.ErrorLogger;\r
143 import org.simantics.browsing.ui.common.NodeContextBuilder;\r
144 import org.simantics.browsing.ui.common.NodeContextUtil;\r
145 import org.simantics.browsing.ui.common.internal.GENodeQueryManager;\r
146 import org.simantics.browsing.ui.common.internal.IGECache;\r
147 import org.simantics.browsing.ui.common.internal.IGraphExplorerContext;\r
148 import org.simantics.browsing.ui.common.internal.UIElementReference;\r
149 import org.simantics.browsing.ui.common.processors.AbstractPrimitiveQueryProcessor;\r
150 import org.simantics.browsing.ui.common.processors.DefaultCheckedStateProcessor;\r
151 import org.simantics.browsing.ui.common.processors.DefaultComparableChildrenProcessor;\r
152 import org.simantics.browsing.ui.common.processors.DefaultFinalChildrenProcessor;\r
153 import org.simantics.browsing.ui.common.processors.DefaultImageDecoratorProcessor;\r
154 import org.simantics.browsing.ui.common.processors.DefaultImagerFactoriesProcessor;\r
155 import org.simantics.browsing.ui.common.processors.DefaultImagerProcessor;\r
156 import org.simantics.browsing.ui.common.processors.DefaultLabelDecoratorProcessor;\r
157 import org.simantics.browsing.ui.common.processors.DefaultLabelerFactoriesProcessor;\r
158 import org.simantics.browsing.ui.common.processors.DefaultLabelerProcessor;\r
159 import org.simantics.browsing.ui.common.processors.DefaultPrunedChildrenProcessor;\r
160 import org.simantics.browsing.ui.common.processors.DefaultSelectedImageDecoratorFactoriesProcessor;\r
161 import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelDecoratorFactoriesProcessor;\r
162 import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelerProcessor;\r
163 import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointFactoryProcessor;\r
164 import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointProcessor;\r
165 import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionProcessor;\r
166 import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionsProcessor;\r
167 import org.simantics.browsing.ui.common.processors.DefaultViewpointProcessor;\r
168 import org.simantics.browsing.ui.common.processors.IsExpandedProcessor;\r
169 import org.simantics.browsing.ui.common.processors.NoSelectionRequestProcessor;\r
170 import org.simantics.browsing.ui.common.processors.ProcessorLifecycle;\r
171 import org.simantics.browsing.ui.content.Labeler;\r
172 import org.simantics.browsing.ui.content.Labeler.CustomModifier;\r
173 import org.simantics.browsing.ui.content.Labeler.DialogModifier;\r
174 import org.simantics.browsing.ui.content.Labeler.EnumerationModifier;\r
175 import org.simantics.browsing.ui.content.Labeler.Modifier;\r
176 import org.simantics.browsing.ui.nattable.override.DefaultTreeLayerConfiguration2;\r
177 import org.simantics.browsing.ui.swt.Activator;\r
178 import org.simantics.browsing.ui.swt.AdaptableHintContext;\r
179 import org.simantics.browsing.ui.swt.DefaultImageDecoratorsProcessor;\r
180 import org.simantics.browsing.ui.swt.DefaultIsExpandedProcessor;\r
181 import org.simantics.browsing.ui.swt.DefaultLabelDecoratorsProcessor;\r
182 import org.simantics.browsing.ui.swt.DefaultSelectedImagerProcessor;\r
183 import org.simantics.browsing.ui.swt.DefaultShowMaxChildrenProcessor;\r
184 import org.simantics.browsing.ui.swt.GraphExplorerImplBase;\r
185 import org.simantics.browsing.ui.swt.ImageLoaderJob;\r
186 import org.simantics.browsing.ui.swt.ViewerCellReference;\r
187 import org.simantics.browsing.ui.swt.ViewerRowReference;\r
188 import org.simantics.browsing.ui.swt.internal.Threads;\r
189 import org.simantics.db.layer0.SelectionHints;\r
190 import org.simantics.utils.datastructures.BinaryFunction;\r
191 import org.simantics.utils.datastructures.MapList;\r
192 import org.simantics.utils.datastructures.disposable.AbstractDisposable;\r
193 import org.simantics.utils.datastructures.hints.IHintContext;\r
194 import org.simantics.utils.threads.IThreadWorkQueue;\r
195 import org.simantics.utils.threads.SWTThread;\r
196 import org.simantics.utils.threads.ThreadUtils;\r
197 import org.simantics.utils.ui.AdaptionUtils;\r
198 import org.simantics.utils.ui.ISelectionUtils;\r
199 import org.simantics.utils.ui.jface.BasePostSelectionProvider;\r
200 \r
201 import gnu.trove.map.hash.THashMap;\r
202 import gnu.trove.map.hash.TObjectIntHashMap;\r
203 \r
204 /**\r
205  * NatTable base GraphExplorer\r
206  * \r
207  * \r
208  * FIXME : asynchronous node loading does not work properly + check expanded/collapsed sate handling\r
209  * TODO: InputValidators + input errors\r
210  * TODO: ability to hide headers\r
211  * TODO: code cleanup (copied from GraphExplorerImpl2) \r
212  * \r
213  * @author Marko Luukkainen <marko.luukkainen@vtt.fi>\r
214  *\r
215  */\r
216 public class NatTableGraphExplorer extends GraphExplorerImplBase implements GraphExplorer{\r
217     public static final int      DEFAULT_MAX_CHILDREN                    = 1000;\r
218         private static final boolean DEBUG_SELECTION_LISTENERS = false;\r
219         private static final boolean DEBUG = false;\r
220         \r
221         private Composite composite;\r
222         private NatTable natTable;\r
223         \r
224         private GETreeLayer treeLayer;\r
225         private DataLayer dataLayer;\r
226         private ViewportLayer viewportLayer;\r
227         private SelectionLayer selectionLayer;\r
228         private GEColumnHeaderDataProvider columnHeaderDataProvider;\r
229         private GEColumnAccessor columnAccessor;\r
230         private DefaultRowHeaderDataLayer rowHeaderDataLayer;\r
231         private DataLayer columnHeaderDataLayer;\r
232         private DataLayer cornerDataLayer;\r
233         \r
234         private List<TreeNode> list = new ArrayList<>();\r
235         \r
236         private NatTableSelectionAdaptor selectionAdaptor;\r
237         private NatTableColumnLayout layout;\r
238         \r
239         LocalResourceManager localResourceManager;\r
240         DeviceResourceManager resourceManager;\r
241         \r
242         \r
243         private IThreadWorkQueue thread;\r
244         \r
245         @SuppressWarnings({ "rawtypes" })\r
246         final HashMap<CacheKey<?>, NodeQueryProcessor> processors = new HashMap<CacheKey<?>, NodeQueryProcessor>();\r
247         @SuppressWarnings({ "rawtypes" })\r
248         final HashMap<Object, PrimitiveQueryProcessor> primitiveProcessors = new HashMap<Object, PrimitiveQueryProcessor>();\r
249         @SuppressWarnings({ "rawtypes" })\r
250         final HashMap<Class, DataSource> dataSources = new HashMap<Class, DataSource>();\r
251 \r
252         FontDescriptor originalFont;\r
253     protected ColorDescriptor originalForeground;\r
254     protected ColorDescriptor originalBackground;\r
255     private Color invalidModificationColor;\r
256     \r
257         private Column[] columns;\r
258         private Map<String,Integer> columnKeyToIndex;\r
259         private boolean columnsAreVisible = true;\r
260         \r
261         private NodeContext rootContext;\r
262         private TreeNode rootNode;\r
263         private StatePersistor persistor = null;\r
264 \r
265         private boolean editable = true;\r
266         \r
267         private boolean disposed = false;\r
268         \r
269         private final CopyOnWriteArrayList<FocusListener> focusListeners = new CopyOnWriteArrayList<FocusListener>();\r
270     private final CopyOnWriteArrayList<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();\r
271     private final CopyOnWriteArrayList<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>();\r
272         \r
273     private int autoExpandLevel = 0;\r
274     private IServiceLocator serviceLocator;\r
275     private IContextService contextService = null;\r
276     private IFocusService focusService = null;\r
277     private IContextActivation editingContext = null;\r
278         \r
279     GeViewerContext explorerContext = new GeViewerContext(this);\r
280     \r
281     private GraphExplorerPostSelectionProvider postSelectionProvider = new GraphExplorerPostSelectionProvider(this);\r
282     private BasePostSelectionProvider selectionProvider        = new BasePostSelectionProvider();\r
283     private SelectionDataResolver selectionDataResolver;\r
284     private SelectionFilter selectionFilter;\r
285     \r
286     private MapList<NodeContext, TreeNode> contextToNodeMap;\r
287     \r
288     private ModificationContext                          modificationContext = null;\r
289     \r
290     private boolean filterSelectionEdit = true;\r
291      \r
292     private boolean expand;\r
293     private boolean verticalBarVisible = false;\r
294     \r
295     private BinaryFunction<Object[], GraphExplorer, Object[]>  selectionTransformation = new BinaryFunction<Object[], GraphExplorer, Object[]>() {\r
296 \r
297         @Override\r
298         public Object[] call(GraphExplorer explorer, Object[] objects) {\r
299             Object[] result = new Object[objects.length];\r
300             for (int i = 0; i < objects.length; i++) {\r
301                 IHintContext context = new AdaptableHintContext(SelectionHints.KEY_MAIN);\r
302                 context.setHint(SelectionHints.KEY_MAIN, objects[i]);\r
303                 result[i] = context;\r
304             }\r
305             return result;\r
306         }\r
307 \r
308     };\r
309     \r
310     static class TransientStateImpl implements TransientExplorerState {\r
311 \r
312         private Integer activeColumn = null;\r
313         \r
314                 @Override\r
315                 public synchronized Integer getActiveColumn() {\r
316                         return activeColumn;\r
317                 }\r
318                 \r
319                 public synchronized void setActiveColumn(Integer column) {\r
320                         activeColumn = column;\r
321                 }\r
322         \r
323     }\r
324     \r
325     private TransientStateImpl transientState = new TransientStateImpl();\r
326     \r
327     public NatTableGraphExplorer(Composite parent) {\r
328         this(parent, SWT.BORDER | SWT.MULTI );\r
329     }\r
330     \r
331     public NatTableGraphExplorer(Composite parent, int style) {\r
332         this.composite = parent;\r
333         \r
334     \r
335         this.localResourceManager = new LocalResourceManager(JFaceResources.getResources());\r
336                 this.resourceManager = new DeviceResourceManager(parent.getDisplay());\r
337 \r
338                 this.imageLoaderJob = new ImageLoaderJob(this);\r
339                 this.imageLoaderJob.setPriority(Job.DECORATE);\r
340                 contextToNodeMap = new MapList<NodeContext, TreeNode>();\r
341                 \r
342                 invalidModificationColor = (Color) localResourceManager.get(ColorDescriptor.createFrom(new RGB(255, 128, 128)));\r
343 \r
344                 this.thread = SWTThread.getThreadAccess(parent);\r
345 \r
346                 for (int i = 0; i < 10; i++)\r
347                         explorerContext.activity.push(0);\r
348                 \r
349                 originalFont = JFaceResources.getDefaultFontDescriptor();\r
350 \r
351                 columns = new Column[0];\r
352                 createNatTable();\r
353                 layout = new NatTableColumnLayout(natTable, columnHeaderDataProvider, rowHeaderDataLayer);\r
354                 this.composite.setLayout(layout);\r
355                                 \r
356                 setBasicListeners();\r
357                 setDefaultProcessors();\r
358                 \r
359                 natTable.addDisposeListener(new DisposeListener() {\r
360                         \r
361                         @Override\r
362                         public void widgetDisposed(DisposeEvent e) {\r
363                                 doDispose();\r
364                                 \r
365                         }\r
366                 });\r
367                 \r
368                 Listener listener = new Listener() {\r
369                         \r
370                         @Override\r
371                         public void handleEvent(Event event) {\r
372                                 \r
373                                 switch (event.type) {\r
374                                         case SWT.Activate:\r
375                                         case SWT.Show:\r
376                                         case SWT.Paint:\r
377                                         {\r
378                                                 visible = true;\r
379                                                 activate();\r
380                                                 break;\r
381                                         }\r
382                                         case SWT.Deactivate:\r
383                                         case SWT.Hide:\r
384                                                 visible = false;\r
385                                 }\r
386                         }\r
387                 };\r
388                 \r
389                 natTable.addListener(SWT.Activate, listener);\r
390                 natTable.addListener(SWT.Deactivate, listener);\r
391                 natTable.addListener(SWT.Show, listener);\r
392                 natTable.addListener(SWT.Hide, listener);\r
393                 natTable.addListener(SWT.Paint,listener);\r
394                 \r
395                 setColumns( new Column[] { new Column(ColumnKeys.SINGLE) });\r
396                 \r
397     }\r
398     \r
399     private long focusGainedAt = 0L;\r
400         private boolean visible = false;\r
401         private Collection<TreeNode> selectedNodes = new ArrayList<TreeNode>();\r
402         \r
403         protected void setBasicListeners() {\r
404                 \r
405                 natTable.addFocusListener(new FocusListener() {\r
406                     @Override\r
407                     public void focusGained(FocusEvent e) {\r
408                         focusGainedAt = ((long) e.time) & 0xFFFFFFFFL;\r
409                         for (FocusListener listener : focusListeners)\r
410                             listener.focusGained(e);\r
411                     }\r
412                     @Override\r
413                     public void focusLost(FocusEvent e) {\r
414                         for (FocusListener listener : focusListeners)\r
415                             listener.focusLost(e);\r
416                     }\r
417                 });\r
418               natTable.addMouseListener(new MouseListener() {\r
419                     @Override\r
420                     public void mouseDoubleClick(MouseEvent e) {\r
421                         for (MouseListener listener : mouseListeners) {\r
422                             listener.mouseDoubleClick(e);\r
423                         }\r
424                     }\r
425                     @Override\r
426                     public void mouseDown(MouseEvent e) {\r
427                         for (MouseListener listener : mouseListeners) {\r
428                             listener.mouseDown(e);\r
429                         }\r
430                     }\r
431                     @Override\r
432                     public void mouseUp(MouseEvent e) {\r
433                         for (MouseListener listener : mouseListeners) {\r
434                             listener.mouseUp(e);\r
435                         }\r
436                     }\r
437                 });\r
438               natTable.addKeyListener(new KeyListener() {\r
439                     @Override\r
440                     public void keyPressed(KeyEvent e) {\r
441                         for (KeyListener listener : keyListeners) {\r
442                             listener.keyPressed(e);\r
443                         }\r
444                     }\r
445                     @Override\r
446                     public void keyReleased(KeyEvent e) {\r
447                         for (KeyListener listener : keyListeners) {\r
448                             listener.keyReleased(e);\r
449                         }\r
450                     }\r
451                 });\r
452                 \r
453               selectionAdaptor.addSelectionChangedListener(new ISelectionChangedListener() {\r
454                                 \r
455                                 @Override\r
456                                 public void selectionChanged(SelectionChangedEvent event) {\r
457                                         //System.out.println("GraphExplorerImpl2.fireSelection");\r
458                                         selectedNodes = AdaptionUtils.adaptToCollection(event.getSelection(), TreeNode.class);\r
459                                         Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);\r
460                                         selectionProvider.setAndFireSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));\r
461                                 }\r
462                         });\r
463                 \r
464               selectionAdaptor.addPostSelectionChangedListener(new ISelectionChangedListener() {\r
465                                 \r
466                                 @Override\r
467                                 public void selectionChanged(SelectionChangedEvent event) {\r
468                                         //System.out.println("GraphExplorerImpl2.firePostSelection");\r
469                                         Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);\r
470                                         selectionProvider.firePostSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));\r
471                                         \r
472                                 }\r
473                         });\r
474 \r
475         }\r
476         \r
477         private NodeContext pendingRoot;\r
478         \r
479         private void activate() {\r
480                 if (pendingRoot != null && !expand) {\r
481                         doSetRoot(pendingRoot);\r
482                         pendingRoot = null;\r
483                 }\r
484         }\r
485         \r
486           /**\r
487      * Invoke only from SWT thread to reset the root of the graph explorer tree.\r
488      * \r
489      * @param root\r
490      */\r
491     private void doSetRoot(NodeContext root) {\r
492         Display display = composite.getDisplay();\r
493                 if (display.getThread() != Thread.currentThread()) {\r
494                         throw new RuntimeException("Invoke from SWT thread only");\r
495                 }\r
496 //      System.out.println("doSetRoot " + root);\r
497         if (isDisposed())\r
498             return;\r
499         if (natTable.isDisposed())\r
500                 return;\r
501         if (root.getConstant(BuiltinKeys.INPUT) == null) {\r
502             ErrorLogger.defaultLogError("root node context does not contain BuiltinKeys.INPUT key. Node = " + root, new Exception("trace"));\r
503             return;\r
504         }\r
505         \r
506         \r
507 \r
508         // Empty caches, release queries.\r
509         if (rootNode != null) {\r
510                 rootNode.dispose();\r
511         }       \r
512         GeViewerContext oldContext = explorerContext;\r
513         GeViewerContext newContext = new GeViewerContext(this);\r
514         this.explorerContext = newContext;\r
515         oldContext.safeDispose();\r
516 \r
517         // Need to empty these or otherwise they won't be emptied until the\r
518         // explorer is disposed which would mean that many unwanted references\r
519         // will be held by this map.\r
520         clearPrimitiveProcessors();\r
521 \r
522         this.rootContext = root.getConstant(BuiltinKeys.IS_ROOT) != null ? root\r
523                 : NodeContextUtil.withConstant(root, BuiltinKeys.IS_ROOT, Boolean.TRUE);\r
524 \r
525         explorerContext.getCache().incRef(this.rootContext);\r
526 \r
527         initializeState();\r
528         \r
529         \r
530         select(rootContext);\r
531         //refreshColumnSizes();\r
532         rootNode = new TreeNode(rootContext, explorerContext);\r
533         if (DEBUG) System.out.println("setRoot " + rootNode);\r
534       \r
535        // viewer.setInput(rootNode);\r
536         \r
537         // Delay content reading.\r
538         \r
539         // This is required for cases when GEImpl2 is attached to selection view. Reading content\r
540         // instantly could stagnate SWT thread under rapid changes in selection. By delaying the \r
541         // content reading we give the system a change to dispose the GEImpl2 before the content is read.\r
542         display.asyncExec(new Runnable() {\r
543                         \r
544                         @Override\r
545                         public void run() {\r
546                                 if (rootNode != null) {\r
547                                     rootNode.updateChildren();\r
548                                     listReIndex();\r
549                                     natTable.refresh(true);\r
550                                 }\r
551                         }\r
552                 });\r
553        \r
554     }\r
555     \r
556     private synchronized void listReIndex() {\r
557         list.clear();\r
558         for (TreeNode c : rootNode.getChildren())\r
559                 _insertToList(c);\r
560     }\r
561     \r
562     private void _insertToList(TreeNode n) {\r
563         n.setListIndex(list.size());\r
564         list.add(n);\r
565         for (TreeNode c : n.getChildren()) {\r
566                 _insertToList(c);\r
567         }\r
568     }\r
569     \r
570     private void initializeState() {\r
571         if (persistor == null)\r
572             return;\r
573 \r
574         ExplorerState state = persistor.deserialize(\r
575                 Platform.getStateLocation(Activator.getDefault().getBundle()).toFile(),\r
576                 getRoot());\r
577 \r
578 \r
579         Object processor = getPrimitiveProcessor(BuiltinKeys.IS_EXPANDED);\r
580         if (processor instanceof DefaultIsExpandedProcessor) {\r
581             DefaultIsExpandedProcessor isExpandedProcessor = (DefaultIsExpandedProcessor)processor;\r
582             for(NodeContext expanded : state.expandedNodes) {\r
583                 isExpandedProcessor.setExpanded(expanded, true);\r
584             }\r
585         }\r
586     }\r
587 \r
588     @Override\r
589     public NodeContext getRoot() {\r
590         return rootContext;\r
591     }\r
592     \r
593     @Override\r
594     public IThreadWorkQueue getThread() {\r
595         return thread;\r
596     }\r
597 \r
598     @Override\r
599     public NodeContext getParentContext(NodeContext context) {\r
600         if (disposed)\r
601             throw new IllegalStateException("disposed");\r
602         if (!thread.currentThreadAccess())\r
603             throw new IllegalStateException("not in SWT display thread " + thread.getThread());\r
604 \r
605         List<TreeNode> nodes = contextToNodeMap.getValuesUnsafe(context);\r
606         for (int i = 0; i < nodes.size(); i++) {\r
607                 if (nodes.get(i).getParent() != null)\r
608                         return nodes.get(i).getParent().getContext();\r
609         }\r
610         return null;\r
611         \r
612     }\r
613     \r
614     \r
615     @SuppressWarnings("unchecked")\r
616     @Override\r
617     public <T> T getAdapter(Class<T> adapter) {\r
618         if(ISelectionProvider.class == adapter) return (T) postSelectionProvider;\r
619         else if(IPostSelectionProvider.class == adapter) return (T) postSelectionProvider;\r
620         return null;\r
621     }\r
622 \r
623         \r
624         protected void setDefaultProcessors() {\r
625                 // Add a simple IMAGER query processor that always returns null.\r
626                 // With this processor no images will ever be shown.\r
627                 // setPrimitiveProcessor(new StaticImagerProcessor(null));\r
628 \r
629                 setProcessor(new DefaultComparableChildrenProcessor());\r
630                 setProcessor(new DefaultLabelDecoratorsProcessor());\r
631                 setProcessor(new DefaultImageDecoratorsProcessor());\r
632                 setProcessor(new DefaultSelectedLabelerProcessor());\r
633                 setProcessor(new DefaultLabelerFactoriesProcessor());\r
634                 setProcessor(new DefaultSelectedImagerProcessor());\r
635                 setProcessor(new DefaultImagerFactoriesProcessor());\r
636                 setPrimitiveProcessor(new DefaultLabelerProcessor());\r
637                 setPrimitiveProcessor(new DefaultCheckedStateProcessor());\r
638                 setPrimitiveProcessor(new DefaultImagerProcessor());\r
639                 setPrimitiveProcessor(new DefaultLabelDecoratorProcessor());\r
640                 setPrimitiveProcessor(new DefaultImageDecoratorProcessor());\r
641                 setPrimitiveProcessor(new NoSelectionRequestProcessor());\r
642 \r
643                 setProcessor(new DefaultFinalChildrenProcessor(this));\r
644 \r
645                 setProcessor(new DefaultPrunedChildrenProcessor());\r
646                 setProcessor(new DefaultSelectedViewpointProcessor());\r
647                 setProcessor(new DefaultSelectedLabelDecoratorFactoriesProcessor());\r
648                 setProcessor(new DefaultSelectedImageDecoratorFactoriesProcessor());\r
649                 setProcessor(new DefaultViewpointContributionsProcessor());\r
650 \r
651                 setPrimitiveProcessor(new DefaultViewpointProcessor());\r
652                 setPrimitiveProcessor(new DefaultViewpointContributionProcessor());\r
653                 setPrimitiveProcessor(new DefaultSelectedViewpointFactoryProcessor());\r
654                 setPrimitiveProcessor(new TreeNodeIsExpandedProcessor());\r
655                 setPrimitiveProcessor(new DefaultShowMaxChildrenProcessor());\r
656         }\r
657         \r
658         @Override\r
659     public Column[] getColumns() {\r
660         return Arrays.copyOf(columns, columns.length);\r
661     }\r
662         \r
663     @Override\r
664     public void setColumnsVisible(boolean visible) {\r
665         columnsAreVisible = visible;\r
666        //FIXME if(natTable != null) this.columnHeaderDataLayer.setHeaderVisible(columnsAreVisible);\r
667     }\r
668 \r
669     @Override\r
670     public void setColumns(final Column[] columns) {\r
671         setColumns(columns, null);\r
672     }\r
673 \r
674     @Override\r
675     public void setColumns(final Column[] columns, Consumer<Map<Column, Object>> callback) {\r
676         assertNotDisposed();\r
677         checkUniqueColumnKeys(columns);\r
678 \r
679         Display d = composite.getDisplay();\r
680         if (d.getThread() == Thread.currentThread()) {\r
681             doSetColumns(columns, callback);\r
682             natTable.refresh(true);\r
683          }else\r
684             d.asyncExec(new Runnable() {\r
685                 @Override\r
686                 public void run() {\r
687                         if (natTable == null)\r
688                                 return;\r
689                     if (natTable.isDisposed())\r
690                         return;\r
691                     doSetColumns(columns, callback);\r
692                     natTable.refresh(true);\r
693                     natTable.getParent().layout();\r
694                 }\r
695             });\r
696     }\r
697     \r
698     private void checkUniqueColumnKeys(Column[] cols) {\r
699         Set<String> usedColumnKeys = new HashSet<String>();\r
700         List<Column> duplicateColumns = new ArrayList<Column>();\r
701         for (Column c : cols) {\r
702             if (!usedColumnKeys.add(c.getKey()))\r
703                 duplicateColumns.add(c);\r
704         }\r
705         if (!duplicateColumns.isEmpty()) {\r
706             throw new IllegalArgumentException("All columns do not have unique keys: " + cols + ", overlapping: " + duplicateColumns);\r
707         }\r
708     }\r
709     \r
710     private void doSetColumns(Column[] cols, Consumer<Map<Column, Object>> callback) {\r
711 \r
712         HashMap<String, Integer> keyToIndex = new HashMap<String, Integer>();\r
713         for (int i = 0; i < cols.length; ++i) {\r
714             keyToIndex.put(cols[i].getKey(), i);\r
715         }\r
716 \r
717         this.columns = Arrays.copyOf(cols, cols.length);\r
718         //this.columns[cols.length] = FILLER_COLUMN;\r
719         this.columnKeyToIndex = keyToIndex;\r
720         \r
721         columnHeaderDataProvider.updateColumnSizes();\r
722 \r
723         Map<Column, Object> map = new HashMap<Column, Object>();\r
724 \r
725         // FIXME : temporary workaround for ModelBrowser.\r
726 //        natTable.setHeaderVisible(columns.length == 1 ? false : columnsAreVisible);\r
727         \r
728         int columnIndex = 0;\r
729 \r
730         for (Column column : columns) {\r
731                 int width = column.getWidth();\r
732                 if(column.hasGrab()) {\r
733                         if (width < 0)\r
734                                 width = 1;\r
735                layout.setColumnData(columnIndex, new ColumnWeightData(column.getWeight(), width));\r
736 \r
737             } else {\r
738                 if (width < 0)\r
739                         width = 50;\r
740                 layout.setColumnData(columnIndex, new ColumnWeightData(columns.length > 1 ? 0 : 1, width));\r
741 \r
742             }\r
743             columnIndex++;\r
744         }\r
745        \r
746        \r
747 \r
748         if(callback != null) callback.accept(map);\r
749     }\r
750     \r
751     int toSWT(Align alignment) {\r
752         switch (alignment) {\r
753             case LEFT: return SWT.LEFT;\r
754             case CENTER: return SWT.CENTER;\r
755             case RIGHT: return SWT.RIGHT;\r
756             default: throw new Error("unhandled alignment: " + alignment);\r
757         }\r
758     }\r
759 \r
760         @Override\r
761         public <T> void setProcessor(NodeQueryProcessor<T> processor) {\r
762                 assertNotDisposed();\r
763                 if (processor == null)\r
764                         throw new IllegalArgumentException("null processor");\r
765 \r
766                 processors.put(processor.getIdentifier(), processor);\r
767         }\r
768 \r
769         @Override\r
770         public <T> void setPrimitiveProcessor(PrimitiveQueryProcessor<T> processor) {\r
771                 assertNotDisposed();\r
772                 if (processor == null)\r
773                         throw new IllegalArgumentException("null processor");\r
774 \r
775                 PrimitiveQueryProcessor<?> oldProcessor = primitiveProcessors.put(\r
776                                 processor.getIdentifier(), processor);\r
777 \r
778                 if (oldProcessor instanceof ProcessorLifecycle)\r
779                         ((ProcessorLifecycle) oldProcessor).detached(this);\r
780                 if (processor instanceof ProcessorLifecycle)\r
781                         ((ProcessorLifecycle) processor).attached(this);\r
782         }\r
783 \r
784         @Override\r
785         public <T> void setDataSource(DataSource<T> provider) {\r
786                 assertNotDisposed();\r
787                 if (provider == null)\r
788                         throw new IllegalArgumentException("null provider");\r
789                 dataSources.put(provider.getProvidedClass(), provider);\r
790         }\r
791 \r
792         @SuppressWarnings("unchecked")\r
793         @Override\r
794         public <T> DataSource<T> removeDataSource(Class<T> forProvidedClass) {\r
795                 assertNotDisposed();\r
796                 if (forProvidedClass == null)\r
797                         throw new IllegalArgumentException("null class");\r
798                 return dataSources.remove(forProvidedClass);\r
799         }\r
800 \r
801         @Override\r
802         public void setPersistor(StatePersistor persistor) {\r
803                 this.persistor = persistor;\r
804         }\r
805 \r
806         @Override\r
807         public SelectionDataResolver getSelectionDataResolver() {\r
808                 return selectionDataResolver;\r
809         }\r
810 \r
811         @Override\r
812         public void setSelectionDataResolver(SelectionDataResolver r) {\r
813                 this.selectionDataResolver = r;\r
814         }\r
815 \r
816         @Override\r
817         public SelectionFilter getSelectionFilter() {\r
818                 return selectionFilter;\r
819         }\r
820 \r
821         @Override\r
822         public void setSelectionFilter(SelectionFilter f) {\r
823                 this.selectionFilter = f;\r
824                 // TODO: re-filter current selection?\r
825         }\r
826         \r
827     protected ISelection constructSelection(NodeContext... contexts) {\r
828         if (contexts ==  null)\r
829             throw new IllegalArgumentException("null contexts");\r
830         if (contexts.length == 0)\r
831             return StructuredSelection.EMPTY;\r
832         if (selectionFilter == null)\r
833             return new StructuredSelection(transformSelection(contexts));\r
834         return new StructuredSelection( transformSelection(filter(selectionFilter, contexts)) );\r
835     }\r
836     \r
837     protected Object[] transformSelection(Object[] objects) {\r
838         return selectionTransformation.call(this, objects);\r
839     }\r
840     \r
841     protected static Object[] filter(SelectionFilter filter, NodeContext[] contexts) {\r
842         int len = contexts.length;\r
843         Object[] objects = new Object[len];\r
844         for (int i = 0; i < len; ++i)\r
845             objects[i] = filter.filter(contexts[i]);\r
846         return objects;\r
847     }\r
848 \r
849         @Override\r
850         public void setSelectionTransformation(\r
851                         BinaryFunction<Object[], GraphExplorer, Object[]> f) {\r
852                 this.selectionTransformation = f;\r
853         }\r
854         \r
855         public ISelection getWidgetSelection() {\r
856                 return selectionAdaptor.getSelection();\r
857         }\r
858 \r
859         @Override\r
860         public <T> void addListener(T listener) {\r
861                 if (listener instanceof FocusListener) {\r
862                         focusListeners.add((FocusListener) listener);\r
863                 } else if (listener instanceof MouseListener) {\r
864                         mouseListeners.add((MouseListener) listener);\r
865                 } else if (listener instanceof KeyListener) {\r
866                         keyListeners.add((KeyListener) listener);\r
867                 }\r
868         }\r
869 \r
870         @Override\r
871         public <T> void removeListener(T listener) {\r
872                 if (listener instanceof FocusListener) {\r
873                         focusListeners.remove(listener);\r
874                 } else if (listener instanceof MouseListener) {\r
875                         mouseListeners.remove(listener);\r
876                 } else if (listener instanceof KeyListener) {\r
877                         keyListeners.remove(listener);\r
878                 }\r
879         }\r
880 \r
881         public void addSelectionListener(SelectionListener listener) {\r
882                 selectionAdaptor.addSelectionListener(listener);\r
883         }\r
884 \r
885         public void removeSelectionListener(SelectionListener listener) {\r
886                 selectionAdaptor.removeSelectionListener(listener);\r
887         }\r
888 \r
889     private Set<String> uiContexts;\r
890     \r
891     @Override\r
892     public void setUIContexts(Set<String> contexts) {\r
893         this.uiContexts = contexts;\r
894     }\r
895         \r
896         @Override\r
897         public void setRoot(final Object root) {\r
898         if(uiContexts != null && uiContexts.size() == 1)\r
899                 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root, BuiltinKeys.UI_CONTEXT, uiContexts.iterator().next()));\r
900         else\r
901                 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root));\r
902         }\r
903 \r
904         @Override\r
905         public void setRootContext(final NodeContext context) {\r
906                 setRootContext0(context);\r
907         }\r
908         \r
909         private void setRoot(NodeContext context) {\r
910                 if (!visible) {\r
911                         pendingRoot = context;\r
912                         Display.getDefault().asyncExec(new Runnable() {\r
913                                 @Override\r
914                                 public void run() {\r
915                                         if (natTable!= null && !natTable.isDisposed())\r
916                                                 natTable.redraw();\r
917                                 }\r
918                         });\r
919                         return;\r
920         }\r
921                 doSetRoot(context);\r
922         }\r
923 \r
924         private void setRootContext0(final NodeContext context) {\r
925                 Assert.isNotNull(context, "root must not be null");\r
926                 if (isDisposed() || natTable.isDisposed())\r
927                         return;\r
928                 Display display = natTable.getDisplay();\r
929                 if (display.getThread() == Thread.currentThread()) {\r
930                         setRoot(context);\r
931                 } else {\r
932                         display.asyncExec(new Runnable() {\r
933                                 @Override\r
934                                 public void run() {\r
935                                         setRoot(context);\r
936                                 }\r
937                         });\r
938                 }\r
939         }\r
940         \r
941         @Override\r
942         public void setFocus() {\r
943                 natTable.setFocus();\r
944         }\r
945         \r
946         @SuppressWarnings("unchecked")\r
947         @Override\r
948         public <T> T getControl() {\r
949                 return (T)natTable;\r
950         }\r
951         \r
952             \r
953     @Override\r
954     public boolean isDisposed() {\r
955         return disposed;\r
956     }\r
957 \r
958     protected void assertNotDisposed() {\r
959         if (isDisposed())\r
960             throw new IllegalStateException("disposed");\r
961     }\r
962     \r
963         @Override\r
964         public boolean isEditable() {\r
965                 return editable;\r
966         }\r
967 \r
968         @Override\r
969         public void setEditable(boolean editable) {\r
970                 if (!thread.currentThreadAccess())\r
971                         throw new IllegalStateException("not in SWT display thread " + thread.getThread());\r
972 \r
973                 this.editable = editable;\r
974                 Display display = natTable.getDisplay();\r
975                 natTable.setBackground(editable ? null : display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));\r
976         }\r
977         \r
978     private void doDispose() {\r
979         if (disposed)\r
980                 return;\r
981         disposed = true;\r
982        // TODO: Since GENodeQueryManager is cached in QueryChache and it refers to this class\r
983        //       we have to remove all references here to reduce memory consumption.\r
984        //       \r
985        //       Proper fix would be to remove references between QueryCache and GENodeQueryManagers.\r
986         if (rootNode != null) {\r
987                 rootNode.dispose();\r
988                 rootNode = null;        \r
989         }       \r
990         explorerContext.dispose();\r
991         explorerContext = null;\r
992         processors.clear();\r
993         detachPrimitiveProcessors();\r
994         primitiveProcessors.clear();\r
995         dataSources.clear();      \r
996         pendingItems.clear();\r
997         rootContext = null;\r
998         mouseListeners.clear();\r
999         selectionProvider.clearListeners();\r
1000         selectionProvider = null;\r
1001         selectionDataResolver = null;\r
1002         selectedNodes.clear();\r
1003         selectedNodes = null;\r
1004         selectionTransformation = null;\r
1005         originalFont = null;\r
1006         localResourceManager.dispose();\r
1007         localResourceManager = null;\r
1008         // Must shutdown image loader job before disposing its ResourceManager\r
1009         imageLoaderJob.dispose();\r
1010         imageLoaderJob.cancel();\r
1011         try {\r
1012             imageLoaderJob.join();\r
1013             imageLoaderJob = null;\r
1014         } catch (InterruptedException e) {\r
1015             ErrorLogger.defaultLogError(e);\r
1016         }\r
1017         resourceManager.dispose();\r
1018         resourceManager = null;\r
1019                 \r
1020         contextToNodeMap.clear(); // should be empty at this point.\r
1021         contextToNodeMap = null;\r
1022         if (postSelectionProvider != null) {\r
1023                 postSelectionProvider.dispose();\r
1024                 postSelectionProvider = null;\r
1025         }\r
1026         imageTasks = null;\r
1027         modificationContext = null;\r
1028         focusService = null;\r
1029         contextService = null;\r
1030         serviceLocator = null;\r
1031         columns = null;\r
1032         columnKeyToIndex.clear();\r
1033         columnKeyToIndex = null;\r
1034 //        if (natTable != null) {\r
1035 //                      natTable.dispose();\r
1036 //                      natTable = null;\r
1037 //              }\r
1038                 treeLayer = null;\r
1039                 dataLayer = null;\r
1040                 viewportLayer = null;\r
1041                 selectionLayer = null;\r
1042                 columnHeaderDataProvider = null;\r
1043                 columnAccessor = null;\r
1044                 rowHeaderDataLayer = null;\r
1045                 columnHeaderDataLayer = null;\r
1046                 cornerDataLayer = null;\r
1047 \r
1048     }\r
1049     \r
1050     @Override\r
1051     public boolean select(NodeContext context) {\r
1052 \r
1053         assertNotDisposed();\r
1054 \r
1055         if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) {\r
1056                 StructuredSelection s = new StructuredSelection();\r
1057             selectionAdaptor.setSelection(s);\r
1058             selectionProvider.setAndFireNonEqualSelection(s);\r
1059             return true;\r
1060         }\r
1061 \r
1062         selectionAdaptor.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0)));\r
1063         \r
1064         return false;\r
1065         \r
1066     }\r
1067     \r
1068     @Override\r
1069     public boolean selectPath(Collection<NodeContext> contexts) {\r
1070         \r
1071         if(contexts == null) throw new IllegalArgumentException("Null list is not allowed");\r
1072         if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed");\r
1073         \r
1074         return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0);\r
1075         \r
1076     }\r
1077     \r
1078     private boolean selectPathInternal(NodeContext[] contexts, int position) {\r
1079 \r
1080         NodeContext head = contexts[position];\r
1081 \r
1082         if(position == contexts.length-1) {\r
1083                 return select(head);\r
1084                 \r
1085         }\r
1086 \r
1087         setExpanded(head, true);\r
1088         if(!waitVisible(contexts[position+1])) return false;\r
1089         \r
1090         return selectPathInternal(contexts, position+1);\r
1091         \r
1092     }\r
1093     \r
1094     private boolean waitVisible(NodeContext context) {\r
1095         long start = System.nanoTime();\r
1096         while(!isVisible(context)) {\r
1097                 Display.getCurrent().readAndDispatch();\r
1098                 long duration = System.nanoTime() - start;\r
1099                 if(duration > 10e9) return false;\r
1100         }\r
1101         return true;\r
1102     }\r
1103     \r
1104     @Override\r
1105     public boolean isVisible(NodeContext context) {\r
1106         if (contextToNodeMap.getValuesUnsafe(context).size() == 0)\r
1107                 return false;\r
1108         \r
1109         return true; //FIXME\r
1110 //        Object elements[] = viewer.getVisibleExpandedElements();\r
1111 //        return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0));\r
1112         \r
1113         \r
1114     }\r
1115     \r
1116     @Override\r
1117     public TransientExplorerState getTransientState() {\r
1118         if (!thread.currentThreadAccess())\r
1119             throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread());\r
1120         return transientState;\r
1121     }\r
1122     \r
1123     @Override\r
1124     public <T> T query(NodeContext context, CacheKey<T> key) {\r
1125         return this.explorerContext.cache.get(context, key);\r
1126     }\r
1127     \r
1128     /**\r
1129      * For setting a more local service locator for the explorer than the global\r
1130      * workbench service locator. Sometimes required to give this implementation\r
1131      * access to local workbench services like IFocusService.\r
1132      * \r
1133      * <p>\r
1134      * Must be invoked during right after construction.\r
1135      * \r
1136      * @param serviceLocator\r
1137      *            a specific service locator or <code>null</code> to use the\r
1138      *            workbench global service locator\r
1139      */\r
1140     public void setServiceLocator(IServiceLocator serviceLocator) {\r
1141         if (serviceLocator == null && PlatformUI.isWorkbenchRunning())\r
1142             serviceLocator = PlatformUI.getWorkbench();\r
1143         this.serviceLocator = serviceLocator;\r
1144         if (serviceLocator != null) {\r
1145             this.contextService = (IContextService) serviceLocator.getService(IContextService.class);\r
1146             this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class);\r
1147         }\r
1148     }\r
1149     \r
1150     private void detachPrimitiveProcessors() {\r
1151         for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {\r
1152             if (p instanceof ProcessorLifecycle) {\r
1153                 ((ProcessorLifecycle) p).detached(this);\r
1154             }\r
1155         }\r
1156     }\r
1157 \r
1158     private void clearPrimitiveProcessors() {\r
1159         for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {\r
1160             if (p instanceof ProcessorLifecycle) {\r
1161                 ((ProcessorLifecycle) p).clear();\r
1162             }\r
1163         }\r
1164     }\r
1165     \r
1166     @Override\r
1167     public void setExpanded(NodeContext context, boolean expanded) {\r
1168         for (TreeNode n : contextToNodeMap.getValues(context)) {\r
1169                 if (expanded)\r
1170                         treeLayer.expandTreeRow(n.getListIndex());\r
1171                 else\r
1172                         treeLayer.collapseTreeRow(n.getListIndex());\r
1173         }\r
1174         //viewer.setExpandedState(context, expanded);\r
1175         \r
1176     }\r
1177     \r
1178     @Override\r
1179     public void setAutoExpandLevel(int level) {\r
1180         this.autoExpandLevel = level;\r
1181         treeLayer.expandAllToLevel(level);\r
1182         //viewer.setAutoExpandLevel(level);\r
1183     }\r
1184     \r
1185     int maxChildren = DEFAULT_MAX_CHILDREN;\r
1186     \r
1187     @Override\r
1188     public int getMaxChildren() {\r
1189         return maxChildren;\r
1190     }\r
1191     \r
1192     @Override\r
1193     public void setMaxChildren(int maxChildren) {\r
1194         this.maxChildren = maxChildren;\r
1195         \r
1196     }\r
1197     \r
1198     @Override\r
1199     public int getMaxChildren(NodeQueryManager manager, NodeContext context) {\r
1200         Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN);\r
1201         //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result);\r
1202         if (result != null) {\r
1203             if (result < 0)\r
1204                 throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result);\r
1205             return result;\r
1206         }\r
1207         return maxChildren;\r
1208     }\r
1209     \r
1210     @Override\r
1211     public <T> NodeQueryProcessor<T> getProcessor(QueryKey<T> key) {\r
1212         return explorerContext.getProcessor(key);\r
1213     }\r
1214 \r
1215     @Override\r
1216     public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(PrimitiveQueryKey<T> key) {\r
1217         return explorerContext.getPrimitiveProcessor(key);\r
1218     }\r
1219     \r
1220     private HashSet<UpdateItem>                            pendingItems        = new HashSet<UpdateItem>();\r
1221     private boolean updating = false;\r
1222     private int updateCounter = 0;\r
1223     final ScheduledExecutorService               uiUpdateScheduler    = ThreadUtils.getNonBlockingWorkExecutor();\r
1224     \r
1225     private class UpdateItem {\r
1226         TreeNode element;\r
1227         int columnIndex;\r
1228         \r
1229         public UpdateItem(TreeNode element) {\r
1230                 this(element,-1);\r
1231         }\r
1232         \r
1233         public UpdateItem(TreeNode element, int columnIndex) {\r
1234                 this.element = element;\r
1235                 this.columnIndex = columnIndex;\r
1236                 if (element != null && element.isDisposed()) {\r
1237                         throw new IllegalArgumentException("Node is disposed. " + element);\r
1238                 }\r
1239         }\r
1240         \r
1241         public void update(NatTable natTable) {\r
1242                 if (element != null) {\r
1243 \r
1244                                 if (element.isDisposed()) {\r
1245                                 return;\r
1246                                 }\r
1247                         if (((TreeNode)element).updateChildren()) {\r
1248                                 listReIndex();\r
1249                                 natTable.refresh(true);\r
1250                                 //viewer.refresh(element,true);\r
1251                         } else {\r
1252                                 if (columnIndex >= 0) {\r
1253                                         natTable.redraw();\r
1254                                         //viewer.update(element, new String[]{columns[columnIndex].getKey()});\r
1255                                 } else {\r
1256                                         natTable.redraw();\r
1257                                         //viewer.refresh(element,true);\r
1258                                 }\r
1259                         }\r
1260                         \r
1261                         if (!element.isDisposed() && autoExpandLevel > 1 && !element.isExpanded() && element.getDepth() <= autoExpandLevel) {\r
1262                                 expand = true;\r
1263                                 treeLayer.expandTreeRow(element.getListIndex());\r
1264                                 //viewer.setExpandedState(element, true);\r
1265                                 expand = false;\r
1266                         }\r
1267                         } else {\r
1268                                 if (rootNode.updateChildren()) {\r
1269                                         listReIndex();\r
1270                                 natTable.refresh(true);\r
1271                                         //viewer.refresh(rootNode,true);\r
1272                                 }\r
1273                         }\r
1274         }\r
1275         \r
1276         @Override\r
1277         public boolean equals(Object obj) {\r
1278                 if (obj == null)\r
1279                         return false;\r
1280                 if (obj.getClass() != getClass())\r
1281                         return false;\r
1282                 UpdateItem other = (UpdateItem)obj;\r
1283                 if (columnIndex != other.columnIndex)\r
1284                         return false;\r
1285                 if (element != null)\r
1286                         return element.equals(other.element);\r
1287                 return other.element == null;\r
1288         }\r
1289         \r
1290         @Override\r
1291         public int hashCode() {\r
1292                 if (element != null)\r
1293                         return element.hashCode() + columnIndex;\r
1294                 return 0;\r
1295         }\r
1296     }\r
1297     \r
1298     private void update(final TreeNode element, final int columnIndex) {\r
1299         if (DEBUG)System.out.println("update " + element + " " + columnIndex);\r
1300         if (natTable.isDisposed())\r
1301                 return;\r
1302         synchronized (pendingItems) {\r
1303                         pendingItems.add(new UpdateItem(element, columnIndex));\r
1304                         if (updating) return;\r
1305                         updateCounter++;\r
1306                         scheduleUpdater();\r
1307                 }\r
1308     }\r
1309 \r
1310     private void update(final TreeNode element) {\r
1311         if (DEBUG)System.out.println("update " + element);\r
1312         if (natTable.isDisposed())\r
1313                 return;\r
1314         if (element != null && element.isDisposed())\r
1315                 return;\r
1316         synchronized (pendingItems) {\r
1317                 \r
1318                         pendingItems.add(new UpdateItem(element));\r
1319                         if (updating) return;\r
1320                         updateCounter++;\r
1321                         scheduleUpdater();\r
1322                 }\r
1323     }\r
1324     \r
1325     boolean scheduleUpdater() {\r
1326 \r
1327         if (natTable.isDisposed())\r
1328             return false;\r
1329 \r
1330         if (!pendingItems.isEmpty()) {\r
1331             \r
1332             int activity = explorerContext.activityInt;\r
1333             long delay = 30;\r
1334             if (activity < 100) {\r
1335                 //System.out.println("Scheduling update immediately.");\r
1336             } else if (activity < 1000) {\r
1337                 //System.out.println("Scheduling update after 500ms.");\r
1338                 delay = 500;\r
1339             } else {\r
1340                 //System.out.println("Scheduling update after 3000ms.");\r
1341                 delay = 3000;\r
1342             }\r
1343 \r
1344             updateCounter = 0;\r
1345             \r
1346             //System.out.println("Scheduling UI update after " + delay + " ms.");\r
1347             uiUpdateScheduler.schedule(new Runnable() {\r
1348                 @Override\r
1349                 public void run() {\r
1350                         \r
1351                     if (natTable == null || natTable.isDisposed())\r
1352                         return;\r
1353                     \r
1354                     if (updateCounter > 0) {\r
1355                         updateCounter = 0;\r
1356                         uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS);\r
1357                     } else {\r
1358                         natTable.getDisplay().asyncExec(new UpdateRunner(NatTableGraphExplorer.this, NatTableGraphExplorer.this.explorerContext));\r
1359                     }\r
1360                     \r
1361                 }\r
1362             }, delay, TimeUnit.MILLISECONDS);\r
1363 \r
1364             updating = true;\r
1365             return true;\r
1366         }\r
1367 \r
1368         return false;\r
1369     }\r
1370     \r
1371     @Override\r
1372     public String startEditing(NodeContext context, String columnKey) {\r
1373         assertNotDisposed();\r
1374         if (!thread.currentThreadAccess())\r
1375             throw new IllegalStateException("not in SWT display thread " + thread.getThread());\r
1376 \r
1377         if(columnKey.startsWith("#")) {\r
1378                 columnKey = columnKey.substring(1);\r
1379         }\r
1380 \r
1381         Integer columnIndex = columnKeyToIndex.get(columnKey);\r
1382         if (columnIndex == null)\r
1383             return "Rename not supported for selection";\r
1384 // FIXME:\r
1385 //        viewer.editElement(context, columnIndex);\r
1386 //        if(viewer.isCellEditorActive()) return null;\r
1387         return "Rename not supported for selection";\r
1388     }\r
1389 \r
1390     @Override\r
1391     public String startEditing(String columnKey) {\r
1392         ISelection selection = postSelectionProvider.getSelection();\r
1393         if(selection == null) return "Rename not supported for selection";\r
1394         NodeContext context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class);\r
1395         if(context == null) return "Rename not supported for selection";\r
1396 \r
1397         return startEditing(context, columnKey);\r
1398 \r
1399     }\r
1400     \r
1401     public void setSelection(final ISelection selection, boolean forceControlUpdate) {\r
1402         assertNotDisposed();\r
1403         boolean equalsOld = selectionProvider.selectionEquals(selection);\r
1404         if (equalsOld && !forceControlUpdate) {\r
1405             // Just set the selection object instance, fire no events nor update\r
1406             // the viewer selection.\r
1407             selectionProvider.setSelection(selection);\r
1408         } else {\r
1409                 Collection<NodeContext> coll =  AdaptionUtils.adaptToCollection(selection, NodeContext.class);\r
1410                 Collection<TreeNode> nodes = new ArrayList<TreeNode>();\r
1411                 for (NodeContext c : coll) {\r
1412                         List<TreeNode> match = contextToNodeMap.getValuesUnsafe(c);\r
1413                         if(match.size() > 0)\r
1414                                 nodes.add(match.get(0));\r
1415                 }\r
1416                 final ISelection sel = new StructuredSelection(nodes.toArray());\r
1417                 if (coll.size() == 0)\r
1418                         return;\r
1419             // Schedule viewer and selection update if necessary.\r
1420             if (natTable.isDisposed())\r
1421                 return;\r
1422             Display d = natTable.getDisplay();\r
1423             if (d.getThread() == Thread.currentThread()) {\r
1424                 selectionAdaptor.setSelection(sel);\r
1425             } else {\r
1426                 d.asyncExec(new Runnable() {\r
1427                     @Override\r
1428                     public void run() {\r
1429                         if (natTable.isDisposed())\r
1430                             return;\r
1431                         selectionAdaptor.setSelection(sel);\r
1432                     }\r
1433                 });\r
1434             }\r
1435         }\r
1436     }\r
1437     \r
1438     @Override\r
1439     public void setModificationContext(ModificationContext modificationContext) {\r
1440         this.modificationContext = modificationContext;\r
1441         \r
1442     }\r
1443     \r
1444     final ExecutorService                        queryUpdateScheduler = Threads.getExecutor();\r
1445     \r
1446     \r
1447         private double getDisplayScale() {\r
1448                 Point dpi = Display.getCurrent().getDPI();\r
1449                 return (double)dpi.x/96.0;\r
1450         }\r
1451     \r
1452     private void createNatTable() {\r
1453         GETreeData treeData = new GETreeData(list);\r
1454                 GETreeRowModel<TreeNode> treeRowModel = new GETreeRowModel<TreeNode>(treeData);\r
1455                 columnAccessor = new GEColumnAccessor(this);\r
1456                 \r
1457                 IDataProvider dataProvider = new ListDataProvider<TreeNode>(list, columnAccessor);\r
1458                 \r
1459                 int defaultFontSize = 12;\r
1460                 int height = (int)Math.ceil(((double)(defaultFontSize))*getDisplayScale()) + DataLayer.DEFAULT_ROW_HEIGHT-defaultFontSize;\r
1461                 dataLayer = new DataLayer(dataProvider, DataLayer.DEFAULT_COLUMN_WIDTH, height);\r
1462                 \r
1463                 // resizable rows are unnecessary in Sulca report.\r
1464                 dataLayer.setRowsResizableByDefault(false);\r
1465                 \r
1466                 // Row header layer\r
1467                 DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(dataProvider);\r
1468                 rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider);\r
1469                 \r
1470                 // adjust row header column width so that row numbers fit into the column. \r
1471                 //adjustRowHeaderWidth(list.size());\r
1472                 \r
1473                 // Column header layer\r
1474                 columnHeaderDataProvider = new GEColumnHeaderDataProvider(this, dataLayer); \r
1475                 columnHeaderDataLayer = new DefaultColumnHeaderDataLayer(columnHeaderDataProvider);\r
1476                 columnHeaderDataLayer.setDefaultRowHeight(height);\r
1477                 columnHeaderDataProvider.updateColumnSizes();\r
1478                 \r
1479                 //ISortModel sortModel = new EcoSortModel(this, generator,dataLayer);\r
1480                 \r
1481                 // Column re-order + hide\r
1482                 ColumnReorderLayer columnReorderLayer = new ColumnReorderLayer(dataLayer);\r
1483                 ColumnHideShowLayer columnHideShowLayer = new ColumnHideShowLayer(columnReorderLayer);\r
1484                                 \r
1485                 \r
1486                 treeLayer = new GETreeLayer(columnHideShowLayer, treeRowModel, false);\r
1487                 \r
1488                 selectionLayer = new SelectionLayer(treeLayer);\r
1489                 \r
1490                 viewportLayer = new ViewportLayer(selectionLayer);\r
1491                 \r
1492                 ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, viewportLayer, selectionLayer);\r
1493                 //      Note: The column header layer is wrapped in a filter row composite.\r
1494                 //      This plugs in the filter row functionality\r
1495         \r
1496                 ColumnOverrideLabelAccumulator labelAccumulator = new ColumnOverrideLabelAccumulator(columnHeaderDataLayer);\r
1497                 columnHeaderDataLayer.setConfigLabelAccumulator(labelAccumulator);\r
1498                 \r
1499                 // Register labels\r
1500                 //SortHeaderLayer<TreeNode> sortHeaderLayer = new SortHeaderLayer<TreeNode>(columnHeaderLayer, sortModel, false);\r
1501 \r
1502                 RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, viewportLayer, selectionLayer);\r
1503 \r
1504                 // Corner layer\r
1505                 DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider);\r
1506                 cornerDataLayer = new DataLayer(cornerDataProvider);\r
1507                 //CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, sortHeaderLayer);\r
1508                 CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, columnHeaderLayer);\r
1509 \r
1510                 // Grid\r
1511                 //GridLayer gridLayer = new GridLayer(viewportLayer,sortHeaderLayer,rowHeaderLayer, cornerLayer);\r
1512                 GridLayer gridLayer = new GridLayer(viewportLayer, columnHeaderLayer,rowHeaderLayer, cornerLayer, false);\r
1513                 \r
1514                 /* Since 1.4.0, alternative row rendering uses row indexes in the original data list. \r
1515                    When combined with collapsed tree rows, rows with odd or even index may end up next to each other,\r
1516                    which defeats the purpose of alternating colors. This overrides that and returns the functionality\r
1517                    that we had with 1.0.1. */\r
1518                 gridLayer.setConfigLabelAccumulatorForRegion(GridRegion.BODY, new RelativeAlternatingRowConfigLabelAccumulator());\r
1519         gridLayer.addConfiguration(new DefaultEditConfiguration());\r
1520         //gridLayer.addConfiguration(new DefaultEditBindings());\r
1521         gridLayer.addConfiguration(new GEEditBindings());\r
1522                 \r
1523                 natTable = new NatTable(composite,gridLayer,false);\r
1524                 \r
1525                 //selectionLayer.registerCommandHandler(new EcoCopyDataCommandHandler(selectionLayer,columnHeaderDataLayer,columnAccessor, columnHeaderDataProvider));\r
1526                 \r
1527                 natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable));\r
1528                 natTable.addConfiguration(new DefaultTreeLayerConfiguration2(treeLayer));\r
1529                 natTable.addConfiguration(new SingleClickSortConfiguration());\r
1530                 //natTable.addLayerListener(this);\r
1531                 \r
1532                 natTable.addConfiguration(new GENatTableThemeConfiguration(treeData));\r
1533                 natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable));\r
1534                 \r
1535                 natTable.addConfiguration(new AbstractRegistryConfiguration() {\r
1536                         \r
1537                         @Override\r
1538                         public void configureRegistry(IConfigRegistry configRegistry) {\r
1539                                   configRegistry.registerConfigAttribute(\r
1540                                 EditConfigAttributes.CELL_EDITABLE_RULE,\r
1541                                 new IEditableRule() {\r
1542 \r
1543                                     @Override\r
1544                                     public boolean isEditable(ILayerCell cell,\r
1545                                             IConfigRegistry configRegistry) {\r
1546                                         int col = cell.getColumnIndex();\r
1547                                         int row = cell.getRowIndex();\r
1548                                         TreeNode node = list.get(row);\r
1549                                         Modifier modifier = getModifier(node,col);\r
1550                                         return modifier != null;\r
1551                                         \r
1552                                     }\r
1553 \r
1554                                     @Override\r
1555                                     public boolean isEditable(int columnIndex, int rowIndex) {\r
1556                                         // there are no callers?\r
1557                                         return false;\r
1558                                     }\r
1559 \r
1560                                 });\r
1561                                   configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, new AdaptableCellEditor());\r
1562                                   configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, new DefaultDisplayConverter(),DisplayMode.EDIT);\r
1563                                  // configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_PAINTER, new GECellPainter(),DisplayMode.NORMAL);\r
1564 \r
1565                                 \r
1566                         }\r
1567                 });\r
1568                 \r
1569                 natTable.configure();\r
1570                 \r
1571 //              natTable.addListener(SWT.MenuDetect, new NatTableMenuListener());\r
1572                 \r
1573 //              DefaultToolTip toolTip = new EcoCellToolTip(natTable, columnAccessor);\r
1574 //              toolTip.setBackgroundColor(natTable.getDisplay().getSystemColor(SWT.COLOR_WHITE));\r
1575 //              toolTip.setPopupDelay(500);\r
1576 //              toolTip.activate();\r
1577 //              toolTip.setShift(new Point(10, 10));\r
1578 \r
1579                 \r
1580 //              menuManager.createContextMenu(composite);\r
1581 //              natTable.setMenu(getMenuManager().getMenu());\r
1582                 \r
1583                 selectionAdaptor = new NatTableSelectionAdaptor(natTable, selectionLayer, treeData);\r
1584     }\r
1585     \r
1586     Modifier getModifier(TreeNode element, int columnIndex) {\r
1587                 GENodeQueryManager manager = element.getManager();\r
1588                 final NodeContext context = element.getContext();\r
1589             Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);\r
1590             if (labeler == null)\r
1591                  return null;\r
1592             Column column = columns[columnIndex];\r
1593 \r
1594         return labeler.getModifier(modificationContext, column.getKey());\r
1595 \r
1596         }\r
1597     \r
1598     private class AdaptableCellEditor implements ICellEditor {\r
1599         ICellEditor editor;\r
1600 \r
1601                 @Override\r
1602                 public Control activateCell(Composite parent, Object originalCanonicalValue, EditModeEnum editMode,\r
1603                                 ICellEditHandler editHandler, ILayerCell cell, IConfigRegistry configRegistry) {\r
1604                         int col = cell.getColumnIndex();\r
1605                         int row = cell.getRowIndex();\r
1606                         TreeNode node = list.get(row);\r
1607                         Modifier modifier = getModifier(node, col);\r
1608                         if (modifier == null)\r
1609                                 return null;\r
1610                         \r
1611                         editor = null;\r
1612                         if (modifier instanceof DialogModifier) {\r
1613                                 DialogModifier mod = (DialogModifier)modifier;\r
1614                                 editor = new DialogCellEditor(node, col, mod);\r
1615                         } else if (modifier instanceof CustomModifier) {\r
1616                                 CustomModifier mod = (CustomModifier)modifier;\r
1617                                 editor = new CustomCellEditor(node, col, mod);\r
1618                         } else if (modifier instanceof EnumerationModifier) {\r
1619                                 EnumerationModifier mod = (EnumerationModifier)modifier;\r
1620                                 editor = new ComboBoxCellEditor(mod.getValues());\r
1621                         } else {\r
1622                                 editor = new TextCellEditor();\r
1623                         }\r
1624                         \r
1625                         return editor.activateCell(parent, originalCanonicalValue, editMode, editHandler, cell, configRegistry);\r
1626                 }\r
1627 \r
1628                 @Override\r
1629                 public int getColumnIndex() {\r
1630                         return editor.getColumnIndex();\r
1631                 }\r
1632 \r
1633                 @Override\r
1634                 public int getRowIndex() {\r
1635                         return editor.getRowIndex();\r
1636                 }\r
1637 \r
1638                 @Override\r
1639                 public int getColumnPosition() {\r
1640                         return editor.getColumnPosition();\r
1641                 }\r
1642 \r
1643                 @Override\r
1644                 public int getRowPosition() {\r
1645                         return editor.getRowPosition();\r
1646                 }\r
1647 \r
1648                 @Override\r
1649                 public Object getEditorValue() {\r
1650                         return editor.getEditorValue();\r
1651                 }\r
1652 \r
1653                 @Override\r
1654                 public void setEditorValue(Object value) {\r
1655                         editor.setEditorValue(value);\r
1656                         \r
1657                 }\r
1658 \r
1659                 @Override\r
1660                 public Object getCanonicalValue() {\r
1661                         return editor.getCanonicalValue();\r
1662                 }\r
1663 \r
1664                 @Override\r
1665                 public Object getCanonicalValue(IEditErrorHandler conversionErrorHandler) {\r
1666                         return editor.getCanonicalValue();\r
1667                 }\r
1668 \r
1669                 @Override\r
1670                 public void setCanonicalValue(Object canonicalValue) {\r
1671                         editor.setCanonicalValue(canonicalValue);\r
1672                         \r
1673                 }\r
1674 \r
1675                 @Override\r
1676                 public boolean validateCanonicalValue(Object canonicalValue) {\r
1677                         return editor.validateCanonicalValue(canonicalValue);\r
1678                 }\r
1679 \r
1680                 @Override\r
1681                 public boolean validateCanonicalValue(Object canonicalValue, IEditErrorHandler validationErrorHandler) {\r
1682                         return editor.validateCanonicalValue(canonicalValue, validationErrorHandler);\r
1683                 }\r
1684 \r
1685                 @Override\r
1686                 public boolean commit(MoveDirectionEnum direction) {\r
1687                         return editor.commit(direction);\r
1688                 }\r
1689 \r
1690                 @Override\r
1691                 public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit) {\r
1692                         return editor.commit(direction, closeAfterCommit);\r
1693                 }\r
1694 \r
1695                 @Override\r
1696                 public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit, boolean skipValidation) {\r
1697                         return editor.commit(direction, closeAfterCommit, skipValidation);\r
1698                 }\r
1699 \r
1700                 @Override\r
1701                 public void close() {\r
1702                         editor.close();\r
1703                         \r
1704                 }\r
1705 \r
1706                 @Override\r
1707                 public boolean isClosed() {\r
1708                         return editor.isClosed();\r
1709                 }\r
1710 \r
1711                 @Override\r
1712                 public Control getEditorControl() {\r
1713                         return editor.getEditorControl();\r
1714                 }\r
1715 \r
1716                 @Override\r
1717                 public Control createEditorControl(Composite parent) {\r
1718                         return editor.createEditorControl(parent);\r
1719                 }\r
1720 \r
1721                 @Override\r
1722                 public boolean openInline(IConfigRegistry configRegistry, List<String> configLabels) {\r
1723                         return EditConfigHelper.openInline(configRegistry, configLabels);\r
1724                 }\r
1725 \r
1726                 @Override\r
1727                 public boolean supportMultiEdit(IConfigRegistry configRegistry, List<String> configLabels) {\r
1728                         return editor.supportMultiEdit(configRegistry, configLabels);\r
1729                 }\r
1730 \r
1731                 @Override\r
1732                 public boolean openMultiEditDialog() {\r
1733                         return editor.openMultiEditDialog();\r
1734                 }\r
1735 \r
1736                 @Override\r
1737                 public boolean openAdjacentEditor() {\r
1738                         return editor.openAdjacentEditor();\r
1739                 }\r
1740 \r
1741                 @Override\r
1742                 public boolean activateAtAnyPosition() {\r
1743                         return true;\r
1744                 }\r
1745 \r
1746                 @Override\r
1747                 public boolean activateOnTraversal(IConfigRegistry configRegistry, List<String> configLabels) {\r
1748                         return editor.activateOnTraversal(configRegistry, configLabels);\r
1749                 }\r
1750 \r
1751                 @Override\r
1752                 public void addEditorControlListeners() {\r
1753                         editor.addEditorControlListeners();\r
1754                         \r
1755                 }\r
1756 \r
1757                 @Override\r
1758                 public void removeEditorControlListeners() {\r
1759                         editor.removeEditorControlListeners();\r
1760                         \r
1761                 }\r
1762 \r
1763                 @Override\r
1764                 public Rectangle calculateControlBounds(Rectangle cellBounds) {\r
1765                         return editor.calculateControlBounds(cellBounds);\r
1766                 }\r
1767         \r
1768         \r
1769     }\r
1770     \r
1771     private class CustomCellEditor extends AbstractCellEditor {\r
1772         TreeNode node;\r
1773         CustomModifier customModifier;\r
1774         Control control;\r
1775         int column;\r
1776         \r
1777         public CustomCellEditor(TreeNode node, int column, CustomModifier customModifier) {\r
1778                         this.customModifier = customModifier;\r
1779                         this.node = node;\r
1780                         this.column = column;\r
1781                 }\r
1782 \r
1783                 @Override\r
1784                 public Object getEditorValue() {\r
1785                         return customModifier.getValue();\r
1786                 }\r
1787 \r
1788                 @Override\r
1789                 public void setEditorValue(Object value) {\r
1790                         customModifier.modify(value.toString());\r
1791                         \r
1792                 }\r
1793 \r
1794                 @Override\r
1795                 public Control getEditorControl() {\r
1796                         return control;\r
1797                 }\r
1798 \r
1799                 @Override\r
1800                 public Control createEditorControl(Composite parent) {\r
1801                         return (Control)customModifier.createControl(parent, null, column, node.getContext());\r
1802                         \r
1803                 }\r
1804 \r
1805                 @Override\r
1806                 protected Control activateCell(Composite parent, Object originalCanonicalValue) {\r
1807                         this.control = createEditorControl(parent);\r
1808                         return control;\r
1809                 }\r
1810         \r
1811         \r
1812     }\r
1813     \r
1814     private class DialogCellEditor extends AbstractDialogCellEditor {\r
1815         TreeNode node;\r
1816         DialogModifier dialogModifier;\r
1817         int column;\r
1818         \r
1819         String res = null;\r
1820         Semaphore sem;\r
1821         \r
1822         boolean closed = false;\r
1823         \r
1824         public DialogCellEditor(TreeNode node, int column, DialogModifier dialogModifier) {\r
1825                         this.dialogModifier = dialogModifier;\r
1826                         this.node = node;\r
1827                         this.column = column;\r
1828                 }\r
1829         \r
1830         @Override\r
1831         public int open() {\r
1832                 sem = new Semaphore(1);\r
1833                 Consumer<String> callback = result -> {\r
1834                             res = result;\r
1835                             sem.release();\r
1836                 };\r
1837                         String status = dialogModifier.query(this.parent.getShell(), null, column, node.getContext(), callback);\r
1838                         if (status != null) {\r
1839                                 closed = true;\r
1840                                 return Window.CANCEL;\r
1841                         }\r
1842                                  \r
1843                         try {\r
1844                                 sem.acquire();\r
1845                         } catch (InterruptedException e) {\r
1846                                 e.printStackTrace();\r
1847                         }\r
1848                         closed = true;\r
1849                         return Window.OK;\r
1850         }\r
1851         \r
1852         @Override\r
1853         public DialogModifier createDialogInstance() {\r
1854                 closed = false;\r
1855                 return dialogModifier;\r
1856         }\r
1857         \r
1858         @Override\r
1859         public Object getDialogInstance() {\r
1860                 return (DialogModifier)this.dialog;\r
1861         }\r
1862         \r
1863         @Override\r
1864         public void close() {\r
1865                 \r
1866         }\r
1867         \r
1868         @Override\r
1869         public Object getEditorValue() {\r
1870                 return null;\r
1871         }\r
1872         \r
1873         @Override\r
1874         public void setEditorValue(Object value) {\r
1875                 // dialog modifier handles this internally\r
1876         }\r
1877         \r
1878         @Override\r
1879         public boolean isClosed() {\r
1880                 return closed;\r
1881         }\r
1882         \r
1883     }\r
1884     \r
1885 \r
1886     /**\r
1887      * The job that is used for off-loading image loading tasks (see\r
1888      * {@link ImageTask} to a worker thread from the main UI thread.\r
1889      */\r
1890     ImageLoaderJob           imageLoaderJob;\r
1891     \r
1892    // Map<NodeContext, ImageTask> imageTasks     = new THashMap<NodeContext, ImageTask>();\r
1893     Map<TreeNode, ImageTask> imageTasks     = new THashMap<TreeNode, ImageTask>();\r
1894     \r
1895     void queueImageTask(TreeNode node, ImageTask task) {\r
1896                 synchronized (imageTasks) {\r
1897                         imageTasks.put(node, task);\r
1898                 }\r
1899                 imageLoaderJob.scheduleIfNecessary(100);\r
1900         }\r
1901     \r
1902     /**\r
1903      * Invoked in a job worker thread.\r
1904      * \r
1905      * @param monitor\r
1906      */\r
1907     @Override\r
1908     protected IStatus setPendingImages(IProgressMonitor monitor) {\r
1909         ImageTask[] tasks = null;\r
1910         synchronized (imageTasks) {\r
1911             tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]);\r
1912             imageTasks.clear();\r
1913         }\r
1914 \r
1915         MultiStatus status = null;\r
1916 \r
1917         // Load missing images\r
1918         for (ImageTask task : tasks) {\r
1919             Object desc = task.descsOrImage;\r
1920                  if (desc instanceof ImageDescriptor) {\r
1921                         try {\r
1922                             desc = resourceManager.get((ImageDescriptor) desc);\r
1923                             task.descsOrImage = desc;\r
1924                         } catch (DeviceResourceException e) {\r
1925                             if (status == null)\r
1926                                 status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null);\r
1927                             status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e));\r
1928                         }\r
1929                     }\r
1930             \r
1931         }\r
1932 \r
1933         // Perform final UI updates in the UI thread.\r
1934         final ImageTask[] _tasks = tasks;\r
1935         thread.asyncExec(new Runnable() {\r
1936             @Override\r
1937             public void run() {\r
1938                 setImages(_tasks);\r
1939             }\r
1940         });\r
1941 \r
1942         return status != null ? status : Status.OK_STATUS;\r
1943     }\r
1944     \r
1945 \r
1946     void setImages(ImageTask[] tasks) {\r
1947         for (ImageTask task : tasks)\r
1948             if (task != null)\r
1949                 setImage(task);\r
1950     }\r
1951     \r
1952     void setImage(ImageTask task) {\r
1953         if (!task.node.isDisposed())\r
1954                 update(task.node, 0);\r
1955     }\r
1956     \r
1957     private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider {\r
1958                 \r
1959                 private NatTableGraphExplorer ge;\r
1960                 \r
1961                 GraphExplorerPostSelectionProvider(NatTableGraphExplorer ge) {\r
1962                         this.ge = ge;\r
1963                 }\r
1964                 \r
1965                 void dispose() {\r
1966                         ge = null;\r
1967                 }\r
1968                 \r
1969             @Override\r
1970             public void setSelection(final ISelection selection) {\r
1971                 if(ge == null) return;\r
1972                 ge.setSelection(selection, false);\r
1973                 \r
1974             }\r
1975             \r
1976 \r
1977             @Override\r
1978             public void removeSelectionChangedListener(ISelectionChangedListener listener) {\r
1979                 if(ge == null) return;\r
1980                 if(ge.isDisposed()) {\r
1981                     if (DEBUG_SELECTION_LISTENERS)\r
1982                         System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener);\r
1983                     return;\r
1984                 }\r
1985                 ge.selectionProvider.removeSelectionChangedListener(listener);\r
1986             }\r
1987             \r
1988             @Override\r
1989             public void addPostSelectionChangedListener(ISelectionChangedListener listener) {\r
1990                 if(ge == null) return;\r
1991                 if (!ge.thread.currentThreadAccess())\r
1992                     throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());\r
1993                 if(ge.isDisposed()) {\r
1994                     System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener);\r
1995                     return;\r
1996                 }\r
1997                 ge.selectionProvider.addPostSelectionChangedListener(listener);\r
1998             }\r
1999 \r
2000             @Override\r
2001             public void removePostSelectionChangedListener(ISelectionChangedListener listener) {\r
2002                 if(ge == null) return;\r
2003                 if(ge.isDisposed()) {\r
2004                     if (DEBUG_SELECTION_LISTENERS)\r
2005                         System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener);\r
2006                     return;\r
2007                 }\r
2008                 ge.selectionProvider.removePostSelectionChangedListener(listener);\r
2009             }\r
2010             \r
2011 \r
2012             @Override\r
2013             public void addSelectionChangedListener(ISelectionChangedListener listener) {\r
2014                 if(ge == null) return;\r
2015                 if (!ge.thread.currentThreadAccess())\r
2016                     throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());\r
2017                 if (ge.natTable.isDisposed() || ge.selectionProvider == null) {\r
2018                     System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener);\r
2019                     return;\r
2020                 }\r
2021 \r
2022                 ge.selectionProvider.addSelectionChangedListener(listener);\r
2023             }\r
2024 \r
2025             \r
2026             @Override\r
2027             public ISelection getSelection() {\r
2028                 if(ge == null) return StructuredSelection.EMPTY;\r
2029                 if (!ge.thread.currentThreadAccess())\r
2030                     throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread());\r
2031                 if (ge.natTable.isDisposed() || ge.selectionProvider == null)\r
2032                     return StructuredSelection.EMPTY;\r
2033                 return ge.selectionProvider.getSelection();\r
2034             }\r
2035             \r
2036         }\r
2037         \r
2038         static class ModifierValidator implements ICellEditorValidator {\r
2039                 private Modifier modifier;\r
2040                 public ModifierValidator(Modifier modifier) {\r
2041                         this.modifier = modifier;\r
2042                 }\r
2043                 \r
2044                 @Override\r
2045                 public String isValid(Object value) {\r
2046                         return modifier.isValid((String)value);\r
2047                 }\r
2048         }\r
2049         \r
2050         static class UpdateRunner implements Runnable {\r
2051 \r
2052             final NatTableGraphExplorer ge;\r
2053 \r
2054             UpdateRunner(NatTableGraphExplorer ge, IGraphExplorerContext geContext) {\r
2055                 this.ge = ge;\r
2056             }\r
2057 \r
2058             public void run() {\r
2059                 try {\r
2060                         doRun();\r
2061                 } catch (Throwable t) {\r
2062                         t.printStackTrace();\r
2063                 }\r
2064             }\r
2065 \r
2066             public void doRun() {\r
2067                 \r
2068                 if (ge.isDisposed())\r
2069                     return;\r
2070 \r
2071                 HashSet<UpdateItem> items;\r
2072 \r
2073                 ScrollBar verticalBar = ge.natTable.getVerticalBar();\r
2074               \r
2075                 \r
2076                 synchronized (ge.pendingItems) {\r
2077                    items = ge.pendingItems;\r
2078                    ge.pendingItems = new HashSet<UpdateItem>();\r
2079                 }\r
2080                 if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size());\r
2081 \r
2082                 ge.natTable.setRedraw(false);\r
2083             for (UpdateItem item : items) {\r
2084                 item.update(ge.natTable);\r
2085             }\r
2086             \r
2087             // check if vertical scroll bar has become visible and refresh layout.\r
2088             boolean currentlyVerticalBarVisible = verticalBar.isVisible();\r
2089             if (ge.verticalBarVisible != currentlyVerticalBarVisible) {\r
2090                 ge.verticalBarVisible = currentlyVerticalBarVisible;\r
2091                 ge.natTable.getParent().layout();\r
2092             }\r
2093             \r
2094             ge.natTable.setRedraw(true);\r
2095             \r
2096                 synchronized (ge.pendingItems) {\r
2097                     if (!ge.scheduleUpdater()) {\r
2098                         ge.updating = false;\r
2099                     }\r
2100                 }\r
2101                 if (DEBUG) {\r
2102                         if (!ge.updating) {\r
2103                                  ge.printTree(ge.rootNode, 0);\r
2104                         }\r
2105                 }\r
2106             }\r
2107 \r
2108         }\r
2109     \r
2110     \r
2111     \r
2112     public static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {\r
2113         // This is for query debugging only.\r
2114         \r
2115         private NatTableGraphExplorer ge;\r
2116         int                  queryIndent   = 0;\r
2117 \r
2118         GECache2             cache         = new GECache2();\r
2119         AtomicBoolean        propagating   = new AtomicBoolean(false);\r
2120         Object               propagateList = new Object();\r
2121         Object               propagate     = new Object();\r
2122         List<Runnable>       scheduleList  = new ArrayList<Runnable>();\r
2123         final Deque<Integer> activity      = new LinkedList<Integer>();\r
2124         int                  activityInt   = 0;\r
2125         \r
2126         AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();\r
2127 \r
2128         /**\r
2129          * Keeps track of nodes that have already been auto-expanded. After\r
2130          * being inserted into this set, nodes will not be forced to stay in an\r
2131          * expanded state after that. This makes it possible for the user to\r
2132          * close auto-expanded nodes.\r
2133          */\r
2134         Map<NodeContext, Boolean>     autoExpanded  = new WeakHashMap<NodeContext, Boolean>();\r
2135 \r
2136         public GeViewerContext(NatTableGraphExplorer ge) {\r
2137                 this.ge = ge;\r
2138         }\r
2139         \r
2140         public MapList<NodeContext,TreeNode> getContextToNodeMap() {\r
2141                 if (ge == null)\r
2142                         return null;\r
2143                 return ge.contextToNodeMap;\r
2144         }\r
2145         \r
2146         public NatTableGraphExplorer getGe() {\r
2147                         return ge;\r
2148                 }\r
2149         \r
2150         @Override\r
2151         protected void doDispose() {\r
2152                 //saveState();\r
2153             autoExpanded.clear();\r
2154         }\r
2155 \r
2156         @Override\r
2157         public IGECache getCache() {\r
2158             return cache;\r
2159         }\r
2160 \r
2161         @Override\r
2162         public int queryIndent() {\r
2163             return queryIndent;\r
2164         }\r
2165 \r
2166         @Override\r
2167         public int queryIndent(int offset) {\r
2168             queryIndent += offset;\r
2169             return queryIndent;\r
2170         }\r
2171 \r
2172         @Override\r
2173         @SuppressWarnings("unchecked")\r
2174         public <T> NodeQueryProcessor<T> getProcessor(Object o) {\r
2175                 if (ge == null)\r
2176                         return null;\r
2177             return ge.processors.get(o);\r
2178         }\r
2179 \r
2180         @Override\r
2181         @SuppressWarnings("unchecked")\r
2182         public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {\r
2183             return ge.primitiveProcessors.get(o);\r
2184         }\r
2185 \r
2186         @SuppressWarnings("unchecked")\r
2187         @Override\r
2188         public <T> DataSource<T> getDataSource(Class<T> clazz) {\r
2189             return ge.dataSources.get(clazz);\r
2190         }\r
2191 \r
2192         @Override\r
2193         public void update(UIElementReference ref) {\r
2194                 if (ref instanceof ViewerCellReference) {\r
2195                     ViewerCellReference tiref = (ViewerCellReference) ref;\r
2196                     Object element = tiref.getElement();\r
2197                     int columnIndex = tiref.getColumn();\r
2198                     // NOTE: must be called regardless of the the item value.\r
2199                     // A null item is currently used to indicate a tree root update.\r
2200                     ge.update((TreeNode)element,columnIndex);\r
2201                 } else if (ref instanceof ViewerRowReference) {\r
2202                         ViewerRowReference rref = (ViewerRowReference)ref;\r
2203                         Object element = rref.getElement();\r
2204                         ge.update((TreeNode)element);\r
2205                 } else {\r
2206                         throw new IllegalArgumentException("Ui Reference is unknkown " + ref);\r
2207                 }\r
2208         }\r
2209 \r
2210         @Override\r
2211         public Object getPropagateLock() {\r
2212             return propagate;\r
2213         }\r
2214 \r
2215         @Override\r
2216         public Object getPropagateListLock() {\r
2217             return propagateList;\r
2218         }\r
2219 \r
2220         @Override\r
2221         public boolean isPropagating() {\r
2222             return propagating.get();\r
2223         }\r
2224 \r
2225         @Override\r
2226         public void setPropagating(boolean b) {\r
2227             this.propagating.set(b);\r
2228         }\r
2229 \r
2230         @Override\r
2231         public List<Runnable> getScheduleList() {\r
2232             return scheduleList;\r
2233         }\r
2234 \r
2235         @Override\r
2236         public void setScheduleList(List<Runnable> list) {\r
2237             this.scheduleList = list;\r
2238         }\r
2239 \r
2240         @Override\r
2241         public Deque<Integer> getActivity() {\r
2242             return activity;\r
2243         }\r
2244 \r
2245         @Override\r
2246         public void setActivityInt(int i) {\r
2247             this.activityInt = i;\r
2248         }\r
2249 \r
2250         @Override\r
2251         public int getActivityInt() {\r
2252             return activityInt;\r
2253         }\r
2254 \r
2255         @Override\r
2256         public void scheduleQueryUpdate(Runnable r) {\r
2257                 if (ge == null)\r
2258                         return;\r
2259             if (ge.isDisposed())\r
2260                 return;\r
2261             if (currentQueryUpdater.compareAndSet(null, r)) {\r
2262                 ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER);\r
2263             }\r
2264         }\r
2265 \r
2266         Runnable QUERY_UPDATE_SCHEDULER = new Runnable() {\r
2267             @Override\r
2268             public void run() {\r
2269                 Runnable r = currentQueryUpdater.getAndSet(null);\r
2270                 if (r != null) {\r
2271                     r.run();\r
2272                 }\r
2273             }\r
2274         };\r
2275         \r
2276         @Override\r
2277         public void dispose() {\r
2278                 cache.dispose();\r
2279                 cache = new DummyCache();\r
2280                 scheduleList.clear();\r
2281                 autoExpanded.clear();\r
2282                 autoExpanded = null;\r
2283                 ge = null;\r
2284             \r
2285         }\r
2286     }\r
2287     \r
2288     private class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor<Boolean> implements\r
2289         IsExpandedProcessor, ProcessorLifecycle {\r
2290                  /**\r
2291              * The set of currently expanded node contexts.\r
2292              */\r
2293             private final HashSet<NodeContext>                        expanded        = new HashSet<NodeContext>();\r
2294             private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();\r
2295 \r
2296             private NatTable natTable;\r
2297             private List<TreeNode> list;\r
2298 \r
2299             public TreeNodeIsExpandedProcessor() {\r
2300             }\r
2301 \r
2302             @Override\r
2303             public Object getIdentifier() {\r
2304                 return BuiltinKeys.IS_EXPANDED;\r
2305             }\r
2306 \r
2307             @Override\r
2308             public String toString() {\r
2309                 return "IsExpandedProcessor";\r
2310             }\r
2311 \r
2312             @Override\r
2313             public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Boolean> key) {\r
2314                 boolean isExpanded = expanded.contains(context);\r
2315                 expandedQueries.put(context, updater);\r
2316                 return Boolean.valueOf(isExpanded);\r
2317             }\r
2318 \r