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