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