1 /*******************************************************************************
2 * Copyright (c) 2013 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.browsing.ui.swt;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.Deque;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.LinkedList;
22 import java.util.List;
25 import java.util.WeakHashMap;
26 import java.util.concurrent.CopyOnWriteArrayList;
27 import java.util.concurrent.ExecutorService;
28 import java.util.concurrent.ScheduledExecutorService;
29 import java.util.concurrent.Semaphore;
30 import java.util.concurrent.TimeUnit;
31 import java.util.concurrent.atomic.AtomicBoolean;
32 import java.util.concurrent.atomic.AtomicReference;
33 import java.util.function.BiFunction;
34 import java.util.function.Consumer;
36 import org.eclipse.core.runtime.Assert;
37 import org.eclipse.core.runtime.IAdaptable;
38 import org.eclipse.core.runtime.IProgressMonitor;
39 import org.eclipse.core.runtime.IStatus;
40 import org.eclipse.core.runtime.MultiStatus;
41 import org.eclipse.core.runtime.Platform;
42 import org.eclipse.core.runtime.Status;
43 import org.eclipse.core.runtime.jobs.Job;
44 import org.eclipse.jface.layout.GridDataFactory;
45 import org.eclipse.jface.layout.TreeColumnLayout;
46 import org.eclipse.jface.resource.ColorDescriptor;
47 import org.eclipse.jface.resource.DeviceResourceException;
48 import org.eclipse.jface.resource.DeviceResourceManager;
49 import org.eclipse.jface.resource.FontDescriptor;
50 import org.eclipse.jface.resource.ImageDescriptor;
51 import org.eclipse.jface.resource.JFaceResources;
52 import org.eclipse.jface.resource.LocalResourceManager;
53 import org.eclipse.jface.viewers.CellEditor;
54 import org.eclipse.jface.viewers.CellLabelProvider;
55 import org.eclipse.jface.viewers.ColumnViewer;
56 import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
57 import org.eclipse.jface.viewers.ColumnViewerEditorActivationListener;
58 import org.eclipse.jface.viewers.ColumnViewerEditorDeactivationEvent;
59 import org.eclipse.jface.viewers.ColumnWeightData;
60 import org.eclipse.jface.viewers.ComboBoxCellEditor;
61 import org.eclipse.jface.viewers.DialogCellEditor;
62 import org.eclipse.jface.viewers.EditingSupport;
63 import org.eclipse.jface.viewers.ICellEditorValidator;
64 import org.eclipse.jface.viewers.IPostSelectionProvider;
65 import org.eclipse.jface.viewers.ISelection;
66 import org.eclipse.jface.viewers.ISelectionChangedListener;
67 import org.eclipse.jface.viewers.ISelectionProvider;
68 import org.eclipse.jface.viewers.ITreeContentProvider;
69 import org.eclipse.jface.viewers.ITreeViewerListener;
70 import org.eclipse.jface.viewers.SelectionChangedEvent;
71 import org.eclipse.jface.viewers.StructuredSelection;
72 import org.eclipse.jface.viewers.TextCellEditor;
73 import org.eclipse.jface.viewers.TreeExpansionEvent;
74 import org.eclipse.jface.viewers.TreeSelection;
75 import org.eclipse.jface.viewers.TreeViewer;
76 import org.eclipse.jface.viewers.TreeViewerColumn;
77 import org.eclipse.jface.viewers.Viewer;
78 import org.eclipse.jface.viewers.ViewerCell;
79 import org.eclipse.swt.SWT;
80 import org.eclipse.swt.events.DisposeEvent;
81 import org.eclipse.swt.events.DisposeListener;
82 import org.eclipse.swt.events.FocusEvent;
83 import org.eclipse.swt.events.FocusListener;
84 import org.eclipse.swt.events.KeyEvent;
85 import org.eclipse.swt.events.KeyListener;
86 import org.eclipse.swt.events.MouseEvent;
87 import org.eclipse.swt.events.MouseListener;
88 import org.eclipse.swt.events.SelectionListener;
89 import org.eclipse.swt.graphics.Color;
90 import org.eclipse.swt.graphics.Font;
91 import org.eclipse.swt.graphics.Image;
92 import org.eclipse.swt.graphics.Point;
93 import org.eclipse.swt.graphics.RGB;
94 import org.eclipse.swt.layout.FillLayout;
95 import org.eclipse.swt.widgets.Composite;
96 import org.eclipse.swt.widgets.Control;
97 import org.eclipse.swt.widgets.Display;
98 import org.eclipse.swt.widgets.Event;
99 import org.eclipse.swt.widgets.Listener;
100 import org.eclipse.swt.widgets.ScrollBar;
101 import org.eclipse.swt.widgets.Tree;
102 import org.eclipse.swt.widgets.TreeColumn;
103 import org.eclipse.swt.widgets.TreeItem;
104 import org.eclipse.ui.PlatformUI;
105 import org.eclipse.ui.contexts.IContextActivation;
106 import org.eclipse.ui.contexts.IContextService;
107 import org.eclipse.ui.services.IServiceLocator;
108 import org.eclipse.ui.swt.IFocusService;
109 import org.simantics.browsing.ui.BuiltinKeys;
110 import org.simantics.browsing.ui.Column;
111 import org.simantics.browsing.ui.Column.Align;
112 import org.simantics.browsing.ui.DataSource;
113 import org.simantics.browsing.ui.ExplorerState;
114 import org.simantics.browsing.ui.GraphExplorer;
115 import org.simantics.browsing.ui.NodeContext;
116 import org.simantics.browsing.ui.NodeContext.CacheKey;
117 import org.simantics.browsing.ui.NodeContext.PrimitiveQueryKey;
118 import org.simantics.browsing.ui.NodeContext.QueryKey;
119 import org.simantics.browsing.ui.NodeQueryManager;
120 import org.simantics.browsing.ui.NodeQueryProcessor;
121 import org.simantics.browsing.ui.PrimitiveQueryProcessor;
122 import org.simantics.browsing.ui.PrimitiveQueryUpdater;
123 import org.simantics.browsing.ui.SelectionDataResolver;
124 import org.simantics.browsing.ui.SelectionFilter;
125 import org.simantics.browsing.ui.StatePersistor;
126 import org.simantics.browsing.ui.common.AdaptableHintContext;
127 import org.simantics.browsing.ui.common.ColumnKeys;
128 import org.simantics.browsing.ui.common.ErrorLogger;
129 import org.simantics.browsing.ui.common.NodeContextBuilder;
130 import org.simantics.browsing.ui.common.NodeContextUtil;
131 import org.simantics.browsing.ui.common.internal.GENodeQueryManager;
132 import org.simantics.browsing.ui.common.internal.IGECache;
133 import org.simantics.browsing.ui.common.internal.IGraphExplorerContext;
134 import org.simantics.browsing.ui.common.internal.UIElementReference;
135 import org.simantics.browsing.ui.common.processors.AbstractPrimitiveQueryProcessor;
136 import org.simantics.browsing.ui.common.processors.DefaultCheckedStateProcessor;
137 import org.simantics.browsing.ui.common.processors.DefaultComparableChildrenProcessor;
138 import org.simantics.browsing.ui.common.processors.DefaultFinalChildrenProcessor;
139 import org.simantics.browsing.ui.common.processors.DefaultImageDecoratorProcessor;
140 import org.simantics.browsing.ui.common.processors.DefaultImagerFactoriesProcessor;
141 import org.simantics.browsing.ui.common.processors.DefaultImagerProcessor;
142 import org.simantics.browsing.ui.common.processors.DefaultLabelDecoratorProcessor;
143 import org.simantics.browsing.ui.common.processors.DefaultLabelerFactoriesProcessor;
144 import org.simantics.browsing.ui.common.processors.DefaultLabelerProcessor;
145 import org.simantics.browsing.ui.common.processors.DefaultPrunedChildrenProcessor;
146 import org.simantics.browsing.ui.common.processors.DefaultSelectedImageDecoratorFactoriesProcessor;
147 import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelDecoratorFactoriesProcessor;
148 import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelerProcessor;
149 import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointFactoryProcessor;
150 import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointProcessor;
151 import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionProcessor;
152 import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionsProcessor;
153 import org.simantics.browsing.ui.common.processors.DefaultViewpointProcessor;
154 import org.simantics.browsing.ui.common.processors.IsExpandedProcessor;
155 import org.simantics.browsing.ui.common.processors.NoSelectionRequestProcessor;
156 import org.simantics.browsing.ui.common.processors.ProcessorLifecycle;
157 import org.simantics.browsing.ui.content.ImageDecorator;
158 import org.simantics.browsing.ui.content.Imager;
159 import org.simantics.browsing.ui.content.LabelDecorator;
160 import org.simantics.browsing.ui.content.Labeler;
161 import org.simantics.browsing.ui.content.Labeler.CustomModifier;
162 import org.simantics.browsing.ui.content.Labeler.DialogModifier;
163 import org.simantics.browsing.ui.content.Labeler.EnumerationModifier;
164 import org.simantics.browsing.ui.content.Labeler.Modifier;
165 import org.simantics.browsing.ui.swt.internal.Threads;
166 import org.simantics.db.layer0.SelectionHints;
167 import org.simantics.ui.SimanticsUI;
168 import org.simantics.utils.datastructures.BijectionMap;
169 import org.simantics.utils.datastructures.MapList;
170 import org.simantics.utils.datastructures.disposable.AbstractDisposable;
171 import org.simantics.utils.datastructures.hints.IHintContext;
172 import org.simantics.utils.threads.IThreadWorkQueue;
173 import org.simantics.utils.threads.SWTThread;
174 import org.simantics.utils.threads.ThreadUtils;
175 import org.simantics.utils.ui.AdaptionUtils;
176 import org.simantics.utils.ui.ISelectionUtils;
177 import org.simantics.utils.ui.jface.BasePostSelectionProvider;
179 import gnu.trove.map.hash.THashMap;
180 import gnu.trove.map.hash.TObjectIntHashMap;
183 * TreeView based GraphExplorer
186 * @author Marko Luukkainen <marko.luukkainen@vtt.fi>
188 public class GraphExplorerImpl2 extends GraphExplorerImplBase implements GraphExplorer {
190 private static final boolean DEBUG_SELECTION_LISTENERS = false;
191 private static final boolean DEBUG = false;
193 private TreeViewer viewer;
195 private LocalResourceManager localResourceManager;
196 private DeviceResourceManager resourceManager;
199 private IThreadWorkQueue thread;
201 @SuppressWarnings({ "rawtypes" })
202 final HashMap<CacheKey<?>, NodeQueryProcessor> processors = new HashMap<CacheKey<?>, NodeQueryProcessor>();
203 @SuppressWarnings({ "rawtypes" })
204 final HashMap<Object, PrimitiveQueryProcessor> primitiveProcessors = new HashMap<Object, PrimitiveQueryProcessor>();
205 @SuppressWarnings({ "rawtypes" })
206 final HashMap<Class, DataSource> dataSources = new HashMap<Class, DataSource>();
208 private FontDescriptor originalFont;
209 protected ColorDescriptor originalForeground;
210 protected ColorDescriptor originalBackground;
211 private Color invalidModificationColor;
213 private Column[] columns;
214 private Map<String,Integer> columnKeyToIndex;
215 private boolean columnsAreVisible = true;
218 private NodeContext rootContext;
219 private TreeNode rootNode;
220 private StatePersistor persistor = null;
222 private boolean editable = true;
224 private boolean disposed = false;
226 private final CopyOnWriteArrayList<FocusListener> focusListeners = new CopyOnWriteArrayList<FocusListener>();
227 private final CopyOnWriteArrayList<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();
228 private final CopyOnWriteArrayList<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>();
230 private int autoExpandLevel = 0;
231 private IServiceLocator serviceLocator;
232 private IContextService contextService = null;
233 private IFocusService focusService = null;
234 private IContextActivation editingContext = null;
236 GeViewerContext explorerContext = new GeViewerContext(this);
238 private GraphExplorerPostSelectionProvider postSelectionProvider = new GraphExplorerPostSelectionProvider(this);
239 private BasePostSelectionProvider selectionProvider = new BasePostSelectionProvider();
240 private SelectionDataResolver selectionDataResolver;
241 private SelectionFilter selectionFilter;
243 private Set<TreeNode> collapsedNodes = new HashSet<TreeNode>();
244 private MapList<NodeContext, TreeNode> contextToNodeMap = new MapList<NodeContext, TreeNode>();
246 private ModificationContext modificationContext = null;
248 private boolean filterSelectionEdit = true;
250 private TreeColumnLayout treeColumnLayout;
252 private boolean expand;
253 private boolean verticalBarVisible = false;
255 private BiFunction<GraphExplorer, Object[], Object[]> selectionTransformation = new BiFunction<GraphExplorer, Object[], Object[]>() {
258 public Object[] apply(GraphExplorer explorer, Object[] objects) {
259 Object[] result = new Object[objects.length];
260 for (int i = 0; i < objects.length; i++) {
261 IHintContext context = new AdaptableHintContext(SelectionHints.KEY_MAIN);
262 context.setHint(SelectionHints.KEY_MAIN, objects[i]);
270 static class TransientStateImpl implements TransientExplorerState {
272 private Integer activeColumn = null;
275 public synchronized Integer getActiveColumn() {
279 public synchronized void setActiveColumn(Integer column) {
280 activeColumn = column;
285 private TransientStateImpl transientState = new TransientStateImpl();
288 public GraphExplorerImpl2(Composite parent) {
289 this(parent, SWT.BORDER | SWT.MULTI );
292 public GraphExplorerImpl2(Composite parent, int style) {
293 this.localResourceManager = new LocalResourceManager(JFaceResources.getResources());
294 this.resourceManager = new DeviceResourceManager(parent.getDisplay());
296 this.imageLoaderJob = new ImageLoaderJob(this);
297 this.imageLoaderJob.setPriority(Job.DECORATE);
299 invalidModificationColor = (Color) localResourceManager.get(ColorDescriptor.createFrom(new RGB(255, 128, 128)));
301 this.thread = SWTThread.getThreadAccess(parent);
303 for (int i = 0; i < 10; i++)
304 explorerContext.activity.push(0);
306 boolean useLayout = true;
307 // FIXME: hack, GraphExplorerComposite uses its own TreeColumnLayout.
308 if (useLayout && !(parent.getLayout() instanceof TreeColumnLayout)) {
310 Composite rootTreeComposite = new Composite(parent, SWT.NONE);
311 treeColumnLayout = new TreeColumnLayout();
312 rootTreeComposite.setLayout(treeColumnLayout);
314 viewer = new TreeViewer(rootTreeComposite,style|SWT.H_SCROLL|SWT.V_SCROLL);
316 GridDataFactory.fillDefaults().grab(true, true).span(3,1).applyTo(rootTreeComposite);
319 viewer = new TreeViewer(parent,style | SWT.H_SCROLL | SWT.V_SCROLL);
322 viewer.getColumnViewerEditor().addEditorActivationListener(new ColumnViewerEditorActivationListener() {
325 public void beforeEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {
330 public void beforeEditorActivated(ColumnViewerEditorActivationEvent event) {
331 // cancel editor activation for double click events.
332 // TODO: this may not work similarly to GraphExplorerImpl
333 if ((event.time - focusGainedAt) < 250L) {
339 public void afterEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {
344 public void afterEditorActivated(ColumnViewerEditorActivationEvent event) {
349 viewer.setUseHashlookup(true);
350 viewer.setContentProvider(new GeViewerContentProvider());
354 originalFont = JFaceResources.getDefaultFontDescriptor();
356 viewer.getTree().setFont((Font) localResourceManager.get(originalFont));
359 setDefaultProcessors();
361 viewer.getTree().addDisposeListener(new DisposeListener() {
364 public void widgetDisposed(DisposeEvent e) {
371 // Add listener to tree for delayed tree population.
373 Listener listener = new Listener() {
376 public void handleEvent(Event event) {
378 switch (event.type) {
394 viewer.getTree().addListener(SWT.Activate, listener);
395 viewer.getTree().addListener(SWT.Deactivate, listener);
396 viewer.getTree().addListener(SWT.Show, listener);
397 viewer.getTree().addListener(SWT.Hide, listener);
398 viewer.getTree().addListener(SWT.Paint,listener);
401 viewer.addTreeListener(new ITreeViewerListener() {
404 public void treeExpanded(TreeExpansionEvent event) {
409 public void treeCollapsed(TreeExpansionEvent event) {
410 collapsedNodes.add((TreeNode)event.getElement());
414 setColumns( new Column[] { new Column(ColumnKeys.SINGLE) });
417 private long focusGainedAt = 0L;
418 private boolean visible = false;
420 private Collection<TreeNode> selectedNodes = new ArrayList<TreeNode>();
422 protected void setBasicListeners() {
423 Tree tree = viewer.getTree();
425 tree.addFocusListener(new FocusListener() {
427 public void focusGained(FocusEvent e) {
428 focusGainedAt = ((long) e.time) & 0xFFFFFFFFL;
429 for (FocusListener listener : focusListeners)
430 listener.focusGained(e);
433 public void focusLost(FocusEvent e) {
434 for (FocusListener listener : focusListeners)
435 listener.focusLost(e);
438 tree.addMouseListener(new MouseListener() {
440 public void mouseDoubleClick(MouseEvent e) {
441 for (MouseListener listener : mouseListeners) {
442 listener.mouseDoubleClick(e);
446 public void mouseDown(MouseEvent e) {
447 for (MouseListener listener : mouseListeners) {
448 listener.mouseDown(e);
452 public void mouseUp(MouseEvent e) {
453 for (MouseListener listener : mouseListeners) {
458 tree.addKeyListener(new KeyListener() {
460 public void keyPressed(KeyEvent e) {
461 for (KeyListener listener : keyListeners) {
462 listener.keyPressed(e);
466 public void keyReleased(KeyEvent e) {
467 for (KeyListener listener : keyListeners) {
468 listener.keyReleased(e);
473 viewer.addSelectionChangedListener(new ISelectionChangedListener() {
476 public void selectionChanged(SelectionChangedEvent event) {
477 //System.out.println("GraphExplorerImpl2.fireSelection");
478 selectedNodes = AdaptionUtils.adaptToCollection(event.getSelection(), TreeNode.class);
479 Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);
480 selectionProvider.setAndFireSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));
484 viewer.addPostSelectionChangedListener(new ISelectionChangedListener() {
487 public void selectionChanged(SelectionChangedEvent event) {
488 //System.out.println("GraphExplorerImpl2.firePostSelection");
489 Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);
490 selectionProvider.firePostSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));
497 private NodeContext pendingRoot;
499 private void activate() {
500 if (pendingRoot != null && !expand) {
501 doSetRoot(pendingRoot);
507 * Invoke only from SWT thread to reset the root of the graph explorer tree.
511 private void doSetRoot(NodeContext root) {
512 Display display = viewer.getTree().getDisplay();
513 if (display.getThread() != Thread.currentThread()) {
514 throw new RuntimeException("Invoke from SWT thread only");
516 // System.out.println("doSetRoot " + root);
519 if (viewer.getTree().isDisposed())
521 if (root.getConstant(BuiltinKeys.INPUT) == null) {
522 ErrorLogger.defaultLogError("root node context does not contain BuiltinKeys.INPUT key. Node = " + root, new Exception("trace"));
528 // Empty caches, release queries.
529 if (rootNode != null) {
532 GeViewerContext oldContext = explorerContext;
533 GeViewerContext newContext = new GeViewerContext(this);
534 this.explorerContext = newContext;
535 oldContext.safeDispose();
537 // Need to empty these or otherwise they won't be emptied until the
538 // explorer is disposed which would mean that many unwanted references
539 // will be held by this map.
540 clearPrimitiveProcessors();
542 this.rootContext = root.getConstant(BuiltinKeys.IS_ROOT) != null ? root
543 : NodeContextUtil.withConstant(root, BuiltinKeys.IS_ROOT, Boolean.TRUE);
545 explorerContext.getCache().incRef(this.rootContext);
551 //refreshColumnSizes();
552 rootNode = new TreeNode(rootContext);
553 if (DEBUG) System.out.println("setRoot " + rootNode);
555 viewer.setInput(rootNode);
557 // Delay content reading.
559 // This is required for cases when GEImpl2 is attached to selection view. Reading content
560 // instantly could stagnate SWT thread under rapid changes in selection. By delaying the
561 // content reading we give the system a change to dispose the GEImpl2 before the content is read.
562 display.asyncExec(new Runnable() {
566 if (rootNode != null) {
567 rootNode.updateChildren();
574 private void initializeState() {
575 if (persistor == null)
578 ExplorerState state = persistor.deserialize(
579 Platform.getStateLocation(Activator.getDefault().getBundle()).toFile(),
583 Object processor = getPrimitiveProcessor(BuiltinKeys.IS_EXPANDED);
584 if (processor instanceof DefaultIsExpandedProcessor) {
585 DefaultIsExpandedProcessor isExpandedProcessor = (DefaultIsExpandedProcessor)processor;
586 for(NodeContext expanded : state.expandedNodes) {
587 isExpandedProcessor.setExpanded(expanded, true);
593 public NodeContext getRoot() {
598 public IThreadWorkQueue getThread() {
603 public NodeContext getParentContext(NodeContext context) {
605 throw new IllegalStateException("disposed");
606 if (!thread.currentThreadAccess())
607 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
609 List<TreeNode> nodes = contextToNodeMap.getValuesUnsafe(context);
610 for (int i = 0; i < nodes.size(); i++) {
611 if (nodes.get(i).getParent() != null)
612 return nodes.get(i).getParent().getContext();
619 @SuppressWarnings("unchecked")
621 public <T> T getAdapter(Class<T> adapter) {
622 if(ISelectionProvider.class == adapter) return (T) postSelectionProvider;
623 else if(IPostSelectionProvider.class == adapter) return (T) postSelectionProvider;
628 protected void setDefaultProcessors() {
629 // Add a simple IMAGER query processor that always returns null.
630 // With this processor no images will ever be shown.
631 // setPrimitiveProcessor(new StaticImagerProcessor(null));
633 setProcessor(new DefaultComparableChildrenProcessor());
634 setProcessor(new DefaultLabelDecoratorsProcessor());
635 setProcessor(new DefaultImageDecoratorsProcessor());
636 setProcessor(new DefaultSelectedLabelerProcessor());
637 setProcessor(new DefaultLabelerFactoriesProcessor());
638 setProcessor(new DefaultSelectedImagerProcessor());
639 setProcessor(new DefaultImagerFactoriesProcessor());
640 setPrimitiveProcessor(new DefaultLabelerProcessor());
641 setPrimitiveProcessor(new DefaultCheckedStateProcessor());
642 setPrimitiveProcessor(new DefaultImagerProcessor());
643 setPrimitiveProcessor(new DefaultLabelDecoratorProcessor());
644 setPrimitiveProcessor(new DefaultImageDecoratorProcessor());
645 setPrimitiveProcessor(new NoSelectionRequestProcessor());
647 setProcessor(new DefaultFinalChildrenProcessor(this));
649 setProcessor(new DefaultPrunedChildrenProcessor());
650 setProcessor(new DefaultSelectedViewpointProcessor());
651 setProcessor(new DefaultSelectedLabelDecoratorFactoriesProcessor());
652 setProcessor(new DefaultSelectedImageDecoratorFactoriesProcessor());
653 setProcessor(new DefaultViewpointContributionsProcessor());
655 setPrimitiveProcessor(new DefaultViewpointProcessor());
656 setPrimitiveProcessor(new DefaultViewpointContributionProcessor());
657 setPrimitiveProcessor(new DefaultSelectedViewpointFactoryProcessor());
658 setPrimitiveProcessor(new TreeNodeIsExpandedProcessor());
659 setPrimitiveProcessor(new DefaultShowMaxChildrenProcessor());
663 public Column[] getColumns() {
664 return Arrays.copyOf(columns, columns.length);
668 public void setColumnsVisible(boolean visible) {
669 columnsAreVisible = visible;
670 if(viewer.getTree() != null) viewer.getTree().setHeaderVisible(columnsAreVisible);
674 public void setColumns(final Column[] columns) {
675 setColumns(columns, null);
679 public void setColumns(final Column[] columns, Consumer<Map<Column, Object>> callback) {
681 checkUniqueColumnKeys(columns);
683 Display d = viewer.getTree().getDisplay();
684 if (d.getThread() == Thread.currentThread()) {
685 doSetColumns(columns, callback);
686 viewer.refresh(true);
688 d.asyncExec(new Runnable() {
693 if (viewer.getTree().isDisposed())
695 doSetColumns(columns, callback);
696 viewer.refresh(true);
697 viewer.getTree().getParent().layout();
702 private void checkUniqueColumnKeys(Column[] cols) {
703 Set<String> usedColumnKeys = new HashSet<String>();
704 List<Column> duplicateColumns = new ArrayList<Column>();
705 for (Column c : cols) {
706 if (!usedColumnKeys.add(c.getKey()))
707 duplicateColumns.add(c);
709 if (!duplicateColumns.isEmpty()) {
710 throw new IllegalArgumentException("All columns do not have unique keys: " + cols + ", overlapping: " + duplicateColumns);
714 private List<TreeViewerColumn> treeViewerColumns = new ArrayList<TreeViewerColumn>();
715 private CellLabelProvider cellLabelProvider = new GeViewerLabelProvider();
716 private List<EditingSupport> editingSupports = new ArrayList<EditingSupport>();
718 private void doSetColumns(Column[] cols, Consumer<Map<Column, Object>> callback) {
719 // Attempt to keep previous column widths.
720 Map<String, Integer> prevWidths = new HashMap<String, Integer>();
722 for (TreeViewerColumn c : treeViewerColumns) {
723 prevWidths.put(c.getColumn().getText(), c.getColumn().getWidth());
724 c.getColumn().dispose();
727 treeViewerColumns.clear();
729 HashMap<String, Integer> keyToIndex = new HashMap<String, Integer>();
730 for (int i = 0; i < cols.length; ++i) {
731 keyToIndex.put(cols[i].getKey(), i);
734 this.columns = Arrays.copyOf(cols, cols.length);
735 //this.columns[cols.length] = FILLER_COLUMN;
736 this.columnKeyToIndex = keyToIndex;
738 Map<Column, Object> map = new HashMap<Column, Object>();
740 // FIXME : temporary workaround for ModelBrowser.
741 viewer.getTree().setHeaderVisible(columns.length == 1 ? false : columnsAreVisible);
745 for (Column column : columns) {
746 TreeViewerColumn tvc = new TreeViewerColumn(viewer, toSWT(column.getAlignment()));
747 treeViewerColumns.add(tvc);
748 tvc.setLabelProvider(cellLabelProvider);
749 EditingSupport support = null;
750 if (editingSupports.size() > columnIndex)
751 support = editingSupports.get(columnIndex);
753 support = new GeEditingSupport(viewer, columnIndex);
754 editingSupports.add(support);
757 tvc.setEditingSupport(support);
759 TreeColumn c = tvc.getColumn();
762 c.setText(column.getLabel());
763 c.setToolTipText(column.getTooltip());
765 int cw = column.getWidth();
767 // Try to keep previous widths
768 Integer w = prevWidths.get(column);
771 else if (cw != Column.DEFAULT_CONTROL_WIDTH)
773 else if (columns.length == 1) {
774 // FIXME : how to handle single column properly?
778 // Go for some kind of default settings then...
779 if (ColumnKeys.PROPERTY.equals(column.getKey()))
784 if (treeColumnLayout != null) {
785 treeColumnLayout.setColumnData(c, new ColumnWeightData(column.getWeight(), true));
788 // if (!column.hasGrab() && !FILLER.equals(column.getKey())) {
789 // c.addListener(SWT.Resize, resizeListener);
790 // c.setResizable(true);
792 // //c.setResizable(false);
799 if(callback != null) callback.accept(map);
802 int toSWT(Align alignment) {
804 case LEFT: return SWT.LEFT;
805 case CENTER: return SWT.CENTER;
806 case RIGHT: return SWT.RIGHT;
807 default: throw new Error("unhandled alignment: " + alignment);
812 public <T> void setProcessor(NodeQueryProcessor<T> processor) {
814 if (processor == null)
815 throw new IllegalArgumentException("null processor");
817 processors.put(processor.getIdentifier(), processor);
821 public <T> void setPrimitiveProcessor(PrimitiveQueryProcessor<T> processor) {
823 if (processor == null)
824 throw new IllegalArgumentException("null processor");
826 PrimitiveQueryProcessor<?> oldProcessor = primitiveProcessors.put(
827 processor.getIdentifier(), processor);
829 if (oldProcessor instanceof ProcessorLifecycle)
830 ((ProcessorLifecycle) oldProcessor).detached(this);
831 if (processor instanceof ProcessorLifecycle)
832 ((ProcessorLifecycle) processor).attached(this);
836 public <T> void setDataSource(DataSource<T> provider) {
838 if (provider == null)
839 throw new IllegalArgumentException("null provider");
840 dataSources.put(provider.getProvidedClass(), provider);
843 @SuppressWarnings("unchecked")
845 public <T> DataSource<T> removeDataSource(Class<T> forProvidedClass) {
847 if (forProvidedClass == null)
848 throw new IllegalArgumentException("null class");
849 return dataSources.remove(forProvidedClass);
853 public void setPersistor(StatePersistor persistor) {
854 this.persistor = persistor;
858 public SelectionDataResolver getSelectionDataResolver() {
859 return selectionDataResolver;
863 public void setSelectionDataResolver(SelectionDataResolver r) {
864 this.selectionDataResolver = r;
868 public SelectionFilter getSelectionFilter() {
869 return selectionFilter;
873 public void setSelectionFilter(SelectionFilter f) {
874 this.selectionFilter = f;
875 // TODO: re-filter current selection?
878 protected ISelection constructSelection(NodeContext... contexts) {
879 if (contexts == null)
880 throw new IllegalArgumentException("null contexts");
881 if (contexts.length == 0)
882 return StructuredSelection.EMPTY;
883 if (selectionFilter == null)
884 return new StructuredSelection(transformSelection(contexts));
885 return new StructuredSelection( transformSelection(filter(selectionFilter, contexts)) );
888 protected Object[] transformSelection(Object[] objects) {
889 return selectionTransformation.apply(this, objects);
892 protected static Object[] filter(SelectionFilter filter, NodeContext[] contexts) {
893 int len = contexts.length;
894 Object[] objects = new Object[len];
895 for (int i = 0; i < len; ++i)
896 objects[i] = filter.filter(contexts[i]);
901 public void setSelectionTransformation(
902 BiFunction<GraphExplorer, Object[], Object[]> f) {
903 this.selectionTransformation = f;
906 public ISelection getWidgetSelection() {
907 return viewer.getSelection();
911 public <T> void addListener(T listener) {
912 if (listener instanceof FocusListener) {
913 focusListeners.add((FocusListener) listener);
914 } else if (listener instanceof MouseListener) {
915 mouseListeners.add((MouseListener) listener);
916 } else if (listener instanceof KeyListener) {
917 keyListeners.add((KeyListener) listener);
922 public <T> void removeListener(T listener) {
923 if (listener instanceof FocusListener) {
924 focusListeners.remove(listener);
925 } else if (listener instanceof MouseListener) {
926 mouseListeners.remove(listener);
927 } else if (listener instanceof KeyListener) {
928 keyListeners.remove(listener);
932 public void addSelectionListener(SelectionListener listener) {
933 viewer.getTree().addSelectionListener(listener);
936 public void removeSelectionListener(SelectionListener listener) {
937 viewer.getTree().removeSelectionListener(listener);
940 private Set<String> uiContexts;
943 public void setUIContexts(Set<String> contexts) {
944 this.uiContexts = contexts;
948 public void setRoot(final Object root) {
949 if(uiContexts != null && uiContexts.size() == 1)
950 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root, BuiltinKeys.UI_CONTEXT, uiContexts.iterator().next()));
952 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root));
956 public void setRootContext(final NodeContext context) {
957 setRootContext0(context);
960 private void setRoot(NodeContext context) {
962 pendingRoot = context;
963 Display.getDefault().asyncExec(new Runnable() {
966 if (viewer != null && !viewer.getTree().isDisposed())
967 viewer.getTree().redraw();
975 private void setRootContext0(final NodeContext context) {
976 Assert.isNotNull(context, "root must not be null");
977 if (isDisposed() || viewer.getTree().isDisposed())
979 Display display = viewer.getTree().getDisplay();
980 if (display.getThread() == Thread.currentThread()) {
983 display.asyncExec(new Runnable() {
993 public void setFocus() {
994 viewer.getTree().setFocus();
997 @SuppressWarnings("unchecked")
999 public <T> T getControl() {
1000 return (T)viewer.getTree();
1005 public boolean isDisposed() {
1009 protected void assertNotDisposed() {
1011 throw new IllegalStateException("disposed");
1015 public boolean isEditable() {
1020 public void setEditable(boolean editable) {
1021 if (!thread.currentThreadAccess())
1022 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
1024 this.editable = editable;
1025 Display display = viewer.getTree().getDisplay();
1026 viewer.getTree().setBackground(editable ? null : display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
1029 private void doDispose() {
1033 // TODO: Since GENodeQueryManager is cached in QueryChache and it refers to this class
1034 // we have to remove all references here to reduce memory consumption.
1036 // Proper fix would be to remove references between QueryCache and GENodeQueryManagers.
1037 explorerContext.dispose();
1038 explorerContext = null;
1040 detachPrimitiveProcessors();
1041 primitiveProcessors.clear();
1042 dataSources.clear();
1043 pendingItems.clear();
1045 mouseListeners.clear();
1046 selectionProvider.clearListeners();
1047 selectionProvider = null;
1048 selectionDataResolver = null;
1049 selectedNodes.clear();
1050 selectedNodes = null;
1051 selectionTransformation = null;
1052 originalFont = null;
1053 localResourceManager.dispose();
1054 localResourceManager = null;
1055 // Must shutdown image loader job before disposing its ResourceManager
1056 imageLoaderJob.dispose();
1057 imageLoaderJob.cancel();
1059 imageLoaderJob.join();
1060 imageLoaderJob = null;
1061 } catch (InterruptedException e) {
1062 ErrorLogger.defaultLogError(e);
1064 resourceManager.dispose();
1065 resourceManager = null;
1066 collapsedNodes.clear();
1067 collapsedNodes = null;
1068 if (rootNode != null) {
1072 contextToNodeMap.clear(); // should be empty at this point.
1073 contextToNodeMap = null;
1074 if (postSelectionProvider != null) {
1075 postSelectionProvider.dispose();
1076 postSelectionProvider = null;
1079 modificationContext = null;
1080 focusService = null;
1081 contextService = null;
1082 serviceLocator = null;
1084 columnKeyToIndex.clear();
1085 columnKeyToIndex = null;
1091 public boolean select(NodeContext context) {
1093 assertNotDisposed();
1095 if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) {
1096 viewer.setSelection(new StructuredSelection());
1097 selectionProvider.setAndFireNonEqualSelection(TreeSelection.EMPTY);
1101 viewer.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0)));
1108 public boolean selectPath(Collection<NodeContext> contexts) {
1110 if(contexts == null) throw new IllegalArgumentException("Null list is not allowed");
1111 if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed");
1113 return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0);
1117 private boolean selectPathInternal(NodeContext[] contexts, int position) {
1119 NodeContext head = contexts[position];
1121 if(position == contexts.length-1) {
1122 return select(head);
1126 setExpanded(head, true);
1127 if(!waitVisible(contexts[position+1])) return false;
1129 return selectPathInternal(contexts, position+1);
1133 private boolean waitVisible(NodeContext context) {
1134 long start = System.nanoTime();
1135 while(!isVisible(context)) {
1136 Display.getCurrent().readAndDispatch();
1137 long duration = System.nanoTime() - start;
1138 if(duration > 10e9) return false;
1144 public boolean isVisible(NodeContext context) {
1145 if (contextToNodeMap.getValuesUnsafe(context).size() == 0)
1148 Object elements[] = viewer.getVisibleExpandedElements();
1149 return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0));
1155 public TransientExplorerState getTransientState() {
1156 if (!thread.currentThreadAccess())
1157 throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread());
1158 return transientState;
1162 public <T> T query(NodeContext context, CacheKey<T> key) {
1163 return this.explorerContext.cache.get(context, key);
1167 * For setting a more local service locator for the explorer than the global
1168 * workbench service locator. Sometimes required to give this implementation
1169 * access to local workbench services like IFocusService.
1172 * Must be invoked during right after construction.
1174 * @param serviceLocator
1175 * a specific service locator or <code>null</code> to use the
1176 * workbench global service locator
1178 public void setServiceLocator(IServiceLocator serviceLocator) {
1179 if (serviceLocator == null && PlatformUI.isWorkbenchRunning())
1180 serviceLocator = PlatformUI.getWorkbench();
1181 this.serviceLocator = serviceLocator;
1182 if (serviceLocator != null) {
1183 this.contextService = (IContextService) serviceLocator.getService(IContextService.class);
1184 this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class);
1188 private void detachPrimitiveProcessors() {
1189 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
1190 if (p instanceof ProcessorLifecycle) {
1191 ((ProcessorLifecycle) p).detached(this);
1196 private void clearPrimitiveProcessors() {
1197 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
1198 if (p instanceof ProcessorLifecycle) {
1199 ((ProcessorLifecycle) p).clear();
1205 public void setExpanded(NodeContext context, boolean expanded) {
1206 viewer.setExpandedState(context, expanded);
1211 public void setAutoExpandLevel(int level) {
1212 this.autoExpandLevel = level;
1213 viewer.setAutoExpandLevel(level);
1216 int maxChildren = GraphExplorerImpl.DEFAULT_MAX_CHILDREN;
1219 public int getMaxChildren() {
1224 public void setMaxChildren(int maxChildren) {
1225 this.maxChildren = maxChildren;
1230 public int getMaxChildren(NodeQueryManager manager, NodeContext context) {
1231 Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN);
1232 //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result);
1233 if (result != null) {
1235 throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result);
1242 public <T> NodeQueryProcessor<T> getProcessor(QueryKey<T> key) {
1243 return explorerContext.getProcessor(key);
1247 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(PrimitiveQueryKey<T> key) {
1248 return explorerContext.getPrimitiveProcessor(key);
1251 private HashSet<UpdateItem> pendingItems = new HashSet<UpdateItem>();
1252 private boolean updating = false;
1253 private int updateCounter = 0;
1254 final ScheduledExecutorService uiUpdateScheduler = ThreadUtils.getNonBlockingWorkExecutor();
1256 private class UpdateItem {
1260 public UpdateItem(TreeNode element) {
1264 public UpdateItem(TreeNode element, int columnIndex) {
1265 this.element = element;
1266 this.columnIndex = columnIndex;
1267 if (element != null && element.isDisposed()) {
1268 throw new IllegalArgumentException("Node is disposed. " + element);
1272 public void update(TreeViewer viewer) {
1273 if (element != null) {
1275 if (element.isDisposed()) {
1278 if (((TreeNode)element).updateChildren()) {
1279 viewer.refresh(element,true);
1281 if (columnIndex >= 0) {
1282 viewer.update(element, new String[]{columns[columnIndex].getKey()});
1284 viewer.refresh(element,true);
1288 if (!element.isDisposed() && autoExpandLevel > 1 && !collapsedNodes.contains(element) && ((TreeNode)element).distanceToRoot() <= autoExpandLevel) {
1290 viewer.setExpandedState(element, true);
1294 if (rootNode.updateChildren())
1295 viewer.refresh(rootNode,true);
1300 public boolean equals(Object obj) {
1303 if (obj.getClass() != getClass())
1305 UpdateItem other = (UpdateItem)obj;
1306 if (columnIndex != other.columnIndex)
1308 if (element != null)
1309 return element.equals(other.element);
1310 return other.element == null;
1314 public int hashCode() {
1315 if (element != null)
1316 return element.hashCode() + columnIndex;
1321 private void update(final TreeNode element, final int columnIndex) {
1322 if (DEBUG)System.out.println("update " + element + " " + columnIndex);
1323 if (viewer.getTree().isDisposed())
1325 synchronized (pendingItems) {
1326 pendingItems.add(new UpdateItem(element, columnIndex));
1327 if (updating) return;
1333 private void update(final TreeNode element) {
1334 if (DEBUG)System.out.println("update " + element);
1335 if (viewer.getTree().isDisposed())
1337 if (element != null && element.isDisposed())
1339 synchronized (pendingItems) {
1341 pendingItems.add(new UpdateItem(element));
1342 if (updating) return;
1348 boolean scheduleUpdater() {
1350 if (viewer.getTree().isDisposed())
1353 if (!pendingItems.isEmpty()) {
1355 int activity = explorerContext.activityInt;
1357 if (activity < 100) {
1358 //System.out.println("Scheduling update immediately.");
1359 } else if (activity < 1000) {
1360 //System.out.println("Scheduling update after 500ms.");
1363 //System.out.println("Scheduling update after 3000ms.");
1369 //System.out.println("Scheduling UI update after " + delay + " ms.");
1370 uiUpdateScheduler.schedule(new Runnable() {
1374 if (viewer == null || viewer.getTree().isDisposed())
1377 if (updateCounter > 0) {
1379 uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS);
1381 viewer.getTree().getDisplay().asyncExec(new UpdateRunner(GraphExplorerImpl2.this, GraphExplorerImpl2.this.explorerContext));
1385 }, delay, TimeUnit.MILLISECONDS);
1395 public String startEditing(NodeContext context, String columnKey) {
1396 assertNotDisposed();
1397 if (!thread.currentThreadAccess())
1398 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
1400 if(columnKey.startsWith("#")) {
1401 columnKey = columnKey.substring(1);
1404 Integer columnIndex = columnKeyToIndex.get(columnKey);
1405 if (columnIndex == null)
1406 return "Rename not supported for selection";
1408 viewer.editElement(context, columnIndex);
1409 if(viewer.isCellEditorActive()) return null;
1410 return "Rename not supported for selection";
1414 public String startEditing(String columnKey) {
1415 ISelection selection = postSelectionProvider.getSelection();
1416 if(selection == null) return "Rename not supported for selection";
1417 NodeContext context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class);
1418 if(context == null) return "Rename not supported for selection";
1420 return startEditing(context, columnKey);
1424 public void setSelection(final ISelection selection, boolean forceControlUpdate) {
1425 assertNotDisposed();
1426 boolean equalsOld = selectionProvider.selectionEquals(selection);
1427 if (equalsOld && !forceControlUpdate) {
1428 // Just set the selection object instance, fire no events nor update
1429 // the viewer selection.
1430 selectionProvider.setSelection(selection);
1432 Collection<NodeContext> coll = AdaptionUtils.adaptToCollection(selection, NodeContext.class);
1433 Collection<TreeNode> nodes = new ArrayList<TreeNode>();
1434 for (NodeContext c : coll) {
1435 List<TreeNode> match = contextToNodeMap.getValuesUnsafe(c);
1436 if(match.size() > 0)
1437 nodes.add(match.get(0));
1439 final ISelection sel = new StructuredSelection(nodes.toArray());
1440 if (coll.size() == 0)
1442 // Schedule viewer and selection update if necessary.
1443 if (viewer.getTree().isDisposed())
1445 Display d = viewer.getTree().getDisplay();
1446 if (d.getThread() == Thread.currentThread()) {
1447 viewer.setSelection(sel);
1449 d.asyncExec(new Runnable() {
1452 if (viewer.getTree().isDisposed())
1454 viewer.setSelection(sel);
1462 public void setModificationContext(ModificationContext modificationContext) {
1463 this.modificationContext = modificationContext;
1467 final ExecutorService queryUpdateScheduler = Threads.getExecutor();
1469 private static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {
1470 // This is for query debugging only.
1472 private GraphExplorerImpl2 ge;
1473 int queryIndent = 0;
1475 GECache2 cache = new GECache2();
1476 AtomicBoolean propagating = new AtomicBoolean(false);
1477 Object propagateList = new Object();
1478 Object propagate = new Object();
1479 List<Runnable> scheduleList = new ArrayList<Runnable>();
1480 final Deque<Integer> activity = new LinkedList<Integer>();
1481 int activityInt = 0;
1483 AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();
1486 * Keeps track of nodes that have already been auto-expanded. After
1487 * being inserted into this set, nodes will not be forced to stay in an
1488 * expanded state after that. This makes it possible for the user to
1489 * close auto-expanded nodes.
1491 Map<NodeContext, Boolean> autoExpanded = new WeakHashMap<NodeContext, Boolean>();
1493 public GeViewerContext(GraphExplorerImpl2 ge) {
1498 protected void doDispose() {
1500 autoExpanded.clear();
1504 public IGECache getCache() {
1509 public int queryIndent() {
1514 public int queryIndent(int offset) {
1515 queryIndent += offset;
1520 @SuppressWarnings("unchecked")
1521 public <T> NodeQueryProcessor<T> getProcessor(Object o) {
1524 return ge.processors.get(o);
1528 @SuppressWarnings("unchecked")
1529 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {
1530 return ge.primitiveProcessors.get(o);
1533 @SuppressWarnings("unchecked")
1535 public <T> DataSource<T> getDataSource(Class<T> clazz) {
1536 return ge.dataSources.get(clazz);
1540 public void update(UIElementReference ref) {
1541 if (ref instanceof ViewerCellReference) {
1542 ViewerCellReference tiref = (ViewerCellReference) ref;
1543 Object element = tiref.getElement();
1544 int columnIndex = tiref.getColumn();
1545 // NOTE: must be called regardless of the the item value.
1546 // A null item is currently used to indicate a tree root update.
1547 ge.update((TreeNode)element,columnIndex);
1548 } else if (ref instanceof ViewerRowReference) {
1549 ViewerRowReference rref = (ViewerRowReference)ref;
1550 Object element = rref.getElement();
1551 ge.update((TreeNode)element);
1553 throw new IllegalArgumentException("Ui Reference is unknkown " + ref);
1558 public Object getPropagateLock() {
1563 public Object getPropagateListLock() {
1564 return propagateList;
1568 public boolean isPropagating() {
1569 return propagating.get();
1573 public void setPropagating(boolean b) {
1574 this.propagating.set(b);
1578 public List<Runnable> getScheduleList() {
1579 return scheduleList;
1583 public void setScheduleList(List<Runnable> list) {
1584 this.scheduleList = list;
1588 public Deque<Integer> getActivity() {
1593 public void setActivityInt(int i) {
1594 this.activityInt = i;
1598 public int getActivityInt() {
1603 public void scheduleQueryUpdate(Runnable r) {
1606 if (ge.isDisposed())
1608 if (currentQueryUpdater.compareAndSet(null, r)) {
1609 ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER);
1613 Runnable QUERY_UPDATE_SCHEDULER = new Runnable() {
1616 Runnable r = currentQueryUpdater.getAndSet(null);
1624 public void dispose() {
1626 cache = new DummyCache();
1627 scheduleList.clear();
1628 autoExpanded.clear();
1629 autoExpanded = null;
1639 private static class GeViewerContentProvider implements ITreeContentProvider {
1641 public Object[] getElements(Object inputElement) {
1642 return getChildren(inputElement);
1646 public Object[] getChildren(Object element) {
1647 TreeNode node = (TreeNode)element;
1648 return node.getChildren().toArray();
1653 public Object getParent(Object element) {
1654 TreeNode node = (TreeNode)element;
1655 return node.getParent();
1659 public boolean hasChildren(Object element) {
1660 return getChildren(element).length > 0;
1664 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
1669 public void dispose() {
1674 private class GeViewerLabelProvider extends CellLabelProvider {
1675 private TreeNode node;
1677 private Labeler labeler;
1678 private Imager imager;
1679 Collection<LabelDecorator> labelDecorators;
1680 Collection<ImageDecorator> imageDecorators;
1682 Map<String, String> labels;
1683 Map<String, String> runtimeLabels;
1685 public void update(ViewerCell cell) {
1686 TreeNode node = (TreeNode)cell.getElement();
1687 NodeContext ctx = node.getContext();
1688 int columnIndex = cell.getColumnIndex();
1689 String columnKey = columns[columnIndex].getKey();
1691 // using columnIndex 0 to refresh data.
1692 // Note that this does not work if ViewerCellReferences are used. (At the moment there is no code that would use them).
1693 if (node != this.node || columnIndex == 0) {
1695 GENodeQueryManager manager = node.getManager();
1696 labeler = manager.query(ctx, BuiltinKeys.SELECTED_LABELER);
1697 imager = manager.query(ctx, BuiltinKeys.SELECTED_IMAGER);
1698 labelDecorators = manager.query(ctx, BuiltinKeys.LABEL_DECORATORS);
1699 imageDecorators = manager.query(ctx, BuiltinKeys.IMAGE_DECORATORS);
1701 if (labeler != null) {
1702 labels = labeler.getLabels();
1703 runtimeLabels = labeler.getRuntimeLabels();
1706 runtimeLabels = null;
1710 //if (DEBUG) System.out.println("GeViewerLabelProvider.update " + context + " " + columnIndex);
1712 setText(cell, columnKey);
1713 setImage(cell, columnKey);
1716 void setImage(ViewerCell cell, String columnKey) {
1717 if (imager != null) {
1718 Object descOrImage = null;
1719 boolean hasUncachedImages = false;
1721 ImageDescriptor desc = imager.getImage(columnKey);
1724 // Attempt to decorate the label
1725 if (!imageDecorators.isEmpty()) {
1726 for (ImageDecorator id : imageDecorators) {
1727 ImageDescriptor ds = id.decorateImage(desc, columnKey, index);
1733 // Try resolving only cached images here and now
1734 Object img = localResourceManager.find(desc);
1736 img = resourceManager.find(desc);
1738 descOrImage = img != null ? img : desc;
1739 hasUncachedImages |= img == null;
1742 if (!hasUncachedImages) {
1743 cell.setImage((Image) descOrImage);
1745 // Schedule loading to another thread to refrain from
1747 // the UI with database operations.
1748 queueImageTask(node, new ImageTask(node, descOrImage));
1751 cell.setImage(null);
1755 private void queueImageTask(TreeNode node, ImageTask task) {
1756 synchronized (imageTasks) {
1757 imageTasks.put(node, task);
1759 imageLoaderJob.scheduleIfNecessary(100);
1762 void setText(ViewerCell cell, String key) {
1763 if (labeler != null) {
1765 if (runtimeLabels != null)
1766 s = runtimeLabels.get(key);
1768 s = labels.get(key);
1769 //if (DEBUG) System.out.println(cell.getElement() + " " + cell.getColumnIndex() + " label:" + s + " key:" + key + " " + labels.size() + " " + (runtimeLabels == null ? "-1" : runtimeLabels.size()));
1771 FontDescriptor font = originalFont;
1772 ColorDescriptor bg = originalBackground;
1773 ColorDescriptor fg = originalForeground;
1775 // Attempt to decorate the label
1776 if (!labelDecorators.isEmpty()) {
1778 for (LabelDecorator ld : labelDecorators) {
1779 String ds = ld.decorateLabel(s, key, index);
1783 FontDescriptor dfont = ld.decorateFont(font, key, index);
1787 ColorDescriptor dbg = ld.decorateBackground(bg, key, index);
1791 ColorDescriptor dfg = ld.decorateForeground(fg, key, index);
1797 if (font != originalFont) {
1798 // System.out.println("set font: " + index + ": " +
1800 cell.setFont((Font) localResourceManager.get(font));
1802 cell.setFont((Font) (originalFont != null ? localResourceManager.get(originalFont) : null));
1804 if (bg != originalBackground)
1805 cell.setBackground((Color) localResourceManager.get(bg));
1807 cell.setBackground((Color) (originalBackground != null ? localResourceManager.get(originalBackground) : null));
1808 if (fg != originalForeground)
1809 cell.setForeground((Color) localResourceManager.get(fg));
1811 cell.setForeground((Color) (originalForeground != null ? localResourceManager.get(originalForeground) : null));
1816 cell.setText(Labeler.NO_LABEL);
1824 private class GeEditingSupport extends EditingSupport {
1825 private Object lastElement;
1827 private Modifier lastModifier;
1829 private int columnIndex;
1830 public GeEditingSupport(ColumnViewer viewer, int columnIndex) {
1832 this.columnIndex = columnIndex;
1836 protected boolean canEdit(Object element) {
1837 if (filterSelectionEdit && !selectedNodes.contains(element)) {
1838 // When item is clicked, canEdit is called before the selection is updated.
1839 // This allows filtering edit attempts when the item is selected.
1842 lastElement = null; // clear cached element + modifier.
1843 Modifier modifier = getModifier((TreeNode)element);
1844 if (modifier == null)
1850 protected CellEditor getCellEditor(Object element) {
1851 TreeNode node = (TreeNode) element;
1852 Modifier modifier = getModifier((TreeNode)element);
1853 NodeContext context = node.getContext();
1854 if (modifier instanceof DialogModifier) {
1855 return performDialogEditing(context, (DialogModifier) modifier);
1856 } else if (modifier instanceof CustomModifier) {
1857 return startCustomEditing(node, (CustomModifier) modifier);
1858 } else if (modifier instanceof EnumerationModifier) {
1859 return startEnumerationEditing((EnumerationModifier) modifier);
1861 return startTextEditing(modifier);
1867 protected Object getValue(Object element) {
1868 Modifier modifier = getModifier((TreeNode)element);
1869 return modifier.getValue();
1872 protected void setValue(Object element, Object value) {
1873 Modifier modifier = getModifier((TreeNode)element);
1874 // CustomModifiers have internal set value mechanism.
1875 if (!(modifier instanceof CustomModifier))
1876 modifier.modify((String)value);
1880 CellEditor startTextEditing( Modifier modifier) {
1881 TextCellEditor editor = new ValidatedTextEditor(viewer.getTree());//new TextCellEditor(viewer.getTree());
1882 editor.setValidator(new ModifierValidator(modifier));
1886 CellEditor startEnumerationEditing(EnumerationModifier modifier) {
1887 if (SimanticsUI.isLinuxGTK()) {
1888 // ComboBoxCellEditor2 does not work when GEImpl2 is embedded into dialog (It does work in SelectionView)
1889 // CBCE2 does not work because it receives a focus lost event when the combo/popup is opened.
1890 return new EnumModifierEditor(viewer.getTree(),modifier);
1892 return new EnumModifierEditor2(viewer.getTree(),modifier);
1896 CellEditor performDialogEditing(final NodeContext context, final DialogModifier modifier) {
1897 DialogCellEditor editor = new DialogCellEditor(viewer.getTree()) {
1900 protected Object openDialogBox(Control cellEditorWindow) {
1902 final Semaphore sem = new Semaphore(1);
1903 Consumer<String> callback = result -> {
1907 String status = modifier.query(cellEditorWindow, null, columnIndex, context, callback);
1912 } catch (InterruptedException e) {
1913 e.printStackTrace();
1920 editor.setValidator(new ModifierValidator(modifier));
1924 CellEditor startCustomEditing(TreeNode node, CustomModifier modifier) {
1925 CustomModifierEditor editor = new CustomModifierEditor(viewer.getTree(), modifier, node, columnIndex);
1929 private Modifier getModifier(TreeNode element) {
1930 if (element == lastElement)
1931 return lastModifier;
1932 lastModifier = GraphExplorerImpl2.this.getModifier(element, columnIndex);
1933 lastElement = element;
1934 return lastModifier;
1940 private Modifier getModifier(TreeNode element, int columnIndex) {
1941 GENodeQueryManager manager = element.getManager();
1942 final NodeContext context = element.getContext();
1943 Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
1944 if (labeler == null)
1946 Column column = columns[columnIndex];
1948 return labeler.getModifier(modificationContext, column.getKey());
1952 static class ImageTask {
1954 Object descsOrImage;
1955 public ImageTask(TreeNode node, Object descsOrImage) {
1957 this.descsOrImage = descsOrImage;
1962 * The job that is used for off-loading image loading tasks (see
1963 * {@link ImageTask} to a worker thread from the main UI thread.
1965 ImageLoaderJob imageLoaderJob;
1967 // Map<NodeContext, ImageTask> imageTasks = new THashMap<NodeContext, ImageTask>();
1968 Map<TreeNode, ImageTask> imageTasks = new THashMap<TreeNode, ImageTask>();
1971 * Invoked in a job worker thread.
1976 protected IStatus setPendingImages(IProgressMonitor monitor) {
1977 ImageTask[] tasks = null;
1978 synchronized (imageTasks) {
1979 tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]);
1983 MultiStatus status = null;
1985 // Load missing images
1986 for (ImageTask task : tasks) {
1987 Object desc = task.descsOrImage;
1988 if (desc instanceof ImageDescriptor) {
1990 desc = resourceManager.get((ImageDescriptor) desc);
1991 task.descsOrImage = desc;
1992 } catch (DeviceResourceException e) {
1994 status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null);
1995 status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e));
2001 // Perform final UI updates in the UI thread.
2002 final ImageTask[] _tasks = tasks;
2003 thread.asyncExec(new Runnable() {
2010 return status != null ? status : Status.OK_STATUS;
2014 void setImages(ImageTask[] tasks) {
2015 for (ImageTask task : tasks)
2020 void setImage(ImageTask task) {
2021 if (!task.node.isDisposed())
2022 update(task.node, 0);
2025 private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider {
2027 private GraphExplorerImpl2 ge;
2029 GraphExplorerPostSelectionProvider(GraphExplorerImpl2 ge) {
2038 public void setSelection(final ISelection selection) {
2039 if(ge == null) return;
2040 ge.setSelection(selection, false);
2046 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
2047 if(ge == null) return;
2048 if(ge.isDisposed()) {
2049 if (DEBUG_SELECTION_LISTENERS)
2050 System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener);
2053 ge.selectionProvider.removeSelectionChangedListener(listener);
2057 public void addPostSelectionChangedListener(ISelectionChangedListener listener) {
2058 if(ge == null) return;
2059 if (!ge.thread.currentThreadAccess())
2060 throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2061 if(ge.isDisposed()) {
2062 System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener);
2065 ge.selectionProvider.addPostSelectionChangedListener(listener);
2069 public void removePostSelectionChangedListener(ISelectionChangedListener listener) {
2070 if(ge == null) return;
2071 if(ge.isDisposed()) {
2072 if (DEBUG_SELECTION_LISTENERS)
2073 System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener);
2076 ge.selectionProvider.removePostSelectionChangedListener(listener);
2081 public void addSelectionChangedListener(ISelectionChangedListener listener) {
2082 if(ge == null) return;
2083 if (!ge.thread.currentThreadAccess())
2084 throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2085 if (ge.viewer.getTree().isDisposed() || ge.selectionProvider == null) {
2086 System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener);
2090 ge.selectionProvider.addSelectionChangedListener(listener);
2095 public ISelection getSelection() {
2096 if(ge == null) return StructuredSelection.EMPTY;
2097 if (!ge.thread.currentThreadAccess())
2098 throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread());
2099 if (ge.viewer.getTree().isDisposed() || ge.selectionProvider == null)
2100 return StructuredSelection.EMPTY;
2101 return ge.selectionProvider.getSelection();
2106 static class ModifierValidator implements ICellEditorValidator {
2107 private Modifier modifier;
2108 public ModifierValidator(Modifier modifier) {
2109 this.modifier = modifier;
2113 public String isValid(Object value) {
2114 return modifier.isValid((String)value);
2118 static class UpdateRunner implements Runnable {
2120 final GraphExplorerImpl2 ge;
2122 UpdateRunner(GraphExplorerImpl2 ge, IGraphExplorerContext geContext) {
2129 } catch (Throwable t) {
2130 t.printStackTrace();
2134 public void doRun() {
2136 if (ge.isDisposed())
2139 HashSet<UpdateItem> items;
2141 ScrollBar verticalBar = ge.viewer.getTree().getVerticalBar();
2144 synchronized (ge.pendingItems) {
2145 items = ge.pendingItems;
2146 ge.pendingItems = new HashSet<UpdateItem>();
2148 if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size());
2150 ge.viewer.getTree().setRedraw(false);
2151 for (UpdateItem item : items) {
2152 item.update(ge.viewer);
2155 // check if vertical scroll bar has become visible and refresh layout.
2156 boolean currentlyVerticalBarVisible = verticalBar.isVisible();
2157 if (ge.verticalBarVisible != currentlyVerticalBarVisible) {
2158 ge.verticalBarVisible = currentlyVerticalBarVisible;
2159 ge.viewer.getTree().getParent().layout();
2162 ge.viewer.getTree().setRedraw(true);
2164 synchronized (ge.pendingItems) {
2165 if (!ge.scheduleUpdater()) {
2166 ge.updating = false;
2171 ge.printTree(ge.rootNode, 0);
2178 private class ValidatedTextEditor extends TextCellEditor {
2181 public ValidatedTextEditor(Composite parent) {
2185 protected void editOccured(org.eclipse.swt.events.ModifyEvent e) {
2186 String value = text.getText();
2187 if (value == null) {
2188 value = "";//$NON-NLS-1$
2190 Object typedValue = value;
2191 boolean oldValidState = isValueValid();
2192 boolean newValidState = isCorrect(typedValue);
2193 if (!newValidState) {
2194 text.setBackground(invalidModificationColor);
2195 text.setToolTipText(getErrorMessage());
2197 text.setBackground(null);
2198 text.setToolTipText(null);
2200 valueChanged(oldValidState, newValidState);
2204 private class EnumModifierEditor2 extends ComboBoxCellEditor2 {
2206 List<String> values;
2207 public EnumModifierEditor2(Composite parent, EnumerationModifier modifier) {
2208 super(parent,modifier.getValues().toArray(new String[modifier.getValues().size()]),SWT.READ_ONLY);
2209 values = modifier.getValues();
2210 setValidator(new ModifierValidator(modifier));
2213 protected void doSetValue(Object value) {
2214 super.doSetValue((Integer)values.indexOf(value));
2218 protected Object doGetValue() {
2219 return values.get((Integer)super.doGetValue());
2223 private class EnumModifierEditor extends ComboBoxCellEditor {
2225 List<String> values;
2226 public EnumModifierEditor(Composite parent, EnumerationModifier modifier) {
2227 super(parent,modifier.getValues().toArray(new String[modifier.getValues().size()]),SWT.READ_ONLY);
2228 values = modifier.getValues();
2229 setValidator(new ModifierValidator(modifier));
2232 protected void doSetValue(Object value) {
2233 super.doSetValue((Integer)values.indexOf(value));
2237 protected Object doGetValue() {
2238 return values.get((Integer)super.doGetValue());
2243 private class CustomModifierEditor extends CellEditor implements ICellEditorValidator, DisposeListener {
2244 private CustomModifier modifier;
2245 private TreeNode node;
2246 private NodeContext context;
2247 private int columnIndex;
2248 private Composite control;
2249 private Control origControl;
2251 public CustomModifierEditor(Composite parent, CustomModifier modifier, TreeNode node, int columnIndex) {
2252 this.modifier = modifier;
2254 this.context = node.getContext();
2255 this.columnIndex = columnIndex;
2261 protected Control createControl(Composite parent) {
2262 control = new Composite(parent, SWT.NONE);
2263 control.setLayout(new FillLayout());
2264 origControl = (Control) modifier.createControl(control, null, columnIndex, context);
2271 protected Object doGetValue() {
2272 return modifier.getValue();
2276 protected void doSetValue(Object value) {
2277 //CustomModifier handles value setting internally.
2281 private void reCreate() {
2282 modifier = (CustomModifier)getModifier(node, columnIndex);
2283 if (control != null && !control.isDisposed()) {
2284 if (!origControl.isDisposed())
2285 origControl.dispose();
2286 origControl = (Control)modifier.createControl(control, null, columnIndex, context);
2287 origControl.addDisposeListener(this);
2290 protected void doSetFocus() {
2291 if (control != null && !control.isDisposed())
2296 public void widgetDisposed(DisposeEvent e) {
2297 if (e.widget == origControl) {
2304 public String isValid(Object value) {
2305 return modifier.isValid((String)value);
2310 private class TreeNode implements IAdaptable {
2311 private NodeContext context;
2313 private TreeNode parent;
2314 private List<TreeNode> children = new ArrayList<TreeNode>();
2315 private GENodeQueryManager manager;
2317 private TreeNode(NodeContext context) {
2318 if (context == null)
2319 throw new NullPointerException();
2320 this.context = context;
2321 contextToNodeMap.add(context, this);
2322 manager = new GENodeQueryManager(explorerContext, null, null, ViewerRowReference.create(this));
2325 public List<TreeNode> getChildren() {
2326 synchronized (children) {
2331 public TreeNode getParent() {
2335 public NodeContext getContext() {
2339 public GENodeQueryManager getManager() {
2343 public TreeNode addChild(NodeContext context) {
2344 TreeNode child = new TreeNode(context);
2345 child.parent = this;
2346 children.add(child);
2347 if (DEBUG) System.out.println("Add " + this + " -> " + child);
2351 public TreeNode addChild(int index, NodeContext context) {
2353 TreeNode child = new TreeNode(context);
2354 child.parent = this;
2355 children.add(index,child);
2356 if (DEBUG) System.out.println("Add " + this + " -> " + child + " at " + index);
2360 public TreeNode setChild(int index, NodeContext context) {
2362 TreeNode child = new TreeNode(context);
2363 child.parent = this;
2364 children.set(index,child);
2365 if (DEBUG) System.out.println("Set " + this + " -> " + child + " at " + index);
2369 public int distanceToRoot() {
2371 TreeNode n = getParent();
2380 public void dispose() {
2382 parent.children.remove(this);
2386 public void dispose2() {
2387 if (DEBUG) System.out.println("dispose " + this);
2389 for (TreeNode n : children) {
2394 contextToNodeMap.remove(context, this);
2400 private void clearCache() {
2401 if (explorerContext != null) {
2402 GECache2 cache = explorerContext.cache;
2404 if (cache != null) {
2405 cache.dispose(context);
2410 public boolean updateChildren() {
2411 if (context == null)
2412 throw new IllegalStateException("Node is disposed.");
2414 NodeContext[] childContexts = manager.query(context, BuiltinKeys.FINAL_CHILDREN);
2416 if (DEBUG) System.out.println("updateChildren " + childContexts.length + " " + this);
2419 boolean modified = false;
2420 synchronized (children) {
2422 int oldCount = children.size();
2423 BijectionMap<Integer, Integer> indexes = new BijectionMap<Integer, Integer>();
2424 Set<Integer> mapped = new HashSet<Integer>();
2425 boolean reorder = false;
2426 // locate matching pairs form old and new children
2427 for (int i = 0; i < oldCount; i++) {
2428 NodeContext oldCtx = children.get(i).context;
2429 for (int j = 0; j <childContexts.length; j++) {
2430 if (mapped.contains(j))
2432 if (oldCtx.equals(childContexts[j])) {
2441 // update children if required
2442 if (childContexts.length != oldCount || reorder || childContexts.length != indexes.size()) {
2444 List<TreeNode> oldChildren = new ArrayList<TreeNode>(oldCount);
2445 oldChildren.addAll(children);
2446 if (childContexts.length >= oldCount) {
2447 for (int i = 0; i < oldCount; i++) {
2448 Integer oldIndex = indexes.getLeft(i);
2449 if (oldIndex == null) {
2450 setChild(i, childContexts[i]);
2452 TreeNode n = oldChildren.get(oldIndex);
2457 for (int i = oldCount; i < childContexts.length; i++) {
2458 addChild(childContexts[i]);
2461 for (int i = 0; i < childContexts.length; i++) {
2462 Integer oldIndex = indexes.getLeft(i);
2463 if (oldIndex == null) {
2464 setChild(i, childContexts[i]);
2466 TreeNode n = oldChildren.get(oldIndex);
2470 for (int i = oldCount -1; i >= childContexts.length; i--) {
2474 for (int i = 0; i < oldChildren.size(); i++) {
2475 if (!indexes.containsLeft(i)) {
2476 oldChildren.get(i).dispose2();
2486 public boolean isDisposed() {
2487 return context == null;
2490 @SuppressWarnings("rawtypes")
2492 public Object getAdapter(Class adapter) {
2493 if (adapter == NodeContext.class)
2495 return context.getAdapter(adapter);
2499 // public String toString() {
2501 // if (manager != null) {
2503 // s+= super.toString() + " ";
2505 // Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
2506 // Map<String,String> labels = labeler.getLabels();
2507 // for (Entry<String, String> l : labels.entrySet()) {
2508 // s+= l.getKey() + " : " + l.getValue() + " ";
2510 // } catch (Exception e) {
2514 // s = super.toString();
2516 // if (context != null)
2517 // s += " context " + context.hashCode();
2524 private static class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor<Boolean> implements
2525 IsExpandedProcessor, ProcessorLifecycle {
2527 * The set of currently expanded node contexts.
2529 private final HashSet<NodeContext> expanded = new HashSet<NodeContext>();
2530 private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();
2534 public TreeNodeIsExpandedProcessor() {
2538 public Object getIdentifier() {
2539 return BuiltinKeys.IS_EXPANDED;
2543 public String toString() {
2544 return "IsExpandedProcessor";
2548 public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Boolean> key) {
2549 boolean isExpanded = expanded.contains(context);
2550 expandedQueries.put(context, updater);
2551 return Boolean.valueOf(isExpanded);
2555 public Collection<NodeContext> getExpanded() {
2556 return new HashSet<NodeContext>(expanded);
2560 public boolean getExpanded(NodeContext context) {
2561 return this.expanded.contains(context);
2565 public boolean setExpanded(NodeContext context, boolean expanded) {
2566 return _setExpanded(context, expanded);
2570 public boolean replaceExpanded(NodeContext context, boolean expanded) {
2571 return nodeStatusChanged(context, expanded);
2574 private boolean _setExpanded(NodeContext context, boolean expanded) {
2576 return this.expanded.add(context);
2578 return this.expanded.remove(context);
2582 Listener treeListener = new Listener() {
2584 public void handleEvent(Event event) {
2585 TreeNode node = (TreeNode) event.item.getData();
2586 NodeContext context = node.getContext();
2587 switch (event.type) {
2589 nodeStatusChanged(context, true);
2592 nodeStatusChanged(context, false);
2598 protected boolean nodeStatusChanged(NodeContext context, boolean expanded) {
2599 boolean result = _setExpanded(context, expanded);
2600 PrimitiveQueryUpdater updater = expandedQueries.get(context);
2601 if (updater != null)
2602 updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded);
2607 public void attached(GraphExplorer explorer) {
2608 Object control = explorer.getControl();
2609 if (control instanceof Tree) {
2610 this.tree = (Tree) control;
2611 tree.addListener(SWT.Expand, treeListener);
2612 tree.addListener(SWT.Collapse, treeListener);
2614 System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control);
2619 public void clear() {
2621 expandedQueries.clear();
2625 public void detached(GraphExplorer explorer) {
2628 tree.removeListener(SWT.Expand, treeListener);
2629 tree.removeListener(SWT.Collapse, treeListener);
2635 private void printTree(TreeNode node, int depth) {
2637 for (int i = 0; i < depth; i++) {
2641 System.out.println(s);
2643 for (TreeNode n : node.getChildren()) {
2650 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used)
2652 final private static class GECacheKey {
2654 private NodeContext context;
2655 private CacheKey<?> key;
2657 GECacheKey(NodeContext context, CacheKey<?> key) {
2658 this.context = context;
2660 if (context == null || key == null)
2661 throw new IllegalArgumentException("Null context or key is not accepted");
2664 GECacheKey(GECacheKey other) {
2665 this.context = other.context;
2666 this.key = other.key;
2667 if (context == null || key == null)
2668 throw new IllegalArgumentException("Null context or key is not accepted");
2671 void setValues(NodeContext context, CacheKey<?> key) {
2672 this.context = context;
2674 if (context == null || key == null)
2675 throw new IllegalArgumentException("Null context or key is not accepted");
2679 public int hashCode() {
2680 return context.hashCode() | key.hashCode();
2684 public boolean equals(Object object) {
2688 else if (object == null)
2691 GECacheKey i = (GECacheKey) object;
2693 return key.equals(i.key) && context.equals(i.context);
2700 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data.
2702 private static class GECache2 implements IGECache {
2704 final HashMap<GECacheKey, IGECacheEntry> entries = new HashMap<GECacheKey, IGECacheEntry>();
2705 final HashMap<GECacheKey, Set<UIElementReference>> treeReferences = new HashMap<GECacheKey, Set<UIElementReference>>();
2706 final HashMap<NodeContext, Set<GECacheKey>> keyRefs = new HashMap<NodeContext, Set<GECacheKey>>();
2709 * This single instance is used for all get operations from the cache. This
2710 * should work since the GE cache is meant to be single-threaded within the
2711 * current UI thread, what ever that thread is. For put operations which
2712 * store the key, this is not used.
2714 NodeContext getNC = new NodeContext() {
2715 @SuppressWarnings("rawtypes")
2717 public Object getAdapter(Class adapter) {
2722 public <T> T getConstant(ConstantKey<T> key) {
2727 public Set<ConstantKey<?>> getKeys() {
2728 return Collections.emptySet();
2731 CacheKey<?> getCK = new CacheKey<Object>() {
2733 public Object processorIdenfitier() {
2737 GECacheKey getKey = new GECacheKey(getNC, getCK);
2740 private void addKey(GECacheKey key) {
2741 Set<GECacheKey> refs = keyRefs.get(key.context);
2745 refs = new HashSet<GECacheKey>();
2747 keyRefs.put(key.context, refs);
2751 private void removeKey(GECacheKey key) {
2752 Set<GECacheKey> refs = keyRefs.get(key.context);
2758 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key, T value) {
2759 // if (DEBUG) System.out.println("Add entry " + context + " " + key);
2760 IGECacheEntry entry = new GECacheEntry(context, key, value);
2761 GECacheKey gekey = new GECacheKey(context, key);
2762 entries.put(gekey, entry);
2767 @SuppressWarnings("unchecked")
2768 public <T> T get(NodeContext context, CacheKey<T> key) {
2769 getKey.setValues(context, key);
2770 IGECacheEntry entry = entries.get(getKey);
2773 return (T) entry.getValue();
2777 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
2778 assert(context != null);
2779 assert(key != null);
2780 getKey.setValues(context, key);
2781 return entries.get(getKey);
2785 public <T> void remove(NodeContext context, CacheKey<T> key) {
2786 // if (DEBUG) System.out.println("Remove entry " + context + " " + key);
2787 getKey.setValues(context, key);
2788 entries.remove(getKey);
2793 public <T> Set<UIElementReference> getTreeReference(NodeContext context, CacheKey<T> key) {
2794 assert(context != null);
2795 assert(key != null);
2796 getKey.setValues(context, key);
2797 return treeReferences.get(getKey);
2801 public <T> void putTreeReference(NodeContext context, CacheKey<T> key, UIElementReference reference) {
2802 assert(context != null);
2803 assert(key != null);
2804 //if (DEBUG) System.out.println("Add tree reference " + context + " " + key);
2805 getKey.setValues(context, key);
2806 Set<UIElementReference> refs = treeReferences.get(getKey);
2808 refs.add(reference);
2810 refs = new HashSet<UIElementReference>(4);
2811 refs.add(reference);
2812 GECacheKey gekey = new GECacheKey(getKey);
2813 treeReferences.put(gekey, refs);
2819 public <T> Set<UIElementReference> removeTreeReference(NodeContext context, CacheKey<T> key) {
2820 assert(context != null);
2821 assert(key != null);
2822 //if (DEBUG) System.out.println("Remove tree reference " + context + " " + key);
2823 getKey.setValues(context, key);
2825 return treeReferences.remove(getKey);
2829 public boolean isShown(NodeContext context) {
2830 return references.get(context) > 0;
2833 private TObjectIntHashMap<NodeContext> references = new TObjectIntHashMap<NodeContext>();
2836 public void incRef(NodeContext context) {
2837 int exist = references.get(context);
2838 references.put(context, exist+1);
2842 public void decRef(NodeContext context) {
2843 int exist = references.get(context);
2844 references.put(context, exist-1);
2846 references.remove(context);
2850 public void dispose() {
2853 treeReferences.clear();
2857 public void dispose(NodeContext context) {
2858 Set<GECacheKey> keys = keyRefs.remove(context);
2860 for (GECacheKey key : keys) {
2861 entries.remove(key);
2862 treeReferences.remove(key);
2870 * Non-functional cache to replace actual cache when GEContext is disposed.
2875 private static class DummyCache extends GECache2 {
2878 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
2883 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key,
2889 public <T> void putTreeReference(NodeContext context, CacheKey<T> key,
2890 UIElementReference reference) {
2894 public <T> T get(NodeContext context, CacheKey<T> key) {
2899 public <T> Set<UIElementReference> getTreeReference(
2900 NodeContext context, CacheKey<T> key) {
2905 public <T> void remove(NodeContext context, CacheKey<T> key) {
2910 public <T> Set<UIElementReference> removeTreeReference(
2911 NodeContext context, CacheKey<T> key) {
2916 public boolean isShown(NodeContext context) {
2921 public void incRef(NodeContext context) {
2926 public void decRef(NodeContext context) {
2931 public void dispose() {
2937 public Object getClicked(Object event) {
2938 MouseEvent e = (MouseEvent)event;
2939 final Tree tree = (Tree) e.getSource();
2940 Point point = new Point(e.x, e.y);
2941 TreeItem item = tree.getItem(point);
2943 // No selectable item at point?
2947 Object data = item.getData();