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