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
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.browsing.ui.swt;
\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
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
178 import gnu.trove.map.hash.THashMap;
\r
179 import gnu.trove.map.hash.TObjectIntHashMap;
\r
182 * TreeView based GraphExplorer
\r
185 * @author Marko Luukkainen <marko.luukkainen@vtt.fi>
\r
187 public class GraphExplorerImpl2 extends GraphExplorerImplBase implements GraphExplorer {
\r
189 private static final boolean DEBUG_SELECTION_LISTENERS = false;
\r
190 private static final boolean DEBUG = false;
\r
192 private TreeViewer viewer;
\r
194 private LocalResourceManager localResourceManager;
\r
195 private DeviceResourceManager resourceManager;
\r
198 private IThreadWorkQueue thread;
\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
207 private FontDescriptor originalFont;
\r
208 protected ColorDescriptor originalForeground;
\r
209 protected ColorDescriptor originalBackground;
\r
210 private Color invalidModificationColor;
\r
212 private Column[] columns;
\r
213 private Map<String,Integer> columnKeyToIndex;
\r
214 private boolean columnsAreVisible = true;
\r
217 private NodeContext rootContext;
\r
218 private TreeNode rootNode;
\r
219 private StatePersistor persistor = null;
\r
221 private boolean editable = true;
\r
223 private boolean disposed = false;
\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
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
235 GeViewerContext explorerContext = new GeViewerContext(this);
\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
242 private Set<TreeNode> collapsedNodes = new HashSet<TreeNode>();
\r
243 private MapList<NodeContext, TreeNode> contextToNodeMap = new MapList<NodeContext, TreeNode>();
\r
245 private ModificationContext modificationContext = null;
\r
247 private boolean filterSelectionEdit = true;
\r
249 private TreeColumnLayout treeColumnLayout;
\r
251 private boolean expand;
\r
252 private boolean verticalBarVisible = false;
\r
254 private BinaryFunction<Object[], GraphExplorer, Object[]> selectionTransformation = new BinaryFunction<Object[], GraphExplorer, Object[]>() {
\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
269 static class TransientStateImpl implements TransientExplorerState {
\r
271 private Integer activeColumn = null;
\r
274 public synchronized Integer getActiveColumn() {
\r
275 return activeColumn;
\r
278 public synchronized void setActiveColumn(Integer column) {
\r
279 activeColumn = column;
\r
284 private TransientStateImpl transientState = new TransientStateImpl();
\r
287 public GraphExplorerImpl2(Composite parent) {
\r
288 this(parent, SWT.BORDER | SWT.MULTI );
\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
295 this.imageLoaderJob = new ImageLoaderJob(this);
\r
296 this.imageLoaderJob.setPriority(Job.DECORATE);
\r
298 invalidModificationColor = (Color) localResourceManager.get(ColorDescriptor.createFrom(new RGB(255, 128, 128)));
\r
300 this.thread = SWTThread.getThreadAccess(parent);
\r
302 for (int i = 0; i < 10; i++)
\r
303 explorerContext.activity.push(0);
\r
305 boolean useLayout = true;
\r
306 // FIXME: hack, GraphExplorerComposite uses its own TreeColumnLayout.
\r
307 if (useLayout && !(parent.getLayout() instanceof TreeColumnLayout)) {
\r
309 Composite rootTreeComposite = new Composite(parent, SWT.NONE);
\r
310 treeColumnLayout = new TreeColumnLayout();
\r
311 rootTreeComposite.setLayout(treeColumnLayout);
\r
313 viewer = new TreeViewer(rootTreeComposite,style|SWT.H_SCROLL|SWT.V_SCROLL);
\r
315 GridDataFactory.fillDefaults().grab(true, true).span(3,1).applyTo(rootTreeComposite);
\r
318 viewer = new TreeViewer(parent,style | SWT.H_SCROLL | SWT.V_SCROLL);
\r
321 viewer.getColumnViewerEditor().addEditorActivationListener(new ColumnViewerEditorActivationListener() {
\r
324 public void beforeEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {
\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
338 public void afterEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {
\r
343 public void afterEditorActivated(ColumnViewerEditorActivationEvent event) {
\r
348 viewer.setUseHashlookup(true);
\r
349 viewer.setContentProvider(new GeViewerContentProvider());
\r
353 originalFont = JFaceResources.getDefaultFontDescriptor();
\r
355 viewer.getTree().setFont((Font) localResourceManager.get(originalFont));
\r
357 setBasicListeners();
\r
358 setDefaultProcessors();
\r
360 viewer.getTree().addDisposeListener(new DisposeListener() {
\r
363 public void widgetDisposed(DisposeEvent e) {
\r
370 // Add listener to tree for delayed tree population.
\r
372 Listener listener = new Listener() {
\r
375 public void handleEvent(Event event) {
\r
377 switch (event.type) {
\r
386 case SWT.Deactivate:
\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
400 viewer.addTreeListener(new ITreeViewerListener() {
\r
403 public void treeExpanded(TreeExpansionEvent event) {
\r
408 public void treeCollapsed(TreeExpansionEvent event) {
\r
409 collapsedNodes.add((TreeNode)event.getElement());
\r
413 setColumns( new Column[] { new Column(ColumnKeys.SINGLE) });
\r
416 private long focusGainedAt = 0L;
\r
417 private boolean visible = false;
\r
419 private Collection<TreeNode> selectedNodes = new ArrayList<TreeNode>();
\r
421 protected void setBasicListeners() {
\r
422 Tree tree = viewer.getTree();
\r
424 tree.addFocusListener(new FocusListener() {
\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
432 public void focusLost(FocusEvent e) {
\r
433 for (FocusListener listener : focusListeners)
\r
434 listener.focusLost(e);
\r
437 tree.addMouseListener(new MouseListener() {
\r
439 public void mouseDoubleClick(MouseEvent e) {
\r
440 for (MouseListener listener : mouseListeners) {
\r
441 listener.mouseDoubleClick(e);
\r
445 public void mouseDown(MouseEvent e) {
\r
446 for (MouseListener listener : mouseListeners) {
\r
447 listener.mouseDown(e);
\r
451 public void mouseUp(MouseEvent e) {
\r
452 for (MouseListener listener : mouseListeners) {
\r
453 listener.mouseUp(e);
\r
457 tree.addKeyListener(new KeyListener() {
\r
459 public void keyPressed(KeyEvent e) {
\r
460 for (KeyListener listener : keyListeners) {
\r
461 listener.keyPressed(e);
\r
465 public void keyReleased(KeyEvent e) {
\r
466 for (KeyListener listener : keyListeners) {
\r
467 listener.keyReleased(e);
\r
472 viewer.addSelectionChangedListener(new ISelectionChangedListener() {
\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
483 viewer.addPostSelectionChangedListener(new ISelectionChangedListener() {
\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
496 private NodeContext pendingRoot;
\r
498 private void activate() {
\r
499 if (pendingRoot != null && !expand) {
\r
500 doSetRoot(pendingRoot);
\r
501 pendingRoot = null;
\r
506 * Invoke only from SWT thread to reset the root of the graph explorer tree.
\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
515 // System.out.println("doSetRoot " + root);
\r
518 if (viewer.getTree().isDisposed())
\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
527 // Empty caches, release queries.
\r
528 if (rootNode != null) {
\r
529 rootNode.dispose();
\r
531 GeViewerContext oldContext = explorerContext;
\r
532 GeViewerContext newContext = new GeViewerContext(this);
\r
533 this.explorerContext = newContext;
\r
534 oldContext.safeDispose();
\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
541 this.rootContext = root.getConstant(BuiltinKeys.IS_ROOT) != null ? root
\r
542 : NodeContextUtil.withConstant(root, BuiltinKeys.IS_ROOT, Boolean.TRUE);
\r
544 explorerContext.getCache().incRef(this.rootContext);
\r
549 select(rootContext);
\r
550 //refreshColumnSizes();
\r
551 rootNode = new TreeNode(rootContext);
\r
552 if (DEBUG) System.out.println("setRoot " + rootNode);
\r
554 viewer.setInput(rootNode);
\r
556 // Delay content reading.
\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
564 public void run() {
\r
565 if (rootNode != null) {
\r
566 rootNode.updateChildren();
\r
573 private void initializeState() {
\r
574 if (persistor == null)
\r
577 ExplorerState state = persistor.deserialize(
\r
578 Platform.getStateLocation(Activator.getDefault().getBundle()).toFile(),
\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
592 public NodeContext getRoot() {
\r
593 return rootContext;
\r
597 public IThreadWorkQueue getThread() {
\r
602 public NodeContext getParentContext(NodeContext context) {
\r
604 throw new IllegalStateException("disposed");
\r
605 if (!thread.currentThreadAccess())
\r
606 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
\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
618 @SuppressWarnings("unchecked")
\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
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
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
646 setProcessor(new DefaultFinalChildrenProcessor(this));
\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
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
662 public Column[] getColumns() {
\r
663 return Arrays.copyOf(columns, columns.length);
\r
667 public void setColumnsVisible(boolean visible) {
\r
668 columnsAreVisible = visible;
\r
669 if(viewer.getTree() != null) viewer.getTree().setHeaderVisible(columnsAreVisible);
\r
673 public void setColumns(final Column[] columns) {
\r
674 setColumns(columns, null);
\r
678 public void setColumns(final Column[] columns, Consumer<Map<Column, Object>> callback) {
\r
679 assertNotDisposed();
\r
680 checkUniqueColumnKeys(columns);
\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
687 d.asyncExec(new Runnable() {
\r
689 public void run() {
\r
690 if (viewer == null)
\r
692 if (viewer.getTree().isDisposed())
\r
694 doSetColumns(columns, callback);
\r
695 viewer.refresh(true);
\r
696 viewer.getTree().getParent().layout();
\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
708 if (!duplicateColumns.isEmpty()) {
\r
709 throw new IllegalArgumentException("All columns do not have unique keys: " + cols + ", overlapping: " + duplicateColumns);
\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
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
721 for (TreeViewerColumn c : treeViewerColumns) {
\r
722 prevWidths.put(c.getColumn().getText(), c.getColumn().getWidth());
\r
723 c.getColumn().dispose();
\r
726 treeViewerColumns.clear();
\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
733 this.columns = Arrays.copyOf(cols, cols.length);
\r
734 //this.columns[cols.length] = FILLER_COLUMN;
\r
735 this.columnKeyToIndex = keyToIndex;
\r
737 Map<Column, Object> map = new HashMap<Column, Object>();
\r
739 // FIXME : temporary workaround for ModelBrowser.
\r
740 viewer.getTree().setHeaderVisible(columns.length == 1 ? false : columnsAreVisible);
\r
742 int columnIndex = 0;
\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
752 support = new GeEditingSupport(viewer, columnIndex);
\r
753 editingSupports.add(support);
\r
756 tvc.setEditingSupport(support);
\r
758 TreeColumn c = tvc.getColumn();
\r
759 map.put(column, c);
\r
761 c.setText(column.getLabel());
\r
762 c.setToolTipText(column.getTooltip());
\r
764 int cw = column.getWidth();
\r
766 // Try to keep previous widths
\r
767 Integer w = prevWidths.get(column);
\r
770 else if (cw != Column.DEFAULT_CONTROL_WIDTH)
\r
772 else if (columns.length == 1) {
\r
773 // FIXME : how to handle single column properly?
\r
777 // Go for some kind of default settings then...
\r
778 if (ColumnKeys.PROPERTY.equals(column.getKey()))
\r
783 if (treeColumnLayout != null) {
\r
784 treeColumnLayout.setColumnData(c, new ColumnWeightData(column.getWeight(), true));
\r
787 // if (!column.hasGrab() && !FILLER.equals(column.getKey())) {
\r
788 // c.addListener(SWT.Resize, resizeListener);
\r
789 // c.setResizable(true);
\r
791 // //c.setResizable(false);
\r
798 if(callback != null) callback.accept(map);
\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
811 public <T> void setProcessor(NodeQueryProcessor<T> processor) {
\r
812 assertNotDisposed();
\r
813 if (processor == null)
\r
814 throw new IllegalArgumentException("null processor");
\r
816 processors.put(processor.getIdentifier(), processor);
\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
825 PrimitiveQueryProcessor<?> oldProcessor = primitiveProcessors.put(
\r
826 processor.getIdentifier(), processor);
\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
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
842 @SuppressWarnings("unchecked")
\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
852 public void setPersistor(StatePersistor persistor) {
\r
853 this.persistor = persistor;
\r
857 public SelectionDataResolver getSelectionDataResolver() {
\r
858 return selectionDataResolver;
\r
862 public void setSelectionDataResolver(SelectionDataResolver r) {
\r
863 this.selectionDataResolver = r;
\r
867 public SelectionFilter getSelectionFilter() {
\r
868 return selectionFilter;
\r
872 public void setSelectionFilter(SelectionFilter f) {
\r
873 this.selectionFilter = f;
\r
874 // TODO: re-filter current selection?
\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
887 protected Object[] transformSelection(Object[] objects) {
\r
888 return selectionTransformation.call(this, objects);
\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
900 public void setSelectionTransformation(
\r
901 BinaryFunction<Object[], GraphExplorer, Object[]> f) {
\r
902 this.selectionTransformation = f;
\r
905 public ISelection getWidgetSelection() {
\r
906 return viewer.getSelection();
\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
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
931 public void addSelectionListener(SelectionListener listener) {
\r
932 viewer.getTree().addSelectionListener(listener);
\r
935 public void removeSelectionListener(SelectionListener listener) {
\r
936 viewer.getTree().removeSelectionListener(listener);
\r
939 private Set<String> uiContexts;
\r
942 public void setUIContexts(Set<String> contexts) {
\r
943 this.uiContexts = contexts;
\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
951 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root));
\r
955 public void setRootContext(final NodeContext context) {
\r
956 setRootContext0(context);
\r
959 private void setRoot(NodeContext context) {
\r
961 pendingRoot = context;
\r
962 Display.getDefault().asyncExec(new Runnable() {
\r
964 public void run() {
\r
965 if (viewer != null && !viewer.getTree().isDisposed())
\r
966 viewer.getTree().redraw();
\r
971 doSetRoot(context);
\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
978 Display display = viewer.getTree().getDisplay();
\r
979 if (display.getThread() == Thread.currentThread()) {
\r
982 display.asyncExec(new Runnable() {
\r
984 public void run() {
\r
992 public void setFocus() {
\r
993 viewer.getTree().setFocus();
\r
996 @SuppressWarnings("unchecked")
\r
998 public <T> T getControl() {
\r
999 return (T)viewer.getTree();
\r
1004 public boolean isDisposed() {
\r
1008 protected void assertNotDisposed() {
\r
1010 throw new IllegalStateException("disposed");
\r
1014 public boolean isEditable() {
\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
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
1028 private void doDispose() {
\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
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
1058 imageLoaderJob.join();
\r
1059 imageLoaderJob = null;
\r
1060 } catch (InterruptedException e) {
\r
1061 ErrorLogger.defaultLogError(e);
\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
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
1077 imageTasks = null;
\r
1078 modificationContext = null;
\r
1079 focusService = null;
\r
1080 contextService = null;
\r
1081 serviceLocator = null;
\r
1083 columnKeyToIndex.clear();
\r
1084 columnKeyToIndex = null;
\r
1090 public boolean select(NodeContext context) {
\r
1092 assertNotDisposed();
\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
1100 viewer.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0)));
\r
1107 public boolean selectPath(Collection<NodeContext> contexts) {
\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
1112 return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0);
\r
1116 private boolean selectPathInternal(NodeContext[] contexts, int position) {
\r
1118 NodeContext head = contexts[position];
\r
1120 if(position == contexts.length-1) {
\r
1121 return select(head);
\r
1125 setExpanded(head, true);
\r
1126 if(!waitVisible(contexts[position+1])) return false;
\r
1128 return selectPathInternal(contexts, position+1);
\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
1143 public boolean isVisible(NodeContext context) {
\r
1144 if (contextToNodeMap.getValuesUnsafe(context).size() == 0)
\r
1147 Object elements[] = viewer.getVisibleExpandedElements();
\r
1148 return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0));
\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
1161 public <T> T query(NodeContext context, CacheKey<T> key) {
\r
1162 return this.explorerContext.cache.get(context, key);
\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
1171 * Must be invoked during right after construction.
\r
1173 * @param serviceLocator
\r
1174 * a specific service locator or <code>null</code> to use the
\r
1175 * workbench global service locator
\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
1187 private void detachPrimitiveProcessors() {
\r
1188 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
\r
1189 if (p instanceof ProcessorLifecycle) {
\r
1190 ((ProcessorLifecycle) p).detached(this);
\r
1195 private void clearPrimitiveProcessors() {
\r
1196 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
\r
1197 if (p instanceof ProcessorLifecycle) {
\r
1198 ((ProcessorLifecycle) p).clear();
\r
1204 public void setExpanded(NodeContext context, boolean expanded) {
\r
1205 viewer.setExpandedState(context, expanded);
\r
1210 public void setAutoExpandLevel(int level) {
\r
1211 this.autoExpandLevel = level;
\r
1212 viewer.setAutoExpandLevel(level);
\r
1215 int maxChildren = GraphExplorerImpl.DEFAULT_MAX_CHILDREN;
\r
1218 public int getMaxChildren() {
\r
1219 return maxChildren;
\r
1223 public void setMaxChildren(int maxChildren) {
\r
1224 this.maxChildren = maxChildren;
\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
1234 throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result);
\r
1237 return maxChildren;
\r
1241 public <T> NodeQueryProcessor<T> getProcessor(QueryKey<T> key) {
\r
1242 return explorerContext.getProcessor(key);
\r
1246 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(PrimitiveQueryKey<T> key) {
\r
1247 return explorerContext.getPrimitiveProcessor(key);
\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
1255 private class UpdateItem {
\r
1259 public UpdateItem(TreeNode element) {
\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
1271 public void update(TreeViewer viewer) {
\r
1272 if (element != null) {
\r
1274 if (element.isDisposed()) {
\r
1277 if (((TreeNode)element).updateChildren()) {
\r
1278 viewer.refresh(element,true);
\r
1280 if (columnIndex >= 0) {
\r
1281 viewer.update(element, new String[]{columns[columnIndex].getKey()});
\r
1283 viewer.refresh(element,true);
\r
1287 if (!element.isDisposed() && autoExpandLevel > 1 && !collapsedNodes.contains(element) && ((TreeNode)element).distanceToRoot() <= autoExpandLevel) {
\r
1289 viewer.setExpandedState(element, true);
\r
1293 if (rootNode.updateChildren())
\r
1294 viewer.refresh(rootNode,true);
\r
1299 public boolean equals(Object obj) {
\r
1302 if (obj.getClass() != getClass())
\r
1304 UpdateItem other = (UpdateItem)obj;
\r
1305 if (columnIndex != other.columnIndex)
\r
1307 if (element != null)
\r
1308 return element.equals(other.element);
\r
1309 return other.element == null;
\r
1313 public int hashCode() {
\r
1314 if (element != null)
\r
1315 return element.hashCode() + columnIndex;
\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
1324 synchronized (pendingItems) {
\r
1325 pendingItems.add(new UpdateItem(element, columnIndex));
\r
1326 if (updating) return;
\r
1328 scheduleUpdater();
\r
1332 private void update(final TreeNode element) {
\r
1333 if (DEBUG)System.out.println("update " + element);
\r
1334 if (viewer.getTree().isDisposed())
\r
1336 if (element != null && element.isDisposed())
\r
1338 synchronized (pendingItems) {
\r
1340 pendingItems.add(new UpdateItem(element));
\r
1341 if (updating) return;
\r
1343 scheduleUpdater();
\r
1347 boolean scheduleUpdater() {
\r
1349 if (viewer.getTree().isDisposed())
\r
1352 if (!pendingItems.isEmpty()) {
\r
1354 int activity = explorerContext.activityInt;
\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
1362 //System.out.println("Scheduling update after 3000ms.");
\r
1366 updateCounter = 0;
\r
1368 //System.out.println("Scheduling UI update after " + delay + " ms.");
\r
1369 uiUpdateScheduler.schedule(new Runnable() {
\r
1371 public void run() {
\r
1373 if (viewer == null || viewer.getTree().isDisposed())
\r
1376 if (updateCounter > 0) {
\r
1377 updateCounter = 0;
\r
1378 uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS);
\r
1380 viewer.getTree().getDisplay().asyncExec(new UpdateRunner(GraphExplorerImpl2.this, GraphExplorerImpl2.this.explorerContext));
\r
1384 }, delay, TimeUnit.MILLISECONDS);
\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
1399 if(columnKey.startsWith("#")) {
\r
1400 columnKey = columnKey.substring(1);
\r
1403 Integer columnIndex = columnKeyToIndex.get(columnKey);
\r
1404 if (columnIndex == null)
\r
1405 return "Rename not supported for selection";
\r
1407 viewer.editElement(context, columnIndex);
\r
1408 if(viewer.isCellEditorActive()) return null;
\r
1409 return "Rename not supported for selection";
\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
1419 return startEditing(context, columnKey);
\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
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
1438 final ISelection sel = new StructuredSelection(nodes.toArray());
\r
1439 if (coll.size() == 0)
\r
1441 // Schedule viewer and selection update if necessary.
\r
1442 if (viewer.getTree().isDisposed())
\r
1444 Display d = viewer.getTree().getDisplay();
\r
1445 if (d.getThread() == Thread.currentThread()) {
\r
1446 viewer.setSelection(sel);
\r
1448 d.asyncExec(new Runnable() {
\r
1450 public void run() {
\r
1451 if (viewer.getTree().isDisposed())
\r
1453 viewer.setSelection(sel);
\r
1461 public void setModificationContext(ModificationContext modificationContext) {
\r
1462 this.modificationContext = modificationContext;
\r
1466 final ExecutorService queryUpdateScheduler = Threads.getExecutor();
\r
1468 private static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {
\r
1469 // This is for query debugging only.
\r
1471 private GraphExplorerImpl2 ge;
\r
1472 int queryIndent = 0;
\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
1482 AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();
\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
1490 Map<NodeContext, Boolean> autoExpanded = new WeakHashMap<NodeContext, Boolean>();
\r
1492 public GeViewerContext(GraphExplorerImpl2 ge) {
\r
1497 protected void doDispose() {
\r
1499 autoExpanded.clear();
\r
1503 public IGECache getCache() {
\r
1508 public int queryIndent() {
\r
1509 return queryIndent;
\r
1513 public int queryIndent(int offset) {
\r
1514 queryIndent += offset;
\r
1515 return queryIndent;
\r
1519 @SuppressWarnings("unchecked")
\r
1520 public <T> NodeQueryProcessor<T> getProcessor(Object o) {
\r
1523 return ge.processors.get(o);
\r
1527 @SuppressWarnings("unchecked")
\r
1528 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {
\r
1529 return ge.primitiveProcessors.get(o);
\r
1532 @SuppressWarnings("unchecked")
\r
1534 public <T> DataSource<T> getDataSource(Class<T> clazz) {
\r
1535 return ge.dataSources.get(clazz);
\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
1552 throw new IllegalArgumentException("Ui Reference is unknkown " + ref);
\r
1557 public Object getPropagateLock() {
\r
1562 public Object getPropagateListLock() {
\r
1563 return propagateList;
\r
1567 public boolean isPropagating() {
\r
1568 return propagating.get();
\r
1572 public void setPropagating(boolean b) {
\r
1573 this.propagating.set(b);
\r
1577 public List<Runnable> getScheduleList() {
\r
1578 return scheduleList;
\r
1582 public void setScheduleList(List<Runnable> list) {
\r
1583 this.scheduleList = list;
\r
1587 public Deque<Integer> getActivity() {
\r
1592 public void setActivityInt(int i) {
\r
1593 this.activityInt = i;
\r
1597 public int getActivityInt() {
\r
1598 return activityInt;
\r
1602 public void scheduleQueryUpdate(Runnable r) {
\r
1605 if (ge.isDisposed())
\r
1607 if (currentQueryUpdater.compareAndSet(null, r)) {
\r
1608 ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER);
\r
1612 Runnable QUERY_UPDATE_SCHEDULER = new Runnable() {
\r
1614 public void run() {
\r
1615 Runnable r = currentQueryUpdater.getAndSet(null);
\r
1623 public void dispose() {
\r
1625 cache = new DummyCache();
\r
1626 scheduleList.clear();
\r
1627 autoExpanded.clear();
\r
1628 autoExpanded = null;
\r
1638 private static class GeViewerContentProvider implements ITreeContentProvider {
\r
1640 public Object[] getElements(Object inputElement) {
\r
1641 return getChildren(inputElement);
\r
1645 public Object[] getChildren(Object element) {
\r
1646 TreeNode node = (TreeNode)element;
\r
1647 return node.getChildren().toArray();
\r
1652 public Object getParent(Object element) {
\r
1653 TreeNode node = (TreeNode)element;
\r
1654 return node.getParent();
\r
1658 public boolean hasChildren(Object element) {
\r
1659 return getChildren(element).length > 0;
\r
1663 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
\r
1668 public void dispose() {
\r
1673 private class GeViewerLabelProvider extends CellLabelProvider {
\r
1674 private TreeNode node;
\r
1676 private Labeler labeler;
\r
1677 private Imager imager;
\r
1678 Collection<LabelDecorator> labelDecorators;
\r
1679 Collection<ImageDecorator> imageDecorators;
\r
1681 Map<String, String> labels;
\r
1682 Map<String, String> runtimeLabels;
\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
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
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
1700 if (labeler != null) {
\r
1701 labels = labeler.getLabels();
\r
1702 runtimeLabels = labeler.getRuntimeLabels();
\r
1705 runtimeLabels = null;
\r
1709 //if (DEBUG) System.out.println("GeViewerLabelProvider.update " + context + " " + columnIndex);
\r
1711 setText(cell, columnKey);
\r
1712 setImage(cell, columnKey);
\r
1715 void setImage(ViewerCell cell, String columnKey) {
\r
1716 if (imager != null) {
\r
1717 Object descOrImage = null;
\r
1718 boolean hasUncachedImages = false;
\r
1720 ImageDescriptor desc = imager.getImage(columnKey);
\r
1721 if (desc != null) {
\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
1732 // Try resolving only cached images here and now
\r
1733 Object img = localResourceManager.find(desc);
\r
1735 img = resourceManager.find(desc);
\r
1737 descOrImage = img != null ? img : desc;
\r
1738 hasUncachedImages |= img == null;
\r
1741 if (!hasUncachedImages) {
\r
1742 cell.setImage((Image) descOrImage);
\r
1744 // Schedule loading to another thread to refrain from
\r
1746 // the UI with database operations.
\r
1747 queueImageTask(node, new ImageTask(node, descOrImage));
\r
1750 cell.setImage(null);
\r
1754 private void queueImageTask(TreeNode node, ImageTask task) {
\r
1755 synchronized (imageTasks) {
\r
1756 imageTasks.put(node, task);
\r
1758 imageLoaderJob.scheduleIfNecessary(100);
\r
1761 void setText(ViewerCell cell, String key) {
\r
1762 if (labeler != null) {
\r
1764 if (runtimeLabels != null)
\r
1765 s = runtimeLabels.get(key);
\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
1770 FontDescriptor font = originalFont;
\r
1771 ColorDescriptor bg = originalBackground;
\r
1772 ColorDescriptor fg = originalForeground;
\r
1774 // Attempt to decorate the label
\r
1775 if (!labelDecorators.isEmpty()) {
\r
1777 for (LabelDecorator ld : labelDecorators) {
\r
1778 String ds = ld.decorateLabel(s, key, index);
\r
1782 FontDescriptor dfont = ld.decorateFont(font, key, index);
\r
1783 if (dfont != null)
\r
1786 ColorDescriptor dbg = ld.decorateBackground(bg, key, index);
\r
1790 ColorDescriptor dfg = ld.decorateForeground(fg, key, index);
\r
1796 if (font != originalFont) {
\r
1797 // System.out.println("set font: " + index + ": " +
\r
1799 cell.setFont((Font) localResourceManager.get(font));
\r
1801 cell.setFont((Font) (originalFont != null ? localResourceManager.get(originalFont) : null));
\r
1803 if (bg != originalBackground)
\r
1804 cell.setBackground((Color) localResourceManager.get(bg));
\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
1810 cell.setForeground((Color) (originalForeground != null ? localResourceManager.get(originalForeground) : null));
\r
1815 cell.setText(Labeler.NO_LABEL);
\r
1823 private class GeEditingSupport extends EditingSupport {
\r
1824 private Object lastElement;
\r
1826 private Modifier lastModifier;
\r
1828 private int columnIndex;
\r
1829 public GeEditingSupport(ColumnViewer viewer, int columnIndex) {
\r
1831 this.columnIndex = columnIndex;
\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
1841 lastElement = null; // clear cached element + modifier.
\r
1842 Modifier modifier = getModifier((TreeNode)element);
\r
1843 if (modifier == null)
\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
1860 return startTextEditing(modifier);
\r
1866 protected Object getValue(Object element) {
\r
1867 Modifier modifier = getModifier((TreeNode)element);
\r
1868 return modifier.getValue();
\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
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
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
1891 return new EnumModifierEditor2(viewer.getTree(),modifier);
\r
1895 CellEditor performDialogEditing(final NodeContext context, final DialogModifier modifier) {
\r
1896 DialogCellEditor editor = new DialogCellEditor(viewer.getTree()) {
\r
1897 String res = null;
\r
1899 protected Object openDialogBox(Control cellEditorWindow) {
\r
1901 final Semaphore sem = new Semaphore(1);
\r
1902 Consumer<String> callback = result -> {
\r
1906 String status = modifier.query(cellEditorWindow, null, columnIndex, context, callback);
\r
1907 if (status != null)
\r
1911 } catch (InterruptedException e) {
\r
1912 e.printStackTrace();
\r
1919 editor.setValidator(new ModifierValidator(modifier));
\r
1923 CellEditor startCustomEditing(TreeNode node, CustomModifier modifier) {
\r
1924 CustomModifierEditor editor = new CustomModifierEditor(viewer.getTree(), modifier, node, columnIndex);
\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
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
1945 Column column = columns[columnIndex];
\r
1947 return labeler.getModifier(modificationContext, column.getKey());
\r
1951 static class ImageTask {
\r
1953 Object descsOrImage;
\r
1954 public ImageTask(TreeNode node, Object descsOrImage) {
\r
1956 this.descsOrImage = descsOrImage;
\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
1964 ImageLoaderJob imageLoaderJob;
\r
1966 // Map<NodeContext, ImageTask> imageTasks = new THashMap<NodeContext, ImageTask>();
\r
1967 Map<TreeNode, ImageTask> imageTasks = new THashMap<TreeNode, ImageTask>();
\r
1970 * Invoked in a job worker thread.
\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
1982 MultiStatus status = null;
\r
1984 // Load missing images
\r
1985 for (ImageTask task : tasks) {
\r
1986 Object desc = task.descsOrImage;
\r
1987 if (desc instanceof ImageDescriptor) {
\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
2000 // Perform final UI updates in the UI thread.
\r
2001 final ImageTask[] _tasks = tasks;
\r
2002 thread.asyncExec(new Runnable() {
\r
2004 public void run() {
\r
2005 setImages(_tasks);
\r
2009 return status != null ? status : Status.OK_STATUS;
\r
2013 void setImages(ImageTask[] tasks) {
\r
2014 for (ImageTask task : tasks)
\r
2019 void setImage(ImageTask task) {
\r
2020 if (!task.node.isDisposed())
\r
2021 update(task.node, 0);
\r
2024 private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider {
\r
2026 private GraphExplorerImpl2 ge;
\r
2028 GraphExplorerPostSelectionProvider(GraphExplorerImpl2 ge) {
\r
2037 public void setSelection(final ISelection selection) {
\r
2038 if(ge == null) return;
\r
2039 ge.setSelection(selection, false);
\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
2052 ge.selectionProvider.removeSelectionChangedListener(listener);
\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
2064 ge.selectionProvider.addPostSelectionChangedListener(listener);
\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
2075 ge.selectionProvider.removePostSelectionChangedListener(listener);
\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
2089 ge.selectionProvider.addSelectionChangedListener(listener);
\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
2105 static class ModifierValidator implements ICellEditorValidator {
\r
2106 private Modifier modifier;
\r
2107 public ModifierValidator(Modifier modifier) {
\r
2108 this.modifier = modifier;
\r
2112 public String isValid(Object value) {
\r
2113 return modifier.isValid((String)value);
\r
2117 static class UpdateRunner implements Runnable {
\r
2119 final GraphExplorerImpl2 ge;
\r
2121 UpdateRunner(GraphExplorerImpl2 ge, IGraphExplorerContext geContext) {
\r
2125 public void run() {
\r
2128 } catch (Throwable t) {
\r
2129 t.printStackTrace();
\r
2133 public void doRun() {
\r
2135 if (ge.isDisposed())
\r
2138 HashSet<UpdateItem> items;
\r
2140 ScrollBar verticalBar = ge.viewer.getTree().getVerticalBar();
\r
2143 synchronized (ge.pendingItems) {
\r
2144 items = ge.pendingItems;
\r
2145 ge.pendingItems = new HashSet<UpdateItem>();
\r
2147 if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size());
\r
2149 ge.viewer.getTree().setRedraw(false);
\r
2150 for (UpdateItem item : items) {
\r
2151 item.update(ge.viewer);
\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
2161 ge.viewer.getTree().setRedraw(true);
\r
2163 synchronized (ge.pendingItems) {
\r
2164 if (!ge.scheduleUpdater()) {
\r
2165 ge.updating = false;
\r
2169 if (!ge.updating) {
\r
2170 ge.printTree(ge.rootNode, 0);
\r
2177 private class ValidatedTextEditor extends TextCellEditor {
\r
2180 public ValidatedTextEditor(Composite parent) {
\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
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
2196 text.setBackground(null);
\r
2197 text.setToolTipText(null);
\r
2199 valueChanged(oldValidState, newValidState);
\r
2203 private class EnumModifierEditor2 extends ComboBoxCellEditor2 {
\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
2212 protected void doSetValue(Object value) {
\r
2213 super.doSetValue((Integer)values.indexOf(value));
\r
2217 protected Object doGetValue() {
\r
2218 return values.get((Integer)super.doGetValue());
\r
2222 private class EnumModifierEditor extends ComboBoxCellEditor {
\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
2231 protected void doSetValue(Object value) {
\r
2232 super.doSetValue((Integer)values.indexOf(value));
\r
2236 protected Object doGetValue() {
\r
2237 return values.get((Integer)super.doGetValue());
\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
2250 public CustomModifierEditor(Composite parent, CustomModifier modifier, TreeNode node, int columnIndex) {
\r
2251 this.modifier = modifier;
\r
2253 this.context = node.getContext();
\r
2254 this.columnIndex = columnIndex;
\r
2255 setValidator(this);
\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
2270 protected Object doGetValue() {
\r
2271 return modifier.getValue();
\r
2275 protected void doSetValue(Object value) {
\r
2276 //CustomModifier handles value setting internally.
\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
2289 protected void doSetFocus() {
\r
2290 if (control != null && !control.isDisposed())
\r
2291 control.setFocus();
\r
2295 public void widgetDisposed(DisposeEvent e) {
\r
2296 if (e.widget == origControl) {
\r
2303 public String isValid(Object value) {
\r
2304 return modifier.isValid((String)value);
\r
2309 private class TreeNode implements IAdaptable {
\r
2310 private NodeContext context;
\r
2312 private TreeNode parent;
\r
2313 private List<TreeNode> children = new ArrayList<TreeNode>();
\r
2314 private GENodeQueryManager manager;
\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
2324 public List<TreeNode> getChildren() {
\r
2325 synchronized (children) {
\r
2330 public TreeNode getParent() {
\r
2334 public NodeContext getContext() {
\r
2338 public GENodeQueryManager getManager() {
\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
2350 public TreeNode addChild(int index, NodeContext context) {
\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
2359 public TreeNode setChild(int index, NodeContext context) {
\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
2368 public int distanceToRoot() {
\r
2370 TreeNode n = getParent();
\r
2371 while (n != null) {
\r
2372 n = n.getParent();
\r
2379 public void dispose() {
\r
2380 if (parent != null)
\r
2381 parent.children.remove(this);
\r
2385 public void dispose2() {
\r
2386 if (DEBUG) System.out.println("dispose " + this);
\r
2388 for (TreeNode n : children) {
\r
2393 contextToNodeMap.remove(context, this);
\r
2395 manager.dispose();
\r
2399 private void clearCache() {
\r
2400 if (explorerContext != null) {
\r
2401 GECache2 cache = explorerContext.cache;
\r
2403 if (cache != null) {
\r
2404 cache.dispose(context);
\r
2409 public boolean updateChildren() {
\r
2410 if (context == null)
\r
2411 throw new IllegalStateException("Node is disposed.");
\r
2413 NodeContext[] childContexts = manager.query(context, BuiltinKeys.FINAL_CHILDREN);
\r
2415 if (DEBUG) System.out.println("updateChildren " + childContexts.length + " " + this);
\r
2418 boolean modified = false;
\r
2419 synchronized (children) {
\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
2431 if (oldCtx.equals(childContexts[j])) {
\r
2432 indexes.map(i, j);
\r
2440 // update children if required
\r
2441 if (childContexts.length != oldCount || reorder || childContexts.length != indexes.size()) {
\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
2451 TreeNode n = oldChildren.get(oldIndex);
\r
2452 children.set(i, n);
\r
2456 for (int i = oldCount; i < childContexts.length; i++) {
\r
2457 addChild(childContexts[i]);
\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
2465 TreeNode n = oldChildren.get(oldIndex);
\r
2466 children.set(i, n);
\r
2469 for (int i = oldCount -1; i >= childContexts.length; i--) {
\r
2470 children.remove(i);
\r
2473 for (int i = 0; i < oldChildren.size(); i++) {
\r
2474 if (!indexes.containsLeft(i)) {
\r
2475 oldChildren.get(i).dispose2();
\r
2485 public boolean isDisposed() {
\r
2486 return context == null;
\r
2489 @SuppressWarnings("rawtypes")
\r
2491 public Object getAdapter(Class adapter) {
\r
2492 if (adapter == NodeContext.class)
\r
2494 return context.getAdapter(adapter);
\r
2498 // public String toString() {
\r
2500 // if (manager != null) {
\r
2502 // s+= super.toString() + " ";
\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
2509 // } catch (Exception e) {
\r
2513 // s = super.toString();
\r
2515 // if (context != null)
\r
2516 // s += " context " + context.hashCode();
\r
2523 private static class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor<Boolean> implements
\r
2524 IsExpandedProcessor, ProcessorLifecycle {
\r
2526 * The set of currently expanded node contexts.
\r
2528 private final HashSet<NodeContext> expanded = new HashSet<NodeContext>();
\r
2529 private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();
\r
2531 private Tree tree;
\r
2533 public TreeNodeIsExpandedProcessor() {
\r
2537 public Object getIdentifier() {
\r
2538 return BuiltinKeys.IS_EXPANDED;
\r
2542 public String toString() {
\r
2543 return "IsExpandedProcessor";
\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
2554 public Collection<NodeContext> getExpanded() {
\r
2555 return new HashSet<NodeContext>(expanded);
\r
2559 public boolean getExpanded(NodeContext context) {
\r
2560 return this.expanded.contains(context);
\r
2564 public boolean setExpanded(NodeContext context, boolean expanded) {
\r
2565 return _setExpanded(context, expanded);
\r
2569 public boolean replaceExpanded(NodeContext context, boolean expanded) {
\r
2570 return nodeStatusChanged(context, expanded);
\r
2573 private boolean _setExpanded(NodeContext context, boolean expanded) {
\r
2575 return this.expanded.add(context);
\r
2577 return this.expanded.remove(context);
\r
2581 Listener treeListener = new Listener() {
\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
2588 nodeStatusChanged(context, true);
\r
2590 case SWT.Collapse:
\r
2591 nodeStatusChanged(context, false);
\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
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
2613 System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control);
\r
2618 public void clear() {
\r
2620 expandedQueries.clear();
\r
2624 public void detached(GraphExplorer explorer) {
\r
2626 if (tree != null) {
\r
2627 tree.removeListener(SWT.Expand, treeListener);
\r
2628 tree.removeListener(SWT.Collapse, treeListener);
\r
2634 private void printTree(TreeNode node, int depth) {
\r
2636 for (int i = 0; i < depth; i++) {
\r
2640 System.out.println(s);
\r
2642 for (TreeNode n : node.getChildren()) {
\r
2649 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used)
\r
2651 final private static class GECacheKey {
\r
2653 private NodeContext context;
\r
2654 private CacheKey<?> key;
\r
2656 GECacheKey(NodeContext context, CacheKey<?> key) {
\r
2657 this.context = context;
\r
2659 if (context == null || key == null)
\r
2660 throw new IllegalArgumentException("Null context or key is not accepted");
\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
2670 void setValues(NodeContext context, CacheKey<?> key) {
\r
2671 this.context = context;
\r
2673 if (context == null || key == null)
\r
2674 throw new IllegalArgumentException("Null context or key is not accepted");
\r
2678 public int hashCode() {
\r
2679 return context.hashCode() | key.hashCode();
\r
2683 public boolean equals(Object object) {
\r
2685 if (this == object)
\r
2687 else if (object == null)
\r
2690 GECacheKey i = (GECacheKey) object;
\r
2692 return key.equals(i.key) && context.equals(i.context);
\r
2699 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data.
\r
2701 private static class GECache2 implements IGECache {
\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
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
2713 NodeContext getNC = new NodeContext() {
\r
2714 @SuppressWarnings("rawtypes")
\r
2716 public Object getAdapter(Class adapter) {
\r
2721 public <T> T getConstant(ConstantKey<T> key) {
\r
2726 public Set<ConstantKey<?>> getKeys() {
\r
2727 return Collections.emptySet();
\r
2730 CacheKey<?> getCK = new CacheKey<Object>() {
\r
2732 public Object processorIdenfitier() {
\r
2736 GECacheKey getKey = new GECacheKey(getNC, getCK);
\r
2739 private void addKey(GECacheKey key) {
\r
2740 Set<GECacheKey> refs = keyRefs.get(key.context);
\r
2741 if (refs != null) {
\r
2744 refs = new HashSet<GECacheKey>();
\r
2746 keyRefs.put(key.context, refs);
\r
2750 private void removeKey(GECacheKey key) {
\r
2751 Set<GECacheKey> refs = keyRefs.get(key.context);
\r
2752 if (refs != null) {
\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
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
2772 return (T) entry.getValue();
\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
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
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
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
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
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
2828 public boolean isShown(NodeContext context) {
\r
2829 return references.get(context) > 0;
\r
2832 private TObjectIntHashMap<NodeContext> references = new TObjectIntHashMap<NodeContext>();
\r
2835 public void incRef(NodeContext context) {
\r
2836 int exist = references.get(context);
\r
2837 references.put(context, exist+1);
\r
2841 public void decRef(NodeContext context) {
\r
2842 int exist = references.get(context);
\r
2843 references.put(context, exist-1);
\r
2845 references.remove(context);
\r
2849 public void dispose() {
\r
2850 references.clear();
\r
2852 treeReferences.clear();
\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
2869 * Non-functional cache to replace actual cache when GEContext is disposed.
\r
2874 private static class DummyCache extends GECache2 {
\r
2877 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
\r
2882 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key,
\r
2888 public <T> void putTreeReference(NodeContext context, CacheKey<T> key,
\r
2889 UIElementReference reference) {
\r
2893 public <T> T get(NodeContext context, CacheKey<T> key) {
\r
2898 public <T> Set<UIElementReference> getTreeReference(
\r
2899 NodeContext context, CacheKey<T> key) {
\r
2904 public <T> void remove(NodeContext context, CacheKey<T> key) {
\r
2909 public <T> Set<UIElementReference> removeTreeReference(
\r
2910 NodeContext context, CacheKey<T> key) {
\r
2915 public boolean isShown(NodeContext context) {
\r
2920 public void incRef(NodeContext context) {
\r
2925 public void decRef(NodeContext context) {
\r
2930 public void dispose() {
\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
2942 // No selectable item at point?
\r
2946 Object data = item.getData();
\r