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.RGB;
\r
92 import org.eclipse.swt.layout.FillLayout;
\r
93 import org.eclipse.swt.widgets.Composite;
\r
94 import org.eclipse.swt.widgets.Control;
\r
95 import org.eclipse.swt.widgets.Display;
\r
96 import org.eclipse.swt.widgets.Event;
\r
97 import org.eclipse.swt.widgets.Listener;
\r
98 import org.eclipse.swt.widgets.ScrollBar;
\r
99 import org.eclipse.swt.widgets.Tree;
\r
100 import org.eclipse.swt.widgets.TreeColumn;
\r
101 import org.eclipse.ui.PlatformUI;
\r
102 import org.eclipse.ui.contexts.IContextActivation;
\r
103 import org.eclipse.ui.contexts.IContextService;
\r
104 import org.eclipse.ui.services.IServiceLocator;
\r
105 import org.eclipse.ui.swt.IFocusService;
\r
106 import org.simantics.browsing.ui.BuiltinKeys;
\r
107 import org.simantics.browsing.ui.Column;
\r
108 import org.simantics.browsing.ui.Column.Align;
\r
109 import org.simantics.browsing.ui.DataSource;
\r
110 import org.simantics.browsing.ui.ExplorerState;
\r
111 import org.simantics.browsing.ui.GraphExplorer;
\r
112 import org.simantics.browsing.ui.NodeContext;
\r
113 import org.simantics.browsing.ui.NodeContext.CacheKey;
\r
114 import org.simantics.browsing.ui.NodeContext.PrimitiveQueryKey;
\r
115 import org.simantics.browsing.ui.NodeContext.QueryKey;
\r
116 import org.simantics.browsing.ui.NodeQueryManager;
\r
117 import org.simantics.browsing.ui.NodeQueryProcessor;
\r
118 import org.simantics.browsing.ui.PrimitiveQueryProcessor;
\r
119 import org.simantics.browsing.ui.PrimitiveQueryUpdater;
\r
120 import org.simantics.browsing.ui.SelectionDataResolver;
\r
121 import org.simantics.browsing.ui.SelectionFilter;
\r
122 import org.simantics.browsing.ui.StatePersistor;
\r
123 import org.simantics.browsing.ui.common.ColumnKeys;
\r
124 import org.simantics.browsing.ui.common.ErrorLogger;
\r
125 import org.simantics.browsing.ui.common.NodeContextBuilder;
\r
126 import org.simantics.browsing.ui.common.NodeContextUtil;
\r
127 import org.simantics.browsing.ui.common.internal.GENodeQueryManager;
\r
128 import org.simantics.browsing.ui.common.internal.IGECache;
\r
129 import org.simantics.browsing.ui.common.internal.IGraphExplorerContext;
\r
130 import org.simantics.browsing.ui.common.internal.UIElementReference;
\r
131 import org.simantics.browsing.ui.common.processors.AbstractPrimitiveQueryProcessor;
\r
132 import org.simantics.browsing.ui.common.processors.DefaultCheckedStateProcessor;
\r
133 import org.simantics.browsing.ui.common.processors.DefaultComparableChildrenProcessor;
\r
134 import org.simantics.browsing.ui.common.processors.DefaultFinalChildrenProcessor;
\r
135 import org.simantics.browsing.ui.common.processors.DefaultImageDecoratorProcessor;
\r
136 import org.simantics.browsing.ui.common.processors.DefaultImagerFactoriesProcessor;
\r
137 import org.simantics.browsing.ui.common.processors.DefaultImagerProcessor;
\r
138 import org.simantics.browsing.ui.common.processors.DefaultLabelDecoratorProcessor;
\r
139 import org.simantics.browsing.ui.common.processors.DefaultLabelerFactoriesProcessor;
\r
140 import org.simantics.browsing.ui.common.processors.DefaultLabelerProcessor;
\r
141 import org.simantics.browsing.ui.common.processors.DefaultPrunedChildrenProcessor;
\r
142 import org.simantics.browsing.ui.common.processors.DefaultSelectedImageDecoratorFactoriesProcessor;
\r
143 import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelDecoratorFactoriesProcessor;
\r
144 import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelerProcessor;
\r
145 import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointFactoryProcessor;
\r
146 import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointProcessor;
\r
147 import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionProcessor;
\r
148 import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionsProcessor;
\r
149 import org.simantics.browsing.ui.common.processors.DefaultViewpointProcessor;
\r
150 import org.simantics.browsing.ui.common.processors.IsExpandedProcessor;
\r
151 import org.simantics.browsing.ui.common.processors.NoSelectionRequestProcessor;
\r
152 import org.simantics.browsing.ui.common.processors.ProcessorLifecycle;
\r
153 import org.simantics.browsing.ui.content.ImageDecorator;
\r
154 import org.simantics.browsing.ui.content.Imager;
\r
155 import org.simantics.browsing.ui.content.LabelDecorator;
\r
156 import org.simantics.browsing.ui.content.Labeler;
\r
157 import org.simantics.browsing.ui.content.Labeler.CustomModifier;
\r
158 import org.simantics.browsing.ui.content.Labeler.DialogModifier;
\r
159 import org.simantics.browsing.ui.content.Labeler.EnumerationModifier;
\r
160 import org.simantics.browsing.ui.content.Labeler.Modifier;
\r
161 import org.simantics.browsing.ui.swt.internal.Threads;
\r
162 import org.simantics.db.layer0.SelectionHints;
\r
163 import org.simantics.ui.SimanticsUI;
\r
164 import org.simantics.utils.datastructures.BijectionMap;
\r
165 import org.simantics.utils.datastructures.BinaryFunction;
\r
166 import org.simantics.utils.datastructures.MapList;
\r
167 import org.simantics.utils.datastructures.disposable.AbstractDisposable;
\r
168 import org.simantics.utils.datastructures.hints.IHintContext;
\r
169 import org.simantics.utils.threads.IThreadWorkQueue;
\r
170 import org.simantics.utils.threads.SWTThread;
\r
171 import org.simantics.utils.threads.ThreadUtils;
\r
172 import org.simantics.utils.ui.AdaptionUtils;
\r
173 import org.simantics.utils.ui.ISelectionUtils;
\r
174 import org.simantics.utils.ui.jface.BasePostSelectionProvider;
\r
176 import gnu.trove.map.hash.THashMap;
\r
177 import gnu.trove.map.hash.TObjectIntHashMap;
\r
180 * TreeView based GraphExplorer
\r
183 * @author Marko Luukkainen <marko.luukkainen@vtt.fi>
\r
185 public class GraphExplorerImpl2 extends GraphExplorerImplBase implements GraphExplorer {
\r
187 private static final boolean DEBUG_SELECTION_LISTENERS = false;
\r
188 private static final boolean DEBUG = false;
\r
190 private TreeViewer viewer;
\r
192 private LocalResourceManager localResourceManager;
\r
193 private DeviceResourceManager resourceManager;
\r
196 private IThreadWorkQueue thread;
\r
198 @SuppressWarnings({ "rawtypes" })
\r
199 final HashMap<CacheKey<?>, NodeQueryProcessor> processors = new HashMap<CacheKey<?>, NodeQueryProcessor>();
\r
200 @SuppressWarnings({ "rawtypes" })
\r
201 final HashMap<Object, PrimitiveQueryProcessor> primitiveProcessors = new HashMap<Object, PrimitiveQueryProcessor>();
\r
202 @SuppressWarnings({ "rawtypes" })
\r
203 final HashMap<Class, DataSource> dataSources = new HashMap<Class, DataSource>();
\r
205 private FontDescriptor originalFont;
\r
206 protected ColorDescriptor originalForeground;
\r
207 protected ColorDescriptor originalBackground;
\r
208 private Color invalidModificationColor;
\r
210 private Column[] columns;
\r
211 private Map<String,Integer> columnKeyToIndex;
\r
212 private boolean columnsAreVisible = true;
\r
215 private NodeContext rootContext;
\r
216 private TreeNode rootNode;
\r
217 private StatePersistor persistor = null;
\r
219 private boolean editable = true;
\r
221 private boolean disposed = false;
\r
223 private final CopyOnWriteArrayList<FocusListener> focusListeners = new CopyOnWriteArrayList<FocusListener>();
\r
224 private final CopyOnWriteArrayList<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();
\r
225 private final CopyOnWriteArrayList<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>();
\r
227 private int autoExpandLevel = 0;
\r
228 private IServiceLocator serviceLocator;
\r
229 private IContextService contextService = null;
\r
230 private IFocusService focusService = null;
\r
231 private IContextActivation editingContext = null;
\r
233 GeViewerContext explorerContext = new GeViewerContext(this);
\r
235 private GraphExplorerPostSelectionProvider postSelectionProvider = new GraphExplorerPostSelectionProvider(this);
\r
236 private BasePostSelectionProvider selectionProvider = new BasePostSelectionProvider();
\r
237 private SelectionDataResolver selectionDataResolver;
\r
238 private SelectionFilter selectionFilter;
\r
240 private Set<TreeNode> collapsedNodes = new HashSet<TreeNode>();
\r
241 private MapList<NodeContext, TreeNode> contextToNodeMap = new MapList<NodeContext, TreeNode>();
\r
243 private ModificationContext modificationContext = null;
\r
245 private boolean filterSelectionEdit = true;
\r
247 private TreeColumnLayout treeColumnLayout;
\r
249 private boolean expand;
\r
250 private boolean verticalBarVisible = false;
\r
252 private BinaryFunction<Object[], GraphExplorer, Object[]> selectionTransformation = new BinaryFunction<Object[], GraphExplorer, Object[]>() {
\r
255 public Object[] call(GraphExplorer explorer, Object[] objects) {
\r
256 Object[] result = new Object[objects.length];
\r
257 for (int i = 0; i < objects.length; i++) {
\r
258 IHintContext context = new AdaptableHintContext(SelectionHints.KEY_MAIN);
\r
259 context.setHint(SelectionHints.KEY_MAIN, objects[i]);
\r
260 result[i] = context;
\r
267 static class TransientStateImpl implements TransientExplorerState {
\r
269 private Integer activeColumn = null;
\r
272 public synchronized Integer getActiveColumn() {
\r
273 return activeColumn;
\r
276 public synchronized void setActiveColumn(Integer column) {
\r
277 activeColumn = column;
\r
282 private TransientStateImpl transientState = new TransientStateImpl();
\r
285 public GraphExplorerImpl2(Composite parent) {
\r
286 this(parent, SWT.BORDER | SWT.MULTI );
\r
289 public GraphExplorerImpl2(Composite parent, int style) {
\r
290 this.localResourceManager = new LocalResourceManager(JFaceResources.getResources());
\r
291 this.resourceManager = new DeviceResourceManager(parent.getDisplay());
\r
293 this.imageLoaderJob = new ImageLoaderJob(this);
\r
294 this.imageLoaderJob.setPriority(Job.DECORATE);
\r
296 invalidModificationColor = (Color) localResourceManager.get(ColorDescriptor.createFrom(new RGB(255, 128, 128)));
\r
298 this.thread = SWTThread.getThreadAccess(parent);
\r
300 for (int i = 0; i < 10; i++)
\r
301 explorerContext.activity.push(0);
\r
303 boolean useLayout = true;
\r
304 // FIXME: hack, GraphExplorerComposite uses its own TreeColumnLayout.
\r
305 if (useLayout && !(parent.getLayout() instanceof TreeColumnLayout)) {
\r
307 Composite rootTreeComposite = new Composite(parent, SWT.NONE);
\r
308 treeColumnLayout = new TreeColumnLayout();
\r
309 rootTreeComposite.setLayout(treeColumnLayout);
\r
311 viewer = new TreeViewer(rootTreeComposite,style|SWT.H_SCROLL|SWT.V_SCROLL);
\r
313 GridDataFactory.fillDefaults().grab(true, true).span(3,1).applyTo(rootTreeComposite);
\r
316 viewer = new TreeViewer(parent,style | SWT.H_SCROLL | SWT.V_SCROLL);
\r
319 viewer.getColumnViewerEditor().addEditorActivationListener(new ColumnViewerEditorActivationListener() {
\r
322 public void beforeEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {
\r
327 public void beforeEditorActivated(ColumnViewerEditorActivationEvent event) {
\r
328 // cancel editor activation for double click events.
\r
329 // TODO: this may not work similarly to GraphExplorerImpl
\r
330 if ((event.time - focusGainedAt) < 250L) {
\r
331 event.cancel = true;
\r
336 public void afterEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {
\r
341 public void afterEditorActivated(ColumnViewerEditorActivationEvent event) {
\r
346 viewer.setUseHashlookup(true);
\r
347 viewer.setContentProvider(new GeViewerContentProvider());
\r
351 originalFont = JFaceResources.getDefaultFontDescriptor();
\r
353 viewer.getTree().setFont((Font) localResourceManager.get(originalFont));
\r
355 setBasicListeners();
\r
356 setDefaultProcessors();
\r
358 viewer.getTree().addDisposeListener(new DisposeListener() {
\r
361 public void widgetDisposed(DisposeEvent e) {
\r
368 // Add listener to tree for delayed tree population.
\r
370 Listener listener = new Listener() {
\r
373 public void handleEvent(Event event) {
\r
375 switch (event.type) {
\r
384 case SWT.Deactivate:
\r
391 viewer.getTree().addListener(SWT.Activate, listener);
\r
392 viewer.getTree().addListener(SWT.Deactivate, listener);
\r
393 viewer.getTree().addListener(SWT.Show, listener);
\r
394 viewer.getTree().addListener(SWT.Hide, listener);
\r
395 viewer.getTree().addListener(SWT.Paint,listener);
\r
398 viewer.addTreeListener(new ITreeViewerListener() {
\r
401 public void treeExpanded(TreeExpansionEvent event) {
\r
406 public void treeCollapsed(TreeExpansionEvent event) {
\r
407 collapsedNodes.add((TreeNode)event.getElement());
\r
411 setColumns( new Column[] { new Column(ColumnKeys.SINGLE) });
\r
414 private long focusGainedAt = 0L;
\r
415 private boolean visible = false;
\r
417 private Collection<TreeNode> selectedNodes = new ArrayList<TreeNode>();
\r
419 protected void setBasicListeners() {
\r
420 Tree tree = viewer.getTree();
\r
422 tree.addFocusListener(new FocusListener() {
\r
424 public void focusGained(FocusEvent e) {
\r
425 focusGainedAt = ((long) e.time) & 0xFFFFFFFFL;
\r
426 for (FocusListener listener : focusListeners)
\r
427 listener.focusGained(e);
\r
430 public void focusLost(FocusEvent e) {
\r
431 for (FocusListener listener : focusListeners)
\r
432 listener.focusLost(e);
\r
435 tree.addMouseListener(new MouseListener() {
\r
437 public void mouseDoubleClick(MouseEvent e) {
\r
438 for (MouseListener listener : mouseListeners) {
\r
439 listener.mouseDoubleClick(e);
\r
443 public void mouseDown(MouseEvent e) {
\r
444 for (MouseListener listener : mouseListeners) {
\r
445 listener.mouseDown(e);
\r
449 public void mouseUp(MouseEvent e) {
\r
450 for (MouseListener listener : mouseListeners) {
\r
451 listener.mouseUp(e);
\r
455 tree.addKeyListener(new KeyListener() {
\r
457 public void keyPressed(KeyEvent e) {
\r
458 for (KeyListener listener : keyListeners) {
\r
459 listener.keyPressed(e);
\r
463 public void keyReleased(KeyEvent e) {
\r
464 for (KeyListener listener : keyListeners) {
\r
465 listener.keyReleased(e);
\r
470 viewer.addSelectionChangedListener(new ISelectionChangedListener() {
\r
473 public void selectionChanged(SelectionChangedEvent event) {
\r
474 //System.out.println("GraphExplorerImpl2.fireSelection");
\r
475 selectedNodes = AdaptionUtils.adaptToCollection(event.getSelection(), TreeNode.class);
\r
476 Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);
\r
477 selectionProvider.setAndFireSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));
\r
481 viewer.addPostSelectionChangedListener(new ISelectionChangedListener() {
\r
484 public void selectionChanged(SelectionChangedEvent event) {
\r
485 //System.out.println("GraphExplorerImpl2.firePostSelection");
\r
486 Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);
\r
487 selectionProvider.firePostSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));
\r
494 private NodeContext pendingRoot;
\r
496 private void activate() {
\r
497 if (pendingRoot != null && !expand) {
\r
498 doSetRoot(pendingRoot);
\r
499 pendingRoot = null;
\r
504 * Invoke only from SWT thread to reset the root of the graph explorer tree.
\r
508 private void doSetRoot(NodeContext root) {
\r
509 Display display = viewer.getTree().getDisplay();
\r
510 if (display.getThread() != Thread.currentThread()) {
\r
511 throw new RuntimeException("Invoke from SWT thread only");
\r
513 // System.out.println("doSetRoot " + root);
\r
516 if (viewer.getTree().isDisposed())
\r
518 if (root.getConstant(BuiltinKeys.INPUT) == null) {
\r
519 ErrorLogger.defaultLogError("root node context does not contain BuiltinKeys.INPUT key. Node = " + root, new Exception("trace"));
\r
525 // Empty caches, release queries.
\r
526 if (rootNode != null) {
\r
527 rootNode.dispose();
\r
529 GeViewerContext oldContext = explorerContext;
\r
530 GeViewerContext newContext = new GeViewerContext(this);
\r
531 this.explorerContext = newContext;
\r
532 oldContext.safeDispose();
\r
534 // Need to empty these or otherwise they won't be emptied until the
\r
535 // explorer is disposed which would mean that many unwanted references
\r
536 // will be held by this map.
\r
537 clearPrimitiveProcessors();
\r
539 this.rootContext = root.getConstant(BuiltinKeys.IS_ROOT) != null ? root
\r
540 : NodeContextUtil.withConstant(root, BuiltinKeys.IS_ROOT, Boolean.TRUE);
\r
542 explorerContext.getCache().incRef(this.rootContext);
\r
547 select(rootContext);
\r
548 //refreshColumnSizes();
\r
549 rootNode = new TreeNode(rootContext);
\r
550 if (DEBUG) System.out.println("setRoot " + rootNode);
\r
552 viewer.setInput(rootNode);
\r
554 // Delay content reading.
\r
556 // This is required for cases when GEImpl2 is attached to selection view. Reading content
\r
557 // instantly could stagnate SWT thread under rapid changes in selection. By delaying the
\r
558 // content reading we give the system a change to dispose the GEImpl2 before the content is read.
\r
559 display.asyncExec(new Runnable() {
\r
562 public void run() {
\r
563 if (rootNode != null) {
\r
564 rootNode.updateChildren();
\r
571 private void initializeState() {
\r
572 if (persistor == null)
\r
575 ExplorerState state = persistor.deserialize(
\r
576 Platform.getStateLocation(Activator.getDefault().getBundle()).toFile(),
\r
580 Object processor = getPrimitiveProcessor(BuiltinKeys.IS_EXPANDED);
\r
581 if (processor instanceof DefaultIsExpandedProcessor) {
\r
582 DefaultIsExpandedProcessor isExpandedProcessor = (DefaultIsExpandedProcessor)processor;
\r
583 for(NodeContext expanded : state.expandedNodes) {
\r
584 isExpandedProcessor.setExpanded(expanded, true);
\r
590 public NodeContext getRoot() {
\r
591 return rootContext;
\r
595 public IThreadWorkQueue getThread() {
\r
600 public NodeContext getParentContext(NodeContext context) {
\r
602 throw new IllegalStateException("disposed");
\r
603 if (!thread.currentThreadAccess())
\r
604 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
\r
606 List<TreeNode> nodes = contextToNodeMap.getValuesUnsafe(context);
\r
607 for (int i = 0; i < nodes.size(); i++) {
\r
608 if (nodes.get(i).getParent() != null)
\r
609 return nodes.get(i).getParent().getContext();
\r
616 @SuppressWarnings("unchecked")
\r
618 public <T> T getAdapter(Class<T> adapter) {
\r
619 if(ISelectionProvider.class == adapter) return (T) postSelectionProvider;
\r
620 else if(IPostSelectionProvider.class == adapter) return (T) postSelectionProvider;
\r
625 protected void setDefaultProcessors() {
\r
626 // Add a simple IMAGER query processor that always returns null.
\r
627 // With this processor no images will ever be shown.
\r
628 // setPrimitiveProcessor(new StaticImagerProcessor(null));
\r
630 setProcessor(new DefaultComparableChildrenProcessor());
\r
631 setProcessor(new DefaultLabelDecoratorsProcessor());
\r
632 setProcessor(new DefaultImageDecoratorsProcessor());
\r
633 setProcessor(new DefaultSelectedLabelerProcessor());
\r
634 setProcessor(new DefaultLabelerFactoriesProcessor());
\r
635 setProcessor(new DefaultSelectedImagerProcessor());
\r
636 setProcessor(new DefaultImagerFactoriesProcessor());
\r
637 setPrimitiveProcessor(new DefaultLabelerProcessor());
\r
638 setPrimitiveProcessor(new DefaultCheckedStateProcessor());
\r
639 setPrimitiveProcessor(new DefaultImagerProcessor());
\r
640 setPrimitiveProcessor(new DefaultLabelDecoratorProcessor());
\r
641 setPrimitiveProcessor(new DefaultImageDecoratorProcessor());
\r
642 setPrimitiveProcessor(new NoSelectionRequestProcessor());
\r
644 setProcessor(new DefaultFinalChildrenProcessor(this));
\r
646 setProcessor(new DefaultPrunedChildrenProcessor());
\r
647 setProcessor(new DefaultSelectedViewpointProcessor());
\r
648 setProcessor(new DefaultSelectedLabelDecoratorFactoriesProcessor());
\r
649 setProcessor(new DefaultSelectedImageDecoratorFactoriesProcessor());
\r
650 setProcessor(new DefaultViewpointContributionsProcessor());
\r
652 setPrimitiveProcessor(new DefaultViewpointProcessor());
\r
653 setPrimitiveProcessor(new DefaultViewpointContributionProcessor());
\r
654 setPrimitiveProcessor(new DefaultSelectedViewpointFactoryProcessor());
\r
655 setPrimitiveProcessor(new TreeNodeIsExpandedProcessor());
\r
656 setPrimitiveProcessor(new DefaultShowMaxChildrenProcessor());
\r
660 public Column[] getColumns() {
\r
661 return Arrays.copyOf(columns, columns.length);
\r
665 public void setColumnsVisible(boolean visible) {
\r
666 columnsAreVisible = visible;
\r
667 if(viewer.getTree() != null) viewer.getTree().setHeaderVisible(columnsAreVisible);
\r
671 public void setColumns(final Column[] columns) {
\r
672 setColumns(columns, null);
\r
676 public void setColumns(final Column[] columns, Consumer<Map<Column, Object>> callback) {
\r
677 assertNotDisposed();
\r
678 checkUniqueColumnKeys(columns);
\r
680 Display d = viewer.getTree().getDisplay();
\r
681 if (d.getThread() == Thread.currentThread()) {
\r
682 doSetColumns(columns, callback);
\r
683 viewer.refresh(true);
\r
685 d.asyncExec(new Runnable() {
\r
687 public void run() {
\r
688 if (viewer == null)
\r
690 if (viewer.getTree().isDisposed())
\r
692 doSetColumns(columns, callback);
\r
693 viewer.refresh(true);
\r
694 viewer.getTree().getParent().layout();
\r
699 private void checkUniqueColumnKeys(Column[] cols) {
\r
700 Set<String> usedColumnKeys = new HashSet<String>();
\r
701 List<Column> duplicateColumns = new ArrayList<Column>();
\r
702 for (Column c : cols) {
\r
703 if (!usedColumnKeys.add(c.getKey()))
\r
704 duplicateColumns.add(c);
\r
706 if (!duplicateColumns.isEmpty()) {
\r
707 throw new IllegalArgumentException("All columns do not have unique keys: " + cols + ", overlapping: " + duplicateColumns);
\r
711 private List<TreeViewerColumn> treeViewerColumns = new ArrayList<TreeViewerColumn>();
\r
712 private CellLabelProvider cellLabelProvider = new GeViewerLabelProvider();
\r
713 private List<EditingSupport> editingSupports = new ArrayList<EditingSupport>();
\r
715 private void doSetColumns(Column[] cols, Consumer<Map<Column, Object>> callback) {
\r
716 // Attempt to keep previous column widths.
\r
717 Map<String, Integer> prevWidths = new HashMap<String, Integer>();
\r
719 for (TreeViewerColumn c : treeViewerColumns) {
\r
720 prevWidths.put(c.getColumn().getText(), c.getColumn().getWidth());
\r
721 c.getColumn().dispose();
\r
724 treeViewerColumns.clear();
\r
726 HashMap<String, Integer> keyToIndex = new HashMap<String, Integer>();
\r
727 for (int i = 0; i < cols.length; ++i) {
\r
728 keyToIndex.put(cols[i].getKey(), i);
\r
731 this.columns = Arrays.copyOf(cols, cols.length);
\r
732 //this.columns[cols.length] = FILLER_COLUMN;
\r
733 this.columnKeyToIndex = keyToIndex;
\r
735 Map<Column, Object> map = new HashMap<Column, Object>();
\r
737 // FIXME : temporary workaround for ModelBrowser.
\r
738 viewer.getTree().setHeaderVisible(columns.length == 1 ? false : columnsAreVisible);
\r
740 int columnIndex = 0;
\r
742 for (Column column : columns) {
\r
743 TreeViewerColumn tvc = new TreeViewerColumn(viewer, toSWT(column.getAlignment()));
\r
744 treeViewerColumns.add(tvc);
\r
745 tvc.setLabelProvider(cellLabelProvider);
\r
746 EditingSupport support = null;
\r
747 if (editingSupports.size() > columnIndex)
\r
748 support = editingSupports.get(columnIndex);
\r
750 support = new GeEditingSupport(viewer, columnIndex);
\r
751 editingSupports.add(support);
\r
754 tvc.setEditingSupport(support);
\r
756 TreeColumn c = tvc.getColumn();
\r
757 map.put(column, c);
\r
759 c.setText(column.getLabel());
\r
760 c.setToolTipText(column.getTooltip());
\r
762 int cw = column.getWidth();
\r
764 // Try to keep previous widths
\r
765 Integer w = prevWidths.get(column);
\r
768 else if (cw != Column.DEFAULT_CONTROL_WIDTH)
\r
770 else if (columns.length == 1) {
\r
771 // FIXME : how to handle single column properly?
\r
775 // Go for some kind of default settings then...
\r
776 if (ColumnKeys.PROPERTY.equals(column.getKey()))
\r
781 if (treeColumnLayout != null) {
\r
782 treeColumnLayout.setColumnData(c, new ColumnWeightData(column.getWeight(), true));
\r
785 // if (!column.hasGrab() && !FILLER.equals(column.getKey())) {
\r
786 // c.addListener(SWT.Resize, resizeListener);
\r
787 // c.setResizable(true);
\r
789 // //c.setResizable(false);
\r
796 if(callback != null) callback.accept(map);
\r
799 int toSWT(Align alignment) {
\r
800 switch (alignment) {
\r
801 case LEFT: return SWT.LEFT;
\r
802 case CENTER: return SWT.CENTER;
\r
803 case RIGHT: return SWT.RIGHT;
\r
804 default: throw new Error("unhandled alignment: " + alignment);
\r
809 public <T> void setProcessor(NodeQueryProcessor<T> processor) {
\r
810 assertNotDisposed();
\r
811 if (processor == null)
\r
812 throw new IllegalArgumentException("null processor");
\r
814 processors.put(processor.getIdentifier(), processor);
\r
818 public <T> void setPrimitiveProcessor(PrimitiveQueryProcessor<T> processor) {
\r
819 assertNotDisposed();
\r
820 if (processor == null)
\r
821 throw new IllegalArgumentException("null processor");
\r
823 PrimitiveQueryProcessor<?> oldProcessor = primitiveProcessors.put(
\r
824 processor.getIdentifier(), processor);
\r
826 if (oldProcessor instanceof ProcessorLifecycle)
\r
827 ((ProcessorLifecycle) oldProcessor).detached(this);
\r
828 if (processor instanceof ProcessorLifecycle)
\r
829 ((ProcessorLifecycle) processor).attached(this);
\r
833 public <T> void setDataSource(DataSource<T> provider) {
\r
834 assertNotDisposed();
\r
835 if (provider == null)
\r
836 throw new IllegalArgumentException("null provider");
\r
837 dataSources.put(provider.getProvidedClass(), provider);
\r
840 @SuppressWarnings("unchecked")
\r
842 public <T> DataSource<T> removeDataSource(Class<T> forProvidedClass) {
\r
843 assertNotDisposed();
\r
844 if (forProvidedClass == null)
\r
845 throw new IllegalArgumentException("null class");
\r
846 return dataSources.remove(forProvidedClass);
\r
850 public void setPersistor(StatePersistor persistor) {
\r
851 this.persistor = persistor;
\r
855 public SelectionDataResolver getSelectionDataResolver() {
\r
856 return selectionDataResolver;
\r
860 public void setSelectionDataResolver(SelectionDataResolver r) {
\r
861 this.selectionDataResolver = r;
\r
865 public SelectionFilter getSelectionFilter() {
\r
866 return selectionFilter;
\r
870 public void setSelectionFilter(SelectionFilter f) {
\r
871 this.selectionFilter = f;
\r
872 // TODO: re-filter current selection?
\r
875 protected ISelection constructSelection(NodeContext... contexts) {
\r
876 if (contexts == null)
\r
877 throw new IllegalArgumentException("null contexts");
\r
878 if (contexts.length == 0)
\r
879 return StructuredSelection.EMPTY;
\r
880 if (selectionFilter == null)
\r
881 return new StructuredSelection(transformSelection(contexts));
\r
882 return new StructuredSelection( transformSelection(filter(selectionFilter, contexts)) );
\r
885 protected Object[] transformSelection(Object[] objects) {
\r
886 return selectionTransformation.call(this, objects);
\r
889 protected static Object[] filter(SelectionFilter filter, NodeContext[] contexts) {
\r
890 int len = contexts.length;
\r
891 Object[] objects = new Object[len];
\r
892 for (int i = 0; i < len; ++i)
\r
893 objects[i] = filter.filter(contexts[i]);
\r
898 public void setSelectionTransformation(
\r
899 BinaryFunction<Object[], GraphExplorer, Object[]> f) {
\r
900 this.selectionTransformation = f;
\r
903 public ISelection getWidgetSelection() {
\r
904 return viewer.getSelection();
\r
908 public <T> void addListener(T listener) {
\r
909 if (listener instanceof FocusListener) {
\r
910 focusListeners.add((FocusListener) listener);
\r
911 } else if (listener instanceof MouseListener) {
\r
912 mouseListeners.add((MouseListener) listener);
\r
913 } else if (listener instanceof KeyListener) {
\r
914 keyListeners.add((KeyListener) listener);
\r
919 public <T> void removeListener(T listener) {
\r
920 if (listener instanceof FocusListener) {
\r
921 focusListeners.remove(listener);
\r
922 } else if (listener instanceof MouseListener) {
\r
923 mouseListeners.remove(listener);
\r
924 } else if (listener instanceof KeyListener) {
\r
925 keyListeners.remove(listener);
\r
929 public void addSelectionListener(SelectionListener listener) {
\r
930 viewer.getTree().addSelectionListener(listener);
\r
933 public void removeSelectionListener(SelectionListener listener) {
\r
934 viewer.getTree().removeSelectionListener(listener);
\r
937 private Set<String> uiContexts;
\r
940 public void setUIContexts(Set<String> contexts) {
\r
941 this.uiContexts = contexts;
\r
945 public void setRoot(final Object root) {
\r
946 if(uiContexts != null && uiContexts.size() == 1)
\r
947 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root, BuiltinKeys.UI_CONTEXT, uiContexts.iterator().next()));
\r
949 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root));
\r
953 public void setRootContext(final NodeContext context) {
\r
954 setRootContext0(context);
\r
957 private void setRoot(NodeContext context) {
\r
959 pendingRoot = context;
\r
960 Display.getDefault().asyncExec(new Runnable() {
\r
962 public void run() {
\r
963 if (viewer != null && !viewer.getTree().isDisposed())
\r
964 viewer.getTree().redraw();
\r
969 doSetRoot(context);
\r
972 private void setRootContext0(final NodeContext context) {
\r
973 Assert.isNotNull(context, "root must not be null");
\r
974 if (isDisposed() || viewer.getTree().isDisposed())
\r
976 Display display = viewer.getTree().getDisplay();
\r
977 if (display.getThread() == Thread.currentThread()) {
\r
980 display.asyncExec(new Runnable() {
\r
982 public void run() {
\r
990 public void setFocus() {
\r
991 viewer.getTree().setFocus();
\r
994 @SuppressWarnings("unchecked")
\r
996 public <T> T getControl() {
\r
997 return (T)viewer.getTree();
\r
1002 public boolean isDisposed() {
\r
1006 protected void assertNotDisposed() {
\r
1008 throw new IllegalStateException("disposed");
\r
1012 public boolean isEditable() {
\r
1017 public void setEditable(boolean editable) {
\r
1018 if (!thread.currentThreadAccess())
\r
1019 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
\r
1021 this.editable = editable;
\r
1022 Display display = viewer.getTree().getDisplay();
\r
1023 viewer.getTree().setBackground(editable ? null : display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
\r
1026 private void doDispose() {
\r
1030 // TODO: Since GENodeQueryManager is cached in QueryChache and it refers to this class
\r
1031 // we have to remove all references here to reduce memory consumption.
\r
1033 // Proper fix would be to remove references between QueryCache and GENodeQueryManagers.
\r
1034 explorerContext.dispose();
\r
1035 explorerContext = null;
\r
1036 processors.clear();
\r
1037 detachPrimitiveProcessors();
\r
1038 primitiveProcessors.clear();
\r
1039 dataSources.clear();
\r
1040 pendingItems.clear();
\r
1041 rootContext = null;
\r
1042 mouseListeners.clear();
\r
1043 selectionProvider.clearListeners();
\r
1044 selectionProvider = null;
\r
1045 selectionDataResolver = null;
\r
1046 selectedNodes.clear();
\r
1047 selectedNodes = null;
\r
1048 selectionTransformation = null;
\r
1049 originalFont = null;
\r
1050 localResourceManager.dispose();
\r
1051 localResourceManager = null;
\r
1052 // Must shutdown image loader job before disposing its ResourceManager
\r
1053 imageLoaderJob.dispose();
\r
1054 imageLoaderJob.cancel();
\r
1056 imageLoaderJob.join();
\r
1057 imageLoaderJob = null;
\r
1058 } catch (InterruptedException e) {
\r
1059 ErrorLogger.defaultLogError(e);
\r
1061 resourceManager.dispose();
\r
1062 resourceManager = null;
\r
1063 collapsedNodes.clear();
\r
1064 collapsedNodes = null;
\r
1065 if (rootNode != null) {
\r
1066 rootNode.dispose();
\r
1069 contextToNodeMap.clear(); // should be empty at this point.
\r
1070 contextToNodeMap = null;
\r
1071 if (postSelectionProvider != null) {
\r
1072 postSelectionProvider.dispose();
\r
1073 postSelectionProvider = null;
\r
1075 imageTasks = null;
\r
1076 modificationContext = null;
\r
1077 focusService = null;
\r
1078 contextService = null;
\r
1079 serviceLocator = null;
\r
1081 columnKeyToIndex.clear();
\r
1082 columnKeyToIndex = null;
\r
1088 public boolean select(NodeContext context) {
\r
1090 assertNotDisposed();
\r
1092 if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) {
\r
1093 viewer.setSelection(new StructuredSelection());
\r
1094 selectionProvider.setAndFireNonEqualSelection(TreeSelection.EMPTY);
\r
1098 viewer.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0)));
\r
1105 public boolean selectPath(Collection<NodeContext> contexts) {
\r
1107 if(contexts == null) throw new IllegalArgumentException("Null list is not allowed");
\r
1108 if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed");
\r
1110 return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0);
\r
1114 private boolean selectPathInternal(NodeContext[] contexts, int position) {
\r
1116 NodeContext head = contexts[position];
\r
1118 if(position == contexts.length-1) {
\r
1119 return select(head);
\r
1123 setExpanded(head, true);
\r
1124 if(!waitVisible(contexts[position+1])) return false;
\r
1126 return selectPathInternal(contexts, position+1);
\r
1130 private boolean waitVisible(NodeContext context) {
\r
1131 long start = System.nanoTime();
\r
1132 while(!isVisible(context)) {
\r
1133 Display.getCurrent().readAndDispatch();
\r
1134 long duration = System.nanoTime() - start;
\r
1135 if(duration > 10e9) return false;
\r
1141 public boolean isVisible(NodeContext context) {
\r
1142 if (contextToNodeMap.getValuesUnsafe(context).size() == 0)
\r
1145 Object elements[] = viewer.getVisibleExpandedElements();
\r
1146 return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0));
\r
1152 public TransientExplorerState getTransientState() {
\r
1153 if (!thread.currentThreadAccess())
\r
1154 throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread());
\r
1155 return transientState;
\r
1159 public <T> T query(NodeContext context, CacheKey<T> key) {
\r
1160 return this.explorerContext.cache.get(context, key);
\r
1164 * For setting a more local service locator for the explorer than the global
\r
1165 * workbench service locator. Sometimes required to give this implementation
\r
1166 * access to local workbench services like IFocusService.
\r
1169 * Must be invoked during right after construction.
\r
1171 * @param serviceLocator
\r
1172 * a specific service locator or <code>null</code> to use the
\r
1173 * workbench global service locator
\r
1175 public void setServiceLocator(IServiceLocator serviceLocator) {
\r
1176 if (serviceLocator == null && PlatformUI.isWorkbenchRunning())
\r
1177 serviceLocator = PlatformUI.getWorkbench();
\r
1178 this.serviceLocator = serviceLocator;
\r
1179 if (serviceLocator != null) {
\r
1180 this.contextService = (IContextService) serviceLocator.getService(IContextService.class);
\r
1181 this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class);
\r
1185 private void detachPrimitiveProcessors() {
\r
1186 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
\r
1187 if (p instanceof ProcessorLifecycle) {
\r
1188 ((ProcessorLifecycle) p).detached(this);
\r
1193 private void clearPrimitiveProcessors() {
\r
1194 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
\r
1195 if (p instanceof ProcessorLifecycle) {
\r
1196 ((ProcessorLifecycle) p).clear();
\r
1202 public void setExpanded(NodeContext context, boolean expanded) {
\r
1203 viewer.setExpandedState(context, expanded);
\r
1208 public void setAutoExpandLevel(int level) {
\r
1209 this.autoExpandLevel = level;
\r
1210 viewer.setAutoExpandLevel(level);
\r
1213 int maxChildren = GraphExplorerImpl.DEFAULT_MAX_CHILDREN;
\r
1216 public int getMaxChildren() {
\r
1217 return maxChildren;
\r
1221 public void setMaxChildren(int maxChildren) {
\r
1222 this.maxChildren = maxChildren;
\r
1227 public int getMaxChildren(NodeQueryManager manager, NodeContext context) {
\r
1228 Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN);
\r
1229 //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result);
\r
1230 if (result != null) {
\r
1232 throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result);
\r
1235 return maxChildren;
\r
1239 public <T> NodeQueryProcessor<T> getProcessor(QueryKey<T> key) {
\r
1240 return explorerContext.getProcessor(key);
\r
1244 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(PrimitiveQueryKey<T> key) {
\r
1245 return explorerContext.getPrimitiveProcessor(key);
\r
1248 private HashSet<UpdateItem> pendingItems = new HashSet<UpdateItem>();
\r
1249 private boolean updating = false;
\r
1250 private int updateCounter = 0;
\r
1251 final ScheduledExecutorService uiUpdateScheduler = ThreadUtils.getNonBlockingWorkExecutor();
\r
1253 private class UpdateItem {
\r
1257 public UpdateItem(TreeNode element) {
\r
1261 public UpdateItem(TreeNode element, int columnIndex) {
\r
1262 this.element = element;
\r
1263 this.columnIndex = columnIndex;
\r
1264 if (element != null && element.isDisposed()) {
\r
1265 throw new IllegalArgumentException("Node is disposed. " + element);
\r
1269 public void update(TreeViewer viewer) {
\r
1270 if (element != null) {
\r
1272 if (element.isDisposed()) {
\r
1275 if (((TreeNode)element).updateChildren()) {
\r
1276 viewer.refresh(element,true);
\r
1278 if (columnIndex >= 0) {
\r
1279 viewer.update(element, new String[]{columns[columnIndex].getKey()});
\r
1281 viewer.refresh(element,true);
\r
1285 if (!element.isDisposed() && autoExpandLevel > 1 && !collapsedNodes.contains(element) && ((TreeNode)element).distanceToRoot() <= autoExpandLevel) {
\r
1287 viewer.setExpandedState(element, true);
\r
1291 if (rootNode.updateChildren())
\r
1292 viewer.refresh(rootNode,true);
\r
1297 public boolean equals(Object obj) {
\r
1300 if (obj.getClass() != getClass())
\r
1302 UpdateItem other = (UpdateItem)obj;
\r
1303 if (columnIndex != other.columnIndex)
\r
1305 if (element != null)
\r
1306 return element.equals(other.element);
\r
1307 return other.element == null;
\r
1311 public int hashCode() {
\r
1312 if (element != null)
\r
1313 return element.hashCode() + columnIndex;
\r
1318 private void update(final TreeNode element, final int columnIndex) {
\r
1319 if (DEBUG)System.out.println("update " + element + " " + columnIndex);
\r
1320 if (viewer.getTree().isDisposed())
\r
1322 synchronized (pendingItems) {
\r
1323 pendingItems.add(new UpdateItem(element, columnIndex));
\r
1324 if (updating) return;
\r
1326 scheduleUpdater();
\r
1330 private void update(final TreeNode element) {
\r
1331 if (DEBUG)System.out.println("update " + element);
\r
1332 if (viewer.getTree().isDisposed())
\r
1334 if (element != null && element.isDisposed())
\r
1336 synchronized (pendingItems) {
\r
1338 pendingItems.add(new UpdateItem(element));
\r
1339 if (updating) return;
\r
1341 scheduleUpdater();
\r
1345 boolean scheduleUpdater() {
\r
1347 if (viewer.getTree().isDisposed())
\r
1350 if (!pendingItems.isEmpty()) {
\r
1352 int activity = explorerContext.activityInt;
\r
1354 if (activity < 100) {
\r
1355 //System.out.println("Scheduling update immediately.");
\r
1356 } else if (activity < 1000) {
\r
1357 //System.out.println("Scheduling update after 500ms.");
\r
1360 //System.out.println("Scheduling update after 3000ms.");
\r
1364 updateCounter = 0;
\r
1366 //System.out.println("Scheduling UI update after " + delay + " ms.");
\r
1367 uiUpdateScheduler.schedule(new Runnable() {
\r
1369 public void run() {
\r
1371 if (viewer == null || viewer.getTree().isDisposed())
\r
1374 if (updateCounter > 0) {
\r
1375 updateCounter = 0;
\r
1376 uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS);
\r
1378 viewer.getTree().getDisplay().asyncExec(new UpdateRunner(GraphExplorerImpl2.this, GraphExplorerImpl2.this.explorerContext));
\r
1382 }, delay, TimeUnit.MILLISECONDS);
\r
1392 public String startEditing(NodeContext context, String columnKey) {
\r
1393 assertNotDisposed();
\r
1394 if (!thread.currentThreadAccess())
\r
1395 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
\r
1397 if(columnKey.startsWith("#")) {
\r
1398 columnKey = columnKey.substring(1);
\r
1401 Integer columnIndex = columnKeyToIndex.get(columnKey);
\r
1402 if (columnIndex == null)
\r
1403 return "Rename not supported for selection";
\r
1405 viewer.editElement(context, columnIndex);
\r
1406 if(viewer.isCellEditorActive()) return null;
\r
1407 return "Rename not supported for selection";
\r
1411 public String startEditing(String columnKey) {
\r
1412 ISelection selection = postSelectionProvider.getSelection();
\r
1413 if(selection == null) return "Rename not supported for selection";
\r
1414 NodeContext context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class);
\r
1415 if(context == null) return "Rename not supported for selection";
\r
1417 return startEditing(context, columnKey);
\r
1421 public void setSelection(final ISelection selection, boolean forceControlUpdate) {
\r
1422 assertNotDisposed();
\r
1423 boolean equalsOld = selectionProvider.selectionEquals(selection);
\r
1424 if (equalsOld && !forceControlUpdate) {
\r
1425 // Just set the selection object instance, fire no events nor update
\r
1426 // the viewer selection.
\r
1427 selectionProvider.setSelection(selection);
\r
1429 Collection<NodeContext> coll = AdaptionUtils.adaptToCollection(selection, NodeContext.class);
\r
1430 Collection<TreeNode> nodes = new ArrayList<TreeNode>();
\r
1431 for (NodeContext c : coll) {
\r
1432 List<TreeNode> match = contextToNodeMap.getValuesUnsafe(c);
\r
1433 if(match.size() > 0)
\r
1434 nodes.add(match.get(0));
\r
1436 final ISelection sel = new StructuredSelection(nodes.toArray());
\r
1437 if (coll.size() == 0)
\r
1439 // Schedule viewer and selection update if necessary.
\r
1440 if (viewer.getTree().isDisposed())
\r
1442 Display d = viewer.getTree().getDisplay();
\r
1443 if (d.getThread() == Thread.currentThread()) {
\r
1444 viewer.setSelection(sel);
\r
1446 d.asyncExec(new Runnable() {
\r
1448 public void run() {
\r
1449 if (viewer.getTree().isDisposed())
\r
1451 viewer.setSelection(sel);
\r
1459 public void setModificationContext(ModificationContext modificationContext) {
\r
1460 this.modificationContext = modificationContext;
\r
1464 final ExecutorService queryUpdateScheduler = Threads.getExecutor();
\r
1466 private static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {
\r
1467 // This is for query debugging only.
\r
1469 private GraphExplorerImpl2 ge;
\r
1470 int queryIndent = 0;
\r
1472 GECache2 cache = new GECache2();
\r
1473 AtomicBoolean propagating = new AtomicBoolean(false);
\r
1474 Object propagateList = new Object();
\r
1475 Object propagate = new Object();
\r
1476 List<Runnable> scheduleList = new ArrayList<Runnable>();
\r
1477 final Deque<Integer> activity = new LinkedList<Integer>();
\r
1478 int activityInt = 0;
\r
1480 AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();
\r
1483 * Keeps track of nodes that have already been auto-expanded. After
\r
1484 * being inserted into this set, nodes will not be forced to stay in an
\r
1485 * expanded state after that. This makes it possible for the user to
\r
1486 * close auto-expanded nodes.
\r
1488 Map<NodeContext, Boolean> autoExpanded = new WeakHashMap<NodeContext, Boolean>();
\r
1490 public GeViewerContext(GraphExplorerImpl2 ge) {
\r
1495 protected void doDispose() {
\r
1497 autoExpanded.clear();
\r
1501 public IGECache getCache() {
\r
1506 public int queryIndent() {
\r
1507 return queryIndent;
\r
1511 public int queryIndent(int offset) {
\r
1512 queryIndent += offset;
\r
1513 return queryIndent;
\r
1517 @SuppressWarnings("unchecked")
\r
1518 public <T> NodeQueryProcessor<T> getProcessor(Object o) {
\r
1521 return ge.processors.get(o);
\r
1525 @SuppressWarnings("unchecked")
\r
1526 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {
\r
1527 return ge.primitiveProcessors.get(o);
\r
1530 @SuppressWarnings("unchecked")
\r
1532 public <T> DataSource<T> getDataSource(Class<T> clazz) {
\r
1533 return ge.dataSources.get(clazz);
\r
1537 public void update(UIElementReference ref) {
\r
1538 if (ref instanceof ViewerCellReference) {
\r
1539 ViewerCellReference tiref = (ViewerCellReference) ref;
\r
1540 Object element = tiref.getElement();
\r
1541 int columnIndex = tiref.getColumn();
\r
1542 // NOTE: must be called regardless of the the item value.
\r
1543 // A null item is currently used to indicate a tree root update.
\r
1544 ge.update((TreeNode)element,columnIndex);
\r
1545 } else if (ref instanceof ViewerRowReference) {
\r
1546 ViewerRowReference rref = (ViewerRowReference)ref;
\r
1547 Object element = rref.getElement();
\r
1548 ge.update((TreeNode)element);
\r
1550 throw new IllegalArgumentException("Ui Reference is unknkown " + ref);
\r
1555 public Object getPropagateLock() {
\r
1560 public Object getPropagateListLock() {
\r
1561 return propagateList;
\r
1565 public boolean isPropagating() {
\r
1566 return propagating.get();
\r
1570 public void setPropagating(boolean b) {
\r
1571 this.propagating.set(b);
\r
1575 public List<Runnable> getScheduleList() {
\r
1576 return scheduleList;
\r
1580 public void setScheduleList(List<Runnable> list) {
\r
1581 this.scheduleList = list;
\r
1585 public Deque<Integer> getActivity() {
\r
1590 public void setActivityInt(int i) {
\r
1591 this.activityInt = i;
\r
1595 public int getActivityInt() {
\r
1596 return activityInt;
\r
1600 public void scheduleQueryUpdate(Runnable r) {
\r
1603 if (ge.isDisposed())
\r
1605 if (currentQueryUpdater.compareAndSet(null, r)) {
\r
1606 ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER);
\r
1610 Runnable QUERY_UPDATE_SCHEDULER = new Runnable() {
\r
1612 public void run() {
\r
1613 Runnable r = currentQueryUpdater.getAndSet(null);
\r
1621 public void dispose() {
\r
1623 cache = new DummyCache();
\r
1624 scheduleList.clear();
\r
1625 autoExpanded.clear();
\r
1626 autoExpanded = null;
\r
1636 private static class GeViewerContentProvider implements ITreeContentProvider {
\r
1638 public Object[] getElements(Object inputElement) {
\r
1639 return getChildren(inputElement);
\r
1643 public Object[] getChildren(Object element) {
\r
1644 TreeNode node = (TreeNode)element;
\r
1645 return node.getChildren().toArray();
\r
1650 public Object getParent(Object element) {
\r
1651 TreeNode node = (TreeNode)element;
\r
1652 return node.getParent();
\r
1656 public boolean hasChildren(Object element) {
\r
1657 return getChildren(element).length > 0;
\r
1661 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
\r
1666 public void dispose() {
\r
1671 private class GeViewerLabelProvider extends CellLabelProvider {
\r
1672 private TreeNode node;
\r
1674 private Labeler labeler;
\r
1675 private Imager imager;
\r
1676 Collection<LabelDecorator> labelDecorators;
\r
1677 Collection<ImageDecorator> imageDecorators;
\r
1679 Map<String, String> labels;
\r
1680 Map<String, String> runtimeLabels;
\r
1682 public void update(ViewerCell cell) {
\r
1683 TreeNode node = (TreeNode)cell.getElement();
\r
1684 NodeContext ctx = node.getContext();
\r
1685 int columnIndex = cell.getColumnIndex();
\r
1686 String columnKey = columns[columnIndex].getKey();
\r
1688 // using columnIndex 0 to refresh data.
\r
1689 // Note that this does not work if ViewerCellReferences are used. (At the moment there is no code that would use them).
\r
1690 if (node != this.node || columnIndex == 0) {
\r
1692 GENodeQueryManager manager = node.getManager();
\r
1693 labeler = manager.query(ctx, BuiltinKeys.SELECTED_LABELER);
\r
1694 imager = manager.query(ctx, BuiltinKeys.SELECTED_IMAGER);
\r
1695 labelDecorators = manager.query(ctx, BuiltinKeys.LABEL_DECORATORS);
\r
1696 imageDecorators = manager.query(ctx, BuiltinKeys.IMAGE_DECORATORS);
\r
1698 if (labeler != null) {
\r
1699 labels = labeler.getLabels();
\r
1700 runtimeLabels = labeler.getRuntimeLabels();
\r
1703 runtimeLabels = null;
\r
1707 //if (DEBUG) System.out.println("GeViewerLabelProvider.update " + context + " " + columnIndex);
\r
1709 setText(cell, columnKey);
\r
1710 setImage(cell, columnKey);
\r
1713 void setImage(ViewerCell cell, String columnKey) {
\r
1714 if (imager != null) {
\r
1715 Object descOrImage = null;
\r
1716 boolean hasUncachedImages = false;
\r
1718 ImageDescriptor desc = imager.getImage(columnKey);
\r
1719 if (desc != null) {
\r
1721 // Attempt to decorate the label
\r
1722 if (!imageDecorators.isEmpty()) {
\r
1723 for (ImageDecorator id : imageDecorators) {
\r
1724 ImageDescriptor ds = id.decorateImage(desc, columnKey, index);
\r
1730 // Try resolving only cached images here and now
\r
1731 Object img = localResourceManager.find(desc);
\r
1733 img = resourceManager.find(desc);
\r
1735 descOrImage = img != null ? img : desc;
\r
1736 hasUncachedImages |= img == null;
\r
1739 if (!hasUncachedImages) {
\r
1740 cell.setImage((Image) descOrImage);
\r
1742 // Schedule loading to another thread to refrain from
\r
1744 // the UI with database operations.
\r
1745 queueImageTask(node, new ImageTask(node, descOrImage));
\r
1748 cell.setImage(null);
\r
1752 private void queueImageTask(TreeNode node, ImageTask task) {
\r
1753 synchronized (imageTasks) {
\r
1754 imageTasks.put(node, task);
\r
1756 imageLoaderJob.scheduleIfNecessary(100);
\r
1759 void setText(ViewerCell cell, String key) {
\r
1760 if (labeler != null) {
\r
1762 if (runtimeLabels != null)
\r
1763 s = runtimeLabels.get(key);
\r
1765 s = labels.get(key);
\r
1766 //if (DEBUG) System.out.println(cell.getElement() + " " + cell.getColumnIndex() + " label:" + s + " key:" + key + " " + labels.size() + " " + (runtimeLabels == null ? "-1" : runtimeLabels.size()));
\r
1768 FontDescriptor font = originalFont;
\r
1769 ColorDescriptor bg = originalBackground;
\r
1770 ColorDescriptor fg = originalForeground;
\r
1772 // Attempt to decorate the label
\r
1773 if (!labelDecorators.isEmpty()) {
\r
1775 for (LabelDecorator ld : labelDecorators) {
\r
1776 String ds = ld.decorateLabel(s, key, index);
\r
1780 FontDescriptor dfont = ld.decorateFont(font, key, index);
\r
1781 if (dfont != null)
\r
1784 ColorDescriptor dbg = ld.decorateBackground(bg, key, index);
\r
1788 ColorDescriptor dfg = ld.decorateForeground(fg, key, index);
\r
1794 if (font != originalFont) {
\r
1795 // System.out.println("set font: " + index + ": " +
\r
1797 cell.setFont((Font) localResourceManager.get(font));
\r
1799 cell.setFont((Font) (originalFont != null ? localResourceManager.get(originalFont) : null));
\r
1801 if (bg != originalBackground)
\r
1802 cell.setBackground((Color) localResourceManager.get(bg));
\r
1804 cell.setBackground((Color) (originalBackground != null ? localResourceManager.get(originalBackground) : null));
\r
1805 if (fg != originalForeground)
\r
1806 cell.setForeground((Color) localResourceManager.get(fg));
\r
1808 cell.setForeground((Color) (originalForeground != null ? localResourceManager.get(originalForeground) : null));
\r
1813 cell.setText(Labeler.NO_LABEL);
\r
1821 private class GeEditingSupport extends EditingSupport {
\r
1822 private Object lastElement;
\r
1824 private Modifier lastModifier;
\r
1826 private int columnIndex;
\r
1827 public GeEditingSupport(ColumnViewer viewer, int columnIndex) {
\r
1829 this.columnIndex = columnIndex;
\r
1833 protected boolean canEdit(Object element) {
\r
1834 if (filterSelectionEdit && !selectedNodes.contains(element)) {
\r
1835 // When item is clicked, canEdit is called before the selection is updated.
\r
1836 // This allows filtering edit attempts when the item is selected.
\r
1839 lastElement = null; // clear cached element + modifier.
\r
1840 Modifier modifier = getModifier((TreeNode)element);
\r
1841 if (modifier == null)
\r
1847 protected CellEditor getCellEditor(Object element) {
\r
1848 TreeNode node = (TreeNode) element;
\r
1849 Modifier modifier = getModifier((TreeNode)element);
\r
1850 NodeContext context = node.getContext();
\r
1851 if (modifier instanceof DialogModifier) {
\r
1852 return performDialogEditing(context, (DialogModifier) modifier);
\r
1853 } else if (modifier instanceof CustomModifier) {
\r
1854 return startCustomEditing(node, (CustomModifier) modifier);
\r
1855 } else if (modifier instanceof EnumerationModifier) {
\r
1856 return startEnumerationEditing((EnumerationModifier) modifier);
\r
1858 return startTextEditing(modifier);
\r
1864 protected Object getValue(Object element) {
\r
1865 Modifier modifier = getModifier((TreeNode)element);
\r
1866 return modifier.getValue();
\r
1869 protected void setValue(Object element, Object value) {
\r
1870 Modifier modifier = getModifier((TreeNode)element);
\r
1871 // CustomModifiers have internal set value mechanism.
\r
1872 if (!(modifier instanceof CustomModifier))
\r
1873 modifier.modify((String)value);
\r
1877 CellEditor startTextEditing( Modifier modifier) {
\r
1878 TextCellEditor editor = new ValidatedTextEditor(viewer.getTree());//new TextCellEditor(viewer.getTree());
\r
1879 editor.setValidator(new ModifierValidator(modifier));
\r
1883 CellEditor startEnumerationEditing(EnumerationModifier modifier) {
\r
1884 if (SimanticsUI.isLinuxGTK()) {
\r
1885 // ComboBoxCellEditor2 does not work when GEImpl2 is embedded into dialog (It does work in SelectionView)
\r
1886 // CBCE2 does not work because it receives a focus lost event when the combo/popup is opened.
\r
1887 return new EnumModifierEditor(viewer.getTree(),modifier);
\r
1889 return new EnumModifierEditor2(viewer.getTree(),modifier);
\r
1893 CellEditor performDialogEditing(final NodeContext context, final DialogModifier modifier) {
\r
1894 DialogCellEditor editor = new DialogCellEditor(viewer.getTree()) {
\r
1895 String res = null;
\r
1897 protected Object openDialogBox(Control cellEditorWindow) {
\r
1899 final Semaphore sem = new Semaphore(1);
\r
1900 Consumer<String> callback = result -> {
\r
1904 String status = modifier.query(cellEditorWindow, null, columnIndex, context, callback);
\r
1905 if (status != null)
\r
1909 } catch (InterruptedException e) {
\r
1910 e.printStackTrace();
\r
1917 editor.setValidator(new ModifierValidator(modifier));
\r
1921 CellEditor startCustomEditing(TreeNode node, CustomModifier modifier) {
\r
1922 CustomModifierEditor editor = new CustomModifierEditor(viewer.getTree(), modifier, node, columnIndex);
\r
1926 private Modifier getModifier(TreeNode element) {
\r
1927 if (element == lastElement)
\r
1928 return lastModifier;
\r
1929 lastModifier = GraphExplorerImpl2.this.getModifier(element, columnIndex);
\r
1930 lastElement = element;
\r
1931 return lastModifier;
\r
1937 private Modifier getModifier(TreeNode element, int columnIndex) {
\r
1938 GENodeQueryManager manager = element.getManager();
\r
1939 final NodeContext context = element.getContext();
\r
1940 Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
\r
1941 if (labeler == null)
\r
1943 Column column = columns[columnIndex];
\r
1945 return labeler.getModifier(modificationContext, column.getKey());
\r
1949 static class ImageTask {
\r
1951 Object descsOrImage;
\r
1952 public ImageTask(TreeNode node, Object descsOrImage) {
\r
1954 this.descsOrImage = descsOrImage;
\r
1959 * The job that is used for off-loading image loading tasks (see
\r
1960 * {@link ImageTask} to a worker thread from the main UI thread.
\r
1962 ImageLoaderJob imageLoaderJob;
\r
1964 // Map<NodeContext, ImageTask> imageTasks = new THashMap<NodeContext, ImageTask>();
\r
1965 Map<TreeNode, ImageTask> imageTasks = new THashMap<TreeNode, ImageTask>();
\r
1968 * Invoked in a job worker thread.
\r
1973 protected IStatus setPendingImages(IProgressMonitor monitor) {
\r
1974 ImageTask[] tasks = null;
\r
1975 synchronized (imageTasks) {
\r
1976 tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]);
\r
1977 imageTasks.clear();
\r
1980 MultiStatus status = null;
\r
1982 // Load missing images
\r
1983 for (ImageTask task : tasks) {
\r
1984 Object desc = task.descsOrImage;
\r
1985 if (desc instanceof ImageDescriptor) {
\r
1987 desc = resourceManager.get((ImageDescriptor) desc);
\r
1988 task.descsOrImage = desc;
\r
1989 } catch (DeviceResourceException e) {
\r
1990 if (status == null)
\r
1991 status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null);
\r
1992 status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e));
\r
1998 // Perform final UI updates in the UI thread.
\r
1999 final ImageTask[] _tasks = tasks;
\r
2000 thread.asyncExec(new Runnable() {
\r
2002 public void run() {
\r
2003 setImages(_tasks);
\r
2007 return status != null ? status : Status.OK_STATUS;
\r
2011 void setImages(ImageTask[] tasks) {
\r
2012 for (ImageTask task : tasks)
\r
2017 void setImage(ImageTask task) {
\r
2018 if (!task.node.isDisposed())
\r
2019 update(task.node, 0);
\r
2022 private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider {
\r
2024 private GraphExplorerImpl2 ge;
\r
2026 GraphExplorerPostSelectionProvider(GraphExplorerImpl2 ge) {
\r
2035 public void setSelection(final ISelection selection) {
\r
2036 if(ge == null) return;
\r
2037 ge.setSelection(selection, false);
\r
2043 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
\r
2044 if(ge == null) return;
\r
2045 if(ge.isDisposed()) {
\r
2046 if (DEBUG_SELECTION_LISTENERS)
\r
2047 System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener);
\r
2050 ge.selectionProvider.removeSelectionChangedListener(listener);
\r
2054 public void addPostSelectionChangedListener(ISelectionChangedListener listener) {
\r
2055 if(ge == null) return;
\r
2056 if (!ge.thread.currentThreadAccess())
\r
2057 throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
\r
2058 if(ge.isDisposed()) {
\r
2059 System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener);
\r
2062 ge.selectionProvider.addPostSelectionChangedListener(listener);
\r
2066 public void removePostSelectionChangedListener(ISelectionChangedListener listener) {
\r
2067 if(ge == null) return;
\r
2068 if(ge.isDisposed()) {
\r
2069 if (DEBUG_SELECTION_LISTENERS)
\r
2070 System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener);
\r
2073 ge.selectionProvider.removePostSelectionChangedListener(listener);
\r
2078 public void addSelectionChangedListener(ISelectionChangedListener listener) {
\r
2079 if(ge == null) return;
\r
2080 if (!ge.thread.currentThreadAccess())
\r
2081 throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
\r
2082 if (ge.viewer.getTree().isDisposed() || ge.selectionProvider == null) {
\r
2083 System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener);
\r
2087 ge.selectionProvider.addSelectionChangedListener(listener);
\r
2092 public ISelection getSelection() {
\r
2093 if(ge == null) return StructuredSelection.EMPTY;
\r
2094 if (!ge.thread.currentThreadAccess())
\r
2095 throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread());
\r
2096 if (ge.viewer.getTree().isDisposed() || ge.selectionProvider == null)
\r
2097 return StructuredSelection.EMPTY;
\r
2098 return ge.selectionProvider.getSelection();
\r
2103 static class ModifierValidator implements ICellEditorValidator {
\r
2104 private Modifier modifier;
\r
2105 public ModifierValidator(Modifier modifier) {
\r
2106 this.modifier = modifier;
\r
2110 public String isValid(Object value) {
\r
2111 return modifier.isValid((String)value);
\r
2115 static class UpdateRunner implements Runnable {
\r
2117 final GraphExplorerImpl2 ge;
\r
2119 UpdateRunner(GraphExplorerImpl2 ge, IGraphExplorerContext geContext) {
\r
2123 public void run() {
\r
2126 } catch (Throwable t) {
\r
2127 t.printStackTrace();
\r
2131 public void doRun() {
\r
2133 if (ge.isDisposed())
\r
2136 HashSet<UpdateItem> items;
\r
2138 ScrollBar verticalBar = ge.viewer.getTree().getVerticalBar();
\r
2141 synchronized (ge.pendingItems) {
\r
2142 items = ge.pendingItems;
\r
2143 ge.pendingItems = new HashSet<UpdateItem>();
\r
2145 if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size());
\r
2147 ge.viewer.getTree().setRedraw(false);
\r
2148 for (UpdateItem item : items) {
\r
2149 item.update(ge.viewer);
\r
2152 // check if vertical scroll bar has become visible and refresh layout.
\r
2153 boolean currentlyVerticalBarVisible = verticalBar.isVisible();
\r
2154 if (ge.verticalBarVisible != currentlyVerticalBarVisible) {
\r
2155 ge.verticalBarVisible = currentlyVerticalBarVisible;
\r
2156 ge.viewer.getTree().getParent().layout();
\r
2159 ge.viewer.getTree().setRedraw(true);
\r
2161 synchronized (ge.pendingItems) {
\r
2162 if (!ge.scheduleUpdater()) {
\r
2163 ge.updating = false;
\r
2167 if (!ge.updating) {
\r
2168 ge.printTree(ge.rootNode, 0);
\r
2175 private class ValidatedTextEditor extends TextCellEditor {
\r
2178 public ValidatedTextEditor(Composite parent) {
\r
2182 protected void editOccured(org.eclipse.swt.events.ModifyEvent e) {
\r
2183 String value = text.getText();
\r
2184 if (value == null) {
\r
2185 value = "";//$NON-NLS-1$
\r
2187 Object typedValue = value;
\r
2188 boolean oldValidState = isValueValid();
\r
2189 boolean newValidState = isCorrect(typedValue);
\r
2190 if (!newValidState) {
\r
2191 text.setBackground(invalidModificationColor);
\r
2192 text.setToolTipText(getErrorMessage());
\r
2194 text.setBackground(null);
\r
2195 text.setToolTipText(null);
\r
2197 valueChanged(oldValidState, newValidState);
\r
2201 private class EnumModifierEditor2 extends ComboBoxCellEditor2 {
\r
2203 List<String> values;
\r
2204 public EnumModifierEditor2(Composite parent, EnumerationModifier modifier) {
\r
2205 super(parent,modifier.getValues().toArray(new String[modifier.getValues().size()]),SWT.READ_ONLY);
\r
2206 values = modifier.getValues();
\r
2207 setValidator(new ModifierValidator(modifier));
\r
2210 protected void doSetValue(Object value) {
\r
2211 super.doSetValue((Integer)values.indexOf(value));
\r
2215 protected Object doGetValue() {
\r
2216 return values.get((Integer)super.doGetValue());
\r
2220 private class EnumModifierEditor extends ComboBoxCellEditor {
\r
2222 List<String> values;
\r
2223 public EnumModifierEditor(Composite parent, EnumerationModifier modifier) {
\r
2224 super(parent,modifier.getValues().toArray(new String[modifier.getValues().size()]),SWT.READ_ONLY);
\r
2225 values = modifier.getValues();
\r
2226 setValidator(new ModifierValidator(modifier));
\r
2229 protected void doSetValue(Object value) {
\r
2230 super.doSetValue((Integer)values.indexOf(value));
\r
2234 protected Object doGetValue() {
\r
2235 return values.get((Integer)super.doGetValue());
\r
2240 private class CustomModifierEditor extends CellEditor implements ICellEditorValidator, DisposeListener {
\r
2241 private CustomModifier modifier;
\r
2242 private TreeNode node;
\r
2243 private NodeContext context;
\r
2244 private int columnIndex;
\r
2245 private Composite control;
\r
2246 private Control origControl;
\r
2248 public CustomModifierEditor(Composite parent, CustomModifier modifier, TreeNode node, int columnIndex) {
\r
2249 this.modifier = modifier;
\r
2251 this.context = node.getContext();
\r
2252 this.columnIndex = columnIndex;
\r
2253 setValidator(this);
\r
2258 protected Control createControl(Composite parent) {
\r
2259 control = new Composite(parent, SWT.NONE);
\r
2260 control.setLayout(new FillLayout());
\r
2261 origControl = (Control) modifier.createControl(control, null, columnIndex, context);
\r
2268 protected Object doGetValue() {
\r
2269 return modifier.getValue();
\r
2273 protected void doSetValue(Object value) {
\r
2274 //CustomModifier handles value setting internally.
\r
2278 private void reCreate() {
\r
2279 modifier = (CustomModifier)getModifier(node, columnIndex);
\r
2280 if (control != null && !control.isDisposed()) {
\r
2281 if (!origControl.isDisposed())
\r
2282 origControl.dispose();
\r
2283 origControl = (Control)modifier.createControl(control, null, columnIndex, context);
\r
2284 origControl.addDisposeListener(this);
\r
2287 protected void doSetFocus() {
\r
2288 if (control != null && !control.isDisposed())
\r
2289 control.setFocus();
\r
2293 public void widgetDisposed(DisposeEvent e) {
\r
2294 if (e.widget == origControl) {
\r
2301 public String isValid(Object value) {
\r
2302 return modifier.isValid((String)value);
\r
2307 private class TreeNode implements IAdaptable {
\r
2308 private NodeContext context;
\r
2310 private TreeNode parent;
\r
2311 private List<TreeNode> children = new ArrayList<TreeNode>();
\r
2312 private GENodeQueryManager manager;
\r
2314 private TreeNode(NodeContext context) {
\r
2315 if (context == null)
\r
2316 throw new NullPointerException();
\r
2317 this.context = context;
\r
2318 contextToNodeMap.add(context, this);
\r
2319 manager = new GENodeQueryManager(explorerContext, null, null, ViewerRowReference.create(this));
\r
2322 public List<TreeNode> getChildren() {
\r
2323 synchronized (children) {
\r
2328 public TreeNode getParent() {
\r
2332 public NodeContext getContext() {
\r
2336 public GENodeQueryManager getManager() {
\r
2340 public TreeNode addChild(NodeContext context) {
\r
2341 TreeNode child = new TreeNode(context);
\r
2342 child.parent = this;
\r
2343 children.add(child);
\r
2344 if (DEBUG) System.out.println("Add " + this + " -> " + child);
\r
2348 public TreeNode addChild(int index, NodeContext context) {
\r
2350 TreeNode child = new TreeNode(context);
\r
2351 child.parent = this;
\r
2352 children.add(index,child);
\r
2353 if (DEBUG) System.out.println("Add " + this + " -> " + child + " at " + index);
\r
2357 public TreeNode setChild(int index, NodeContext context) {
\r
2359 TreeNode child = new TreeNode(context);
\r
2360 child.parent = this;
\r
2361 children.set(index,child);
\r
2362 if (DEBUG) System.out.println("Set " + this + " -> " + child + " at " + index);
\r
2366 public int distanceToRoot() {
\r
2368 TreeNode n = getParent();
\r
2369 while (n != null) {
\r
2370 n = n.getParent();
\r
2377 public void dispose() {
\r
2378 if (parent != null)
\r
2379 parent.children.remove(this);
\r
2383 public void dispose2() {
\r
2384 if (DEBUG) System.out.println("dispose " + this);
\r
2386 for (TreeNode n : children) {
\r
2391 contextToNodeMap.remove(context, this);
\r
2393 manager.dispose();
\r
2397 private void clearCache() {
\r
2398 if (explorerContext != null) {
\r
2399 GECache2 cache = explorerContext.cache;
\r
2401 if (cache != null) {
\r
2402 cache.dispose(context);
\r
2407 public boolean updateChildren() {
\r
2408 if (context == null)
\r
2409 throw new IllegalStateException("Node is disposed.");
\r
2411 NodeContext[] childContexts = manager.query(context, BuiltinKeys.FINAL_CHILDREN);
\r
2413 if (DEBUG) System.out.println("updateChildren " + childContexts.length + " " + this);
\r
2416 boolean modified = false;
\r
2417 synchronized (children) {
\r
2419 int oldCount = children.size();
\r
2420 BijectionMap<Integer, Integer> indexes = new BijectionMap<Integer, Integer>();
\r
2421 Set<Integer> mapped = new HashSet<Integer>();
\r
2422 boolean reorder = false;
\r
2423 // locate matching pairs form old and new children
\r
2424 for (int i = 0; i < oldCount; i++) {
\r
2425 NodeContext oldCtx = children.get(i).context;
\r
2426 for (int j = 0; j <childContexts.length; j++) {
\r
2427 if (mapped.contains(j))
\r
2429 if (oldCtx.equals(childContexts[j])) {
\r
2430 indexes.map(i, j);
\r
2438 // update children if required
\r
2439 if (childContexts.length != oldCount || reorder || childContexts.length != indexes.size()) {
\r
2441 List<TreeNode> oldChildren = new ArrayList<TreeNode>(oldCount);
\r
2442 oldChildren.addAll(children);
\r
2443 if (childContexts.length >= oldCount) {
\r
2444 for (int i = 0; i < oldCount; i++) {
\r
2445 Integer oldIndex = indexes.getLeft(i);
\r
2446 if (oldIndex == null) {
\r
2447 setChild(i, childContexts[i]);
\r
2449 TreeNode n = oldChildren.get(oldIndex);
\r
2450 children.set(i, n);
\r
2454 for (int i = oldCount; i < childContexts.length; i++) {
\r
2455 addChild(childContexts[i]);
\r
2458 for (int i = 0; i < childContexts.length; i++) {
\r
2459 Integer oldIndex = indexes.getLeft(i);
\r
2460 if (oldIndex == null) {
\r
2461 setChild(i, childContexts[i]);
\r
2463 TreeNode n = oldChildren.get(oldIndex);
\r
2464 children.set(i, n);
\r
2467 for (int i = oldCount -1; i >= childContexts.length; i--) {
\r
2468 children.remove(i);
\r
2471 for (int i = 0; i < oldChildren.size(); i++) {
\r
2472 if (!indexes.containsLeft(i)) {
\r
2473 oldChildren.get(i).dispose2();
\r
2483 public boolean isDisposed() {
\r
2484 return context == null;
\r
2487 @SuppressWarnings("rawtypes")
\r
2489 public Object getAdapter(Class adapter) {
\r
2490 if (adapter == NodeContext.class)
\r
2492 return context.getAdapter(adapter);
\r
2496 // public String toString() {
\r
2498 // if (manager != null) {
\r
2500 // s+= super.toString() + " ";
\r
2502 // Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
\r
2503 // Map<String,String> labels = labeler.getLabels();
\r
2504 // for (Entry<String, String> l : labels.entrySet()) {
\r
2505 // s+= l.getKey() + " : " + l.getValue() + " ";
\r
2507 // } catch (Exception e) {
\r
2511 // s = super.toString();
\r
2513 // if (context != null)
\r
2514 // s += " context " + context.hashCode();
\r
2521 private static class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor<Boolean> implements
\r
2522 IsExpandedProcessor, ProcessorLifecycle {
\r
2524 * The set of currently expanded node contexts.
\r
2526 private final HashSet<NodeContext> expanded = new HashSet<NodeContext>();
\r
2527 private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();
\r
2529 private Tree tree;
\r
2531 public TreeNodeIsExpandedProcessor() {
\r
2535 public Object getIdentifier() {
\r
2536 return BuiltinKeys.IS_EXPANDED;
\r
2540 public String toString() {
\r
2541 return "IsExpandedProcessor";
\r
2545 public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Boolean> key) {
\r
2546 boolean isExpanded = expanded.contains(context);
\r
2547 expandedQueries.put(context, updater);
\r
2548 return Boolean.valueOf(isExpanded);
\r
2552 public Collection<NodeContext> getExpanded() {
\r
2553 return new HashSet<NodeContext>(expanded);
\r
2557 public boolean getExpanded(NodeContext context) {
\r
2558 return this.expanded.contains(context);
\r
2562 public boolean setExpanded(NodeContext context, boolean expanded) {
\r
2563 return _setExpanded(context, expanded);
\r
2567 public boolean replaceExpanded(NodeContext context, boolean expanded) {
\r
2568 return nodeStatusChanged(context, expanded);
\r
2571 private boolean _setExpanded(NodeContext context, boolean expanded) {
\r
2573 return this.expanded.add(context);
\r
2575 return this.expanded.remove(context);
\r
2579 Listener treeListener = new Listener() {
\r
2581 public void handleEvent(Event event) {
\r
2582 TreeNode node = (TreeNode) event.item.getData();
\r
2583 NodeContext context = node.getContext();
\r
2584 switch (event.type) {
\r
2586 nodeStatusChanged(context, true);
\r
2588 case SWT.Collapse:
\r
2589 nodeStatusChanged(context, false);
\r
2595 protected boolean nodeStatusChanged(NodeContext context, boolean expanded) {
\r
2596 boolean result = _setExpanded(context, expanded);
\r
2597 PrimitiveQueryUpdater updater = expandedQueries.get(context);
\r
2598 if (updater != null)
\r
2599 updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded);
\r
2604 public void attached(GraphExplorer explorer) {
\r
2605 Object control = explorer.getControl();
\r
2606 if (control instanceof Tree) {
\r
2607 this.tree = (Tree) control;
\r
2608 tree.addListener(SWT.Expand, treeListener);
\r
2609 tree.addListener(SWT.Collapse, treeListener);
\r
2611 System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control);
\r
2616 public void clear() {
\r
2618 expandedQueries.clear();
\r
2622 public void detached(GraphExplorer explorer) {
\r
2624 if (tree != null) {
\r
2625 tree.removeListener(SWT.Expand, treeListener);
\r
2626 tree.removeListener(SWT.Collapse, treeListener);
\r
2632 private void printTree(TreeNode node, int depth) {
\r
2634 for (int i = 0; i < depth; i++) {
\r
2638 System.out.println(s);
\r
2640 for (TreeNode n : node.getChildren()) {
\r
2647 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used)
\r
2649 final private static class GECacheKey {
\r
2651 private NodeContext context;
\r
2652 private CacheKey<?> key;
\r
2654 GECacheKey(NodeContext context, CacheKey<?> key) {
\r
2655 this.context = context;
\r
2657 if (context == null || key == null)
\r
2658 throw new IllegalArgumentException("Null context or key is not accepted");
\r
2661 GECacheKey(GECacheKey other) {
\r
2662 this.context = other.context;
\r
2663 this.key = other.key;
\r
2664 if (context == null || key == null)
\r
2665 throw new IllegalArgumentException("Null context or key is not accepted");
\r
2668 void setValues(NodeContext context, CacheKey<?> key) {
\r
2669 this.context = context;
\r
2671 if (context == null || key == null)
\r
2672 throw new IllegalArgumentException("Null context or key is not accepted");
\r
2676 public int hashCode() {
\r
2677 return context.hashCode() | key.hashCode();
\r
2681 public boolean equals(Object object) {
\r
2683 if (this == object)
\r
2685 else if (object == null)
\r
2688 GECacheKey i = (GECacheKey) object;
\r
2690 return key.equals(i.key) && context.equals(i.context);
\r
2697 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data.
\r
2699 private static class GECache2 implements IGECache {
\r
2701 final HashMap<GECacheKey, IGECacheEntry> entries = new HashMap<GECacheKey, IGECacheEntry>();
\r
2702 final HashMap<GECacheKey, Set<UIElementReference>> treeReferences = new HashMap<GECacheKey, Set<UIElementReference>>();
\r
2703 final HashMap<NodeContext, Set<GECacheKey>> keyRefs = new HashMap<NodeContext, Set<GECacheKey>>();
\r
2706 * This single instance is used for all get operations from the cache. This
\r
2707 * should work since the GE cache is meant to be single-threaded within the
\r
2708 * current UI thread, what ever that thread is. For put operations which
\r
2709 * store the key, this is not used.
\r
2711 NodeContext getNC = new NodeContext() {
\r
2712 @SuppressWarnings("rawtypes")
\r
2714 public Object getAdapter(Class adapter) {
\r
2719 public <T> T getConstant(ConstantKey<T> key) {
\r
2724 public Set<ConstantKey<?>> getKeys() {
\r
2725 return Collections.emptySet();
\r
2728 CacheKey<?> getCK = new CacheKey<Object>() {
\r
2730 public Object processorIdenfitier() {
\r
2734 GECacheKey getKey = new GECacheKey(getNC, getCK);
\r
2737 private void addKey(GECacheKey key) {
\r
2738 Set<GECacheKey> refs = keyRefs.get(key.context);
\r
2739 if (refs != null) {
\r
2742 refs = new HashSet<GECacheKey>();
\r
2744 keyRefs.put(key.context, refs);
\r
2748 private void removeKey(GECacheKey key) {
\r
2749 Set<GECacheKey> refs = keyRefs.get(key.context);
\r
2750 if (refs != null) {
\r
2755 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key, T value) {
\r
2756 // if (DEBUG) System.out.println("Add entry " + context + " " + key);
\r
2757 IGECacheEntry entry = new GECacheEntry(context, key, value);
\r
2758 GECacheKey gekey = new GECacheKey(context, key);
\r
2759 entries.put(gekey, entry);
\r
2764 @SuppressWarnings("unchecked")
\r
2765 public <T> T get(NodeContext context, CacheKey<T> key) {
\r
2766 getKey.setValues(context, key);
\r
2767 IGECacheEntry entry = entries.get(getKey);
\r
2768 if (entry == null)
\r
2770 return (T) entry.getValue();
\r
2774 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
\r
2775 assert(context != null);
\r
2776 assert(key != null);
\r
2777 getKey.setValues(context, key);
\r
2778 return entries.get(getKey);
\r
2782 public <T> void remove(NodeContext context, CacheKey<T> key) {
\r
2783 // if (DEBUG) System.out.println("Remove entry " + context + " " + key);
\r
2784 getKey.setValues(context, key);
\r
2785 entries.remove(getKey);
\r
2786 removeKey(getKey);
\r
2790 public <T> Set<UIElementReference> getTreeReference(NodeContext context, CacheKey<T> key) {
\r
2791 assert(context != null);
\r
2792 assert(key != null);
\r
2793 getKey.setValues(context, key);
\r
2794 return treeReferences.get(getKey);
\r
2798 public <T> void putTreeReference(NodeContext context, CacheKey<T> key, UIElementReference reference) {
\r
2799 assert(context != null);
\r
2800 assert(key != null);
\r
2801 //if (DEBUG) System.out.println("Add tree reference " + context + " " + key);
\r
2802 getKey.setValues(context, key);
\r
2803 Set<UIElementReference> refs = treeReferences.get(getKey);
\r
2804 if (refs != null) {
\r
2805 refs.add(reference);
\r
2807 refs = new HashSet<UIElementReference>(4);
\r
2808 refs.add(reference);
\r
2809 GECacheKey gekey = new GECacheKey(getKey);
\r
2810 treeReferences.put(gekey, refs);
\r
2816 public <T> Set<UIElementReference> removeTreeReference(NodeContext context, CacheKey<T> key) {
\r
2817 assert(context != null);
\r
2818 assert(key != null);
\r
2819 //if (DEBUG) System.out.println("Remove tree reference " + context + " " + key);
\r
2820 getKey.setValues(context, key);
\r
2821 removeKey(getKey);
\r
2822 return treeReferences.remove(getKey);
\r
2826 public boolean isShown(NodeContext context) {
\r
2827 return references.get(context) > 0;
\r
2830 private TObjectIntHashMap<NodeContext> references = new TObjectIntHashMap<NodeContext>();
\r
2833 public void incRef(NodeContext context) {
\r
2834 int exist = references.get(context);
\r
2835 references.put(context, exist+1);
\r
2839 public void decRef(NodeContext context) {
\r
2840 int exist = references.get(context);
\r
2841 references.put(context, exist-1);
\r
2843 references.remove(context);
\r
2847 public void dispose() {
\r
2848 references.clear();
\r
2850 treeReferences.clear();
\r
2854 public void dispose(NodeContext context) {
\r
2855 Set<GECacheKey> keys = keyRefs.remove(context);
\r
2856 if (keys != null) {
\r
2857 for (GECacheKey key : keys) {
\r
2858 entries.remove(key);
\r
2859 treeReferences.remove(key);
\r
2867 * Non-functional cache to replace actual cache when GEContext is disposed.
\r
2872 private static class DummyCache extends GECache2 {
\r
2875 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
\r
2880 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key,
\r
2886 public <T> void putTreeReference(NodeContext context, CacheKey<T> key,
\r
2887 UIElementReference reference) {
\r
2891 public <T> T get(NodeContext context, CacheKey<T> key) {
\r
2896 public <T> Set<UIElementReference> getTreeReference(
\r
2897 NodeContext context, CacheKey<T> key) {
\r
2902 public <T> void remove(NodeContext context, CacheKey<T> key) {
\r
2907 public <T> Set<UIElementReference> removeTreeReference(
\r
2908 NodeContext context, CacheKey<T> key) {
\r
2913 public boolean isShown(NodeContext context) {
\r
2918 public void incRef(NodeContext context) {
\r
2923 public void decRef(NodeContext context) {
\r
2928 public void dispose() {
\r