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.ColumnKeys;
127 import org.simantics.browsing.ui.common.ErrorLogger;
128 import org.simantics.browsing.ui.common.NodeContextBuilder;
129 import org.simantics.browsing.ui.common.NodeContextUtil;
130 import org.simantics.browsing.ui.common.internal.GENodeQueryManager;
131 import org.simantics.browsing.ui.common.internal.IGECache;
132 import org.simantics.browsing.ui.common.internal.IGraphExplorerContext;
133 import org.simantics.browsing.ui.common.internal.UIElementReference;
134 import org.simantics.browsing.ui.common.processors.AbstractPrimitiveQueryProcessor;
135 import org.simantics.browsing.ui.common.processors.DefaultCheckedStateProcessor;
136 import org.simantics.browsing.ui.common.processors.DefaultComparableChildrenProcessor;
137 import org.simantics.browsing.ui.common.processors.DefaultFinalChildrenProcessor;
138 import org.simantics.browsing.ui.common.processors.DefaultImageDecoratorProcessor;
139 import org.simantics.browsing.ui.common.processors.DefaultImagerFactoriesProcessor;
140 import org.simantics.browsing.ui.common.processors.DefaultImagerProcessor;
141 import org.simantics.browsing.ui.common.processors.DefaultLabelDecoratorProcessor;
142 import org.simantics.browsing.ui.common.processors.DefaultLabelerFactoriesProcessor;
143 import org.simantics.browsing.ui.common.processors.DefaultLabelerProcessor;
144 import org.simantics.browsing.ui.common.processors.DefaultPrunedChildrenProcessor;
145 import org.simantics.browsing.ui.common.processors.DefaultSelectedImageDecoratorFactoriesProcessor;
146 import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelDecoratorFactoriesProcessor;
147 import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelerProcessor;
148 import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointFactoryProcessor;
149 import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointProcessor;
150 import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionProcessor;
151 import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionsProcessor;
152 import org.simantics.browsing.ui.common.processors.DefaultViewpointProcessor;
153 import org.simantics.browsing.ui.common.processors.IsExpandedProcessor;
154 import org.simantics.browsing.ui.common.processors.NoSelectionRequestProcessor;
155 import org.simantics.browsing.ui.common.processors.ProcessorLifecycle;
156 import org.simantics.browsing.ui.content.ImageDecorator;
157 import org.simantics.browsing.ui.content.Imager;
158 import org.simantics.browsing.ui.content.LabelDecorator;
159 import org.simantics.browsing.ui.content.Labeler;
160 import org.simantics.browsing.ui.content.Labeler.CustomModifier;
161 import org.simantics.browsing.ui.content.Labeler.DialogModifier;
162 import org.simantics.browsing.ui.content.Labeler.EnumerationModifier;
163 import org.simantics.browsing.ui.content.Labeler.Modifier;
164 import org.simantics.browsing.ui.swt.internal.Threads;
165 import org.simantics.db.layer0.SelectionHints;
166 import org.simantics.ui.SimanticsUI;
167 import org.simantics.utils.datastructures.BijectionMap;
168 import org.simantics.utils.datastructures.MapList;
169 import org.simantics.utils.datastructures.disposable.AbstractDisposable;
170 import org.simantics.utils.datastructures.hints.IHintContext;
171 import org.simantics.utils.threads.IThreadWorkQueue;
172 import org.simantics.utils.threads.SWTThread;
173 import org.simantics.utils.threads.ThreadUtils;
174 import org.simantics.utils.ui.AdaptionUtils;
175 import org.simantics.utils.ui.ISelectionUtils;
176 import org.simantics.utils.ui.jface.BasePostSelectionProvider;
178 import gnu.trove.map.hash.THashMap;
179 import gnu.trove.map.hash.TObjectIntHashMap;
182 * TreeView based GraphExplorer
185 * @author Marko Luukkainen <marko.luukkainen@vtt.fi>
187 public class GraphExplorerImpl2 extends GraphExplorerImplBase implements GraphExplorer {
189 private static final boolean DEBUG_SELECTION_LISTENERS = false;
190 private static final boolean DEBUG = false;
192 private TreeViewer viewer;
194 private LocalResourceManager localResourceManager;
195 private DeviceResourceManager resourceManager;
198 private IThreadWorkQueue thread;
200 @SuppressWarnings({ "rawtypes" })
201 final HashMap<CacheKey<?>, NodeQueryProcessor> processors = new HashMap<CacheKey<?>, NodeQueryProcessor>();
202 @SuppressWarnings({ "rawtypes" })
203 final HashMap<Object, PrimitiveQueryProcessor> primitiveProcessors = new HashMap<Object, PrimitiveQueryProcessor>();
204 @SuppressWarnings({ "rawtypes" })
205 final HashMap<Class, DataSource> dataSources = new HashMap<Class, DataSource>();
207 private FontDescriptor originalFont;
208 protected ColorDescriptor originalForeground;
209 protected ColorDescriptor originalBackground;
210 private Color invalidModificationColor;
212 private Column[] columns;
213 private Map<String,Integer> columnKeyToIndex;
214 private boolean columnsAreVisible = true;
217 private NodeContext rootContext;
218 private TreeNode rootNode;
219 private StatePersistor persistor = null;
221 private boolean editable = true;
223 private boolean disposed = false;
225 private final CopyOnWriteArrayList<FocusListener> focusListeners = new CopyOnWriteArrayList<FocusListener>();
226 private final CopyOnWriteArrayList<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();
227 private final CopyOnWriteArrayList<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>();
229 private int autoExpandLevel = 0;
230 private IServiceLocator serviceLocator;
231 private IContextService contextService = null;
232 private IFocusService focusService = null;
233 private IContextActivation editingContext = null;
235 GeViewerContext explorerContext = new GeViewerContext(this);
237 private GraphExplorerPostSelectionProvider postSelectionProvider = new GraphExplorerPostSelectionProvider(this);
238 private BasePostSelectionProvider selectionProvider = new BasePostSelectionProvider();
239 private SelectionDataResolver selectionDataResolver;
240 private SelectionFilter selectionFilter;
242 private Set<TreeNode> collapsedNodes = new HashSet<TreeNode>();
243 private MapList<NodeContext, TreeNode> contextToNodeMap = new MapList<NodeContext, TreeNode>();
245 private ModificationContext modificationContext = null;
247 private boolean filterSelectionEdit = true;
249 private TreeColumnLayout treeColumnLayout;
251 private boolean expand;
252 private boolean verticalBarVisible = false;
254 private BiFunction<GraphExplorer, Object[], Object[]> selectionTransformation = new BiFunction<GraphExplorer, Object[], Object[]>() {
257 public Object[] apply(GraphExplorer explorer, Object[] objects) {
258 Object[] result = new Object[objects.length];
259 for (int i = 0; i < objects.length; i++) {
260 IHintContext context = new AdaptableHintContext(SelectionHints.KEY_MAIN);
261 context.setHint(SelectionHints.KEY_MAIN, objects[i]);
269 static class TransientStateImpl implements TransientExplorerState {
271 private Integer activeColumn = null;
274 public synchronized Integer getActiveColumn() {
278 public synchronized void setActiveColumn(Integer column) {
279 activeColumn = column;
284 private TransientStateImpl transientState = new TransientStateImpl();
287 public GraphExplorerImpl2(Composite parent) {
288 this(parent, SWT.BORDER | SWT.MULTI );
291 public GraphExplorerImpl2(Composite parent, int style) {
292 this.localResourceManager = new LocalResourceManager(JFaceResources.getResources());
293 this.resourceManager = new DeviceResourceManager(parent.getDisplay());
295 this.imageLoaderJob = new ImageLoaderJob(this);
296 this.imageLoaderJob.setPriority(Job.DECORATE);
298 invalidModificationColor = (Color) localResourceManager.get(ColorDescriptor.createFrom(new RGB(255, 128, 128)));
300 this.thread = SWTThread.getThreadAccess(parent);
302 for (int i = 0; i < 10; i++)
303 explorerContext.activity.push(0);
305 boolean useLayout = true;
306 // FIXME: hack, GraphExplorerComposite uses its own TreeColumnLayout.
307 if (useLayout && !(parent.getLayout() instanceof TreeColumnLayout)) {
309 Composite rootTreeComposite = new Composite(parent, SWT.NONE);
310 treeColumnLayout = new TreeColumnLayout();
311 rootTreeComposite.setLayout(treeColumnLayout);
313 viewer = new TreeViewer(rootTreeComposite,style|SWT.H_SCROLL|SWT.V_SCROLL);
315 GridDataFactory.fillDefaults().grab(true, true).span(3,1).applyTo(rootTreeComposite);
318 viewer = new TreeViewer(parent,style | SWT.H_SCROLL | SWT.V_SCROLL);
321 viewer.getColumnViewerEditor().addEditorActivationListener(new ColumnViewerEditorActivationListener() {
324 public void beforeEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {
329 public void beforeEditorActivated(ColumnViewerEditorActivationEvent event) {
330 // cancel editor activation for double click events.
331 // TODO: this may not work similarly to GraphExplorerImpl
332 if ((event.time - focusGainedAt) < 250L) {
338 public void afterEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {
343 public void afterEditorActivated(ColumnViewerEditorActivationEvent event) {
348 viewer.setUseHashlookup(true);
349 viewer.setContentProvider(new GeViewerContentProvider());
353 originalFont = JFaceResources.getDefaultFontDescriptor();
355 viewer.getTree().setFont((Font) localResourceManager.get(originalFont));
358 setDefaultProcessors();
360 viewer.getTree().addDisposeListener(new DisposeListener() {
363 public void widgetDisposed(DisposeEvent e) {
370 // Add listener to tree for delayed tree population.
372 Listener listener = new Listener() {
375 public void handleEvent(Event event) {
377 switch (event.type) {
393 viewer.getTree().addListener(SWT.Activate, listener);
394 viewer.getTree().addListener(SWT.Deactivate, listener);
395 viewer.getTree().addListener(SWT.Show, listener);
396 viewer.getTree().addListener(SWT.Hide, listener);
397 viewer.getTree().addListener(SWT.Paint,listener);
400 viewer.addTreeListener(new ITreeViewerListener() {
403 public void treeExpanded(TreeExpansionEvent event) {
408 public void treeCollapsed(TreeExpansionEvent event) {
409 collapsedNodes.add((TreeNode)event.getElement());
413 setColumns( new Column[] { new Column(ColumnKeys.SINGLE) });
416 private long focusGainedAt = 0L;
417 private boolean visible = false;
419 private Collection<TreeNode> selectedNodes = new ArrayList<TreeNode>();
421 protected void setBasicListeners() {
422 Tree tree = viewer.getTree();
424 tree.addFocusListener(new FocusListener() {
426 public void focusGained(FocusEvent e) {
427 focusGainedAt = ((long) e.time) & 0xFFFFFFFFL;
428 for (FocusListener listener : focusListeners)
429 listener.focusGained(e);
432 public void focusLost(FocusEvent e) {
433 for (FocusListener listener : focusListeners)
434 listener.focusLost(e);
437 tree.addMouseListener(new MouseListener() {
439 public void mouseDoubleClick(MouseEvent e) {
440 for (MouseListener listener : mouseListeners) {
441 listener.mouseDoubleClick(e);
445 public void mouseDown(MouseEvent e) {
446 for (MouseListener listener : mouseListeners) {
447 listener.mouseDown(e);
451 public void mouseUp(MouseEvent e) {
452 for (MouseListener listener : mouseListeners) {
457 tree.addKeyListener(new KeyListener() {
459 public void keyPressed(KeyEvent e) {
460 for (KeyListener listener : keyListeners) {
461 listener.keyPressed(e);
465 public void keyReleased(KeyEvent e) {
466 for (KeyListener listener : keyListeners) {
467 listener.keyReleased(e);
472 viewer.addSelectionChangedListener(new ISelectionChangedListener() {
475 public void selectionChanged(SelectionChangedEvent event) {
476 //System.out.println("GraphExplorerImpl2.fireSelection");
477 selectedNodes = AdaptionUtils.adaptToCollection(event.getSelection(), TreeNode.class);
478 Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);
479 selectionProvider.setAndFireSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));
483 viewer.addPostSelectionChangedListener(new ISelectionChangedListener() {
486 public void selectionChanged(SelectionChangedEvent event) {
487 //System.out.println("GraphExplorerImpl2.firePostSelection");
488 Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);
489 selectionProvider.firePostSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));
496 private NodeContext pendingRoot;
498 private void activate() {
499 if (pendingRoot != null && !expand) {
500 doSetRoot(pendingRoot);
506 * Invoke only from SWT thread to reset the root of the graph explorer tree.
510 private void doSetRoot(NodeContext root) {
511 Display display = viewer.getTree().getDisplay();
512 if (display.getThread() != Thread.currentThread()) {
513 throw new RuntimeException("Invoke from SWT thread only");
515 // System.out.println("doSetRoot " + root);
518 if (viewer.getTree().isDisposed())
520 if (root.getConstant(BuiltinKeys.INPUT) == null) {
521 ErrorLogger.defaultLogError("root node context does not contain BuiltinKeys.INPUT key. Node = " + root, new Exception("trace"));
527 // Empty caches, release queries.
528 if (rootNode != null) {
531 GeViewerContext oldContext = explorerContext;
532 GeViewerContext newContext = new GeViewerContext(this);
533 this.explorerContext = newContext;
534 oldContext.safeDispose();
536 // Need to empty these or otherwise they won't be emptied until the
537 // explorer is disposed which would mean that many unwanted references
538 // will be held by this map.
539 clearPrimitiveProcessors();
541 this.rootContext = root.getConstant(BuiltinKeys.IS_ROOT) != null ? root
542 : NodeContextUtil.withConstant(root, BuiltinKeys.IS_ROOT, Boolean.TRUE);
544 explorerContext.getCache().incRef(this.rootContext);
550 //refreshColumnSizes();
551 rootNode = new TreeNode(rootContext);
552 if (DEBUG) System.out.println("setRoot " + rootNode);
554 viewer.setInput(rootNode);
556 // Delay content reading.
558 // This is required for cases when GEImpl2 is attached to selection view. Reading content
559 // instantly could stagnate SWT thread under rapid changes in selection. By delaying the
560 // content reading we give the system a change to dispose the GEImpl2 before the content is read.
561 display.asyncExec(new Runnable() {
565 if (rootNode != null) {
566 rootNode.updateChildren();
573 private void initializeState() {
574 if (persistor == null)
577 ExplorerState state = persistor.deserialize(
578 Platform.getStateLocation(Activator.getDefault().getBundle()).toFile(),
582 Object processor = getPrimitiveProcessor(BuiltinKeys.IS_EXPANDED);
583 if (processor instanceof DefaultIsExpandedProcessor) {
584 DefaultIsExpandedProcessor isExpandedProcessor = (DefaultIsExpandedProcessor)processor;
585 for(NodeContext expanded : state.expandedNodes) {
586 isExpandedProcessor.setExpanded(expanded, true);
592 public NodeContext getRoot() {
597 public IThreadWorkQueue getThread() {
602 public NodeContext getParentContext(NodeContext context) {
604 throw new IllegalStateException("disposed");
605 if (!thread.currentThreadAccess())
606 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
608 List<TreeNode> nodes = contextToNodeMap.getValuesUnsafe(context);
609 for (int i = 0; i < nodes.size(); i++) {
610 if (nodes.get(i).getParent() != null)
611 return nodes.get(i).getParent().getContext();
618 @SuppressWarnings("unchecked")
620 public <T> T getAdapter(Class<T> adapter) {
621 if(ISelectionProvider.class == adapter) return (T) postSelectionProvider;
622 else if(IPostSelectionProvider.class == adapter) return (T) postSelectionProvider;
627 protected void setDefaultProcessors() {
628 // Add a simple IMAGER query processor that always returns null.
629 // With this processor no images will ever be shown.
630 // setPrimitiveProcessor(new StaticImagerProcessor(null));
632 setProcessor(new DefaultComparableChildrenProcessor());
633 setProcessor(new DefaultLabelDecoratorsProcessor());
634 setProcessor(new DefaultImageDecoratorsProcessor());
635 setProcessor(new DefaultSelectedLabelerProcessor());
636 setProcessor(new DefaultLabelerFactoriesProcessor());
637 setProcessor(new DefaultSelectedImagerProcessor());
638 setProcessor(new DefaultImagerFactoriesProcessor());
639 setPrimitiveProcessor(new DefaultLabelerProcessor());
640 setPrimitiveProcessor(new DefaultCheckedStateProcessor());
641 setPrimitiveProcessor(new DefaultImagerProcessor());
642 setPrimitiveProcessor(new DefaultLabelDecoratorProcessor());
643 setPrimitiveProcessor(new DefaultImageDecoratorProcessor());
644 setPrimitiveProcessor(new NoSelectionRequestProcessor());
646 setProcessor(new DefaultFinalChildrenProcessor(this));
648 setProcessor(new DefaultPrunedChildrenProcessor());
649 setProcessor(new DefaultSelectedViewpointProcessor());
650 setProcessor(new DefaultSelectedLabelDecoratorFactoriesProcessor());
651 setProcessor(new DefaultSelectedImageDecoratorFactoriesProcessor());
652 setProcessor(new DefaultViewpointContributionsProcessor());
654 setPrimitiveProcessor(new DefaultViewpointProcessor());
655 setPrimitiveProcessor(new DefaultViewpointContributionProcessor());
656 setPrimitiveProcessor(new DefaultSelectedViewpointFactoryProcessor());
657 setPrimitiveProcessor(new TreeNodeIsExpandedProcessor());
658 setPrimitiveProcessor(new DefaultShowMaxChildrenProcessor());
662 public Column[] getColumns() {
663 return Arrays.copyOf(columns, columns.length);
667 public void setColumnsVisible(boolean visible) {
668 columnsAreVisible = visible;
669 if(viewer.getTree() != null) viewer.getTree().setHeaderVisible(columnsAreVisible);
673 public void setColumns(final Column[] columns) {
674 setColumns(columns, null);
678 public void setColumns(final Column[] columns, Consumer<Map<Column, Object>> callback) {
680 checkUniqueColumnKeys(columns);
682 Display d = viewer.getTree().getDisplay();
683 if (d.getThread() == Thread.currentThread()) {
684 doSetColumns(columns, callback);
685 viewer.refresh(true);
687 d.asyncExec(new Runnable() {
692 if (viewer.getTree().isDisposed())
694 doSetColumns(columns, callback);
695 viewer.refresh(true);
696 viewer.getTree().getParent().layout();
701 private void checkUniqueColumnKeys(Column[] cols) {
702 Set<String> usedColumnKeys = new HashSet<String>();
703 List<Column> duplicateColumns = new ArrayList<Column>();
704 for (Column c : cols) {
705 if (!usedColumnKeys.add(c.getKey()))
706 duplicateColumns.add(c);
708 if (!duplicateColumns.isEmpty()) {
709 throw new IllegalArgumentException("All columns do not have unique keys: " + cols + ", overlapping: " + duplicateColumns);
713 private List<TreeViewerColumn> treeViewerColumns = new ArrayList<TreeViewerColumn>();
714 private CellLabelProvider cellLabelProvider = new GeViewerLabelProvider();
715 private List<EditingSupport> editingSupports = new ArrayList<EditingSupport>();
717 private void doSetColumns(Column[] cols, Consumer<Map<Column, Object>> callback) {
718 // Attempt to keep previous column widths.
719 Map<String, Integer> prevWidths = new HashMap<String, Integer>();
721 for (TreeViewerColumn c : treeViewerColumns) {
722 prevWidths.put(c.getColumn().getText(), c.getColumn().getWidth());
723 c.getColumn().dispose();
726 treeViewerColumns.clear();
728 HashMap<String, Integer> keyToIndex = new HashMap<String, Integer>();
729 for (int i = 0; i < cols.length; ++i) {
730 keyToIndex.put(cols[i].getKey(), i);
733 this.columns = Arrays.copyOf(cols, cols.length);
734 //this.columns[cols.length] = FILLER_COLUMN;
735 this.columnKeyToIndex = keyToIndex;
737 Map<Column, Object> map = new HashMap<Column, Object>();
739 // FIXME : temporary workaround for ModelBrowser.
740 viewer.getTree().setHeaderVisible(columns.length == 1 ? false : columnsAreVisible);
744 for (Column column : columns) {
745 TreeViewerColumn tvc = new TreeViewerColumn(viewer, toSWT(column.getAlignment()));
746 treeViewerColumns.add(tvc);
747 tvc.setLabelProvider(cellLabelProvider);
748 EditingSupport support = null;
749 if (editingSupports.size() > columnIndex)
750 support = editingSupports.get(columnIndex);
752 support = new GeEditingSupport(viewer, columnIndex);
753 editingSupports.add(support);
756 tvc.setEditingSupport(support);
758 TreeColumn c = tvc.getColumn();
761 c.setText(column.getLabel());
762 c.setToolTipText(column.getTooltip());
764 int cw = column.getWidth();
766 // Try to keep previous widths
767 Integer w = prevWidths.get(column);
770 else if (cw != Column.DEFAULT_CONTROL_WIDTH)
772 else if (columns.length == 1) {
773 // FIXME : how to handle single column properly?
777 // Go for some kind of default settings then...
778 if (ColumnKeys.PROPERTY.equals(column.getKey()))
783 if (treeColumnLayout != null) {
784 treeColumnLayout.setColumnData(c, new ColumnWeightData(column.getWeight(), true));
787 // if (!column.hasGrab() && !FILLER.equals(column.getKey())) {
788 // c.addListener(SWT.Resize, resizeListener);
789 // c.setResizable(true);
791 // //c.setResizable(false);
798 if(callback != null) callback.accept(map);
801 int toSWT(Align alignment) {
803 case LEFT: return SWT.LEFT;
804 case CENTER: return SWT.CENTER;
805 case RIGHT: return SWT.RIGHT;
806 default: throw new Error("unhandled alignment: " + alignment);
811 public <T> void setProcessor(NodeQueryProcessor<T> processor) {
813 if (processor == null)
814 throw new IllegalArgumentException("null processor");
816 processors.put(processor.getIdentifier(), processor);
820 public <T> void setPrimitiveProcessor(PrimitiveQueryProcessor<T> processor) {
822 if (processor == null)
823 throw new IllegalArgumentException("null processor");
825 PrimitiveQueryProcessor<?> oldProcessor = primitiveProcessors.put(
826 processor.getIdentifier(), processor);
828 if (oldProcessor instanceof ProcessorLifecycle)
829 ((ProcessorLifecycle) oldProcessor).detached(this);
830 if (processor instanceof ProcessorLifecycle)
831 ((ProcessorLifecycle) processor).attached(this);
835 public <T> void setDataSource(DataSource<T> provider) {
837 if (provider == null)
838 throw new IllegalArgumentException("null provider");
839 dataSources.put(provider.getProvidedClass(), provider);
842 @SuppressWarnings("unchecked")
844 public <T> DataSource<T> removeDataSource(Class<T> forProvidedClass) {
846 if (forProvidedClass == null)
847 throw new IllegalArgumentException("null class");
848 return dataSources.remove(forProvidedClass);
852 public void setPersistor(StatePersistor persistor) {
853 this.persistor = persistor;
857 public SelectionDataResolver getSelectionDataResolver() {
858 return selectionDataResolver;
862 public void setSelectionDataResolver(SelectionDataResolver r) {
863 this.selectionDataResolver = r;
867 public SelectionFilter getSelectionFilter() {
868 return selectionFilter;
872 public void setSelectionFilter(SelectionFilter f) {
873 this.selectionFilter = f;
874 // TODO: re-filter current selection?
877 protected ISelection constructSelection(NodeContext... contexts) {
878 if (contexts == null)
879 throw new IllegalArgumentException("null contexts");
880 if (contexts.length == 0)
881 return StructuredSelection.EMPTY;
882 if (selectionFilter == null)
883 return new StructuredSelection(transformSelection(contexts));
884 return new StructuredSelection( transformSelection(filter(selectionFilter, contexts)) );
887 protected Object[] transformSelection(Object[] objects) {
888 return selectionTransformation.apply(this, objects);
891 protected static Object[] filter(SelectionFilter filter, NodeContext[] contexts) {
892 int len = contexts.length;
893 Object[] objects = new Object[len];
894 for (int i = 0; i < len; ++i)
895 objects[i] = filter.filter(contexts[i]);
900 public void setSelectionTransformation(
901 BiFunction<GraphExplorer, Object[], Object[]> f) {
902 this.selectionTransformation = f;
905 public ISelection getWidgetSelection() {
906 return viewer.getSelection();
910 public <T> void addListener(T listener) {
911 if (listener instanceof FocusListener) {
912 focusListeners.add((FocusListener) listener);
913 } else if (listener instanceof MouseListener) {
914 mouseListeners.add((MouseListener) listener);
915 } else if (listener instanceof KeyListener) {
916 keyListeners.add((KeyListener) listener);
921 public <T> void removeListener(T listener) {
922 if (listener instanceof FocusListener) {
923 focusListeners.remove(listener);
924 } else if (listener instanceof MouseListener) {
925 mouseListeners.remove(listener);
926 } else if (listener instanceof KeyListener) {
927 keyListeners.remove(listener);
931 public void addSelectionListener(SelectionListener listener) {
932 viewer.getTree().addSelectionListener(listener);
935 public void removeSelectionListener(SelectionListener listener) {
936 viewer.getTree().removeSelectionListener(listener);
939 private Set<String> uiContexts;
942 public void setUIContexts(Set<String> contexts) {
943 this.uiContexts = contexts;
947 public void setRoot(final Object root) {
948 if(uiContexts != null && uiContexts.size() == 1)
949 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root, BuiltinKeys.UI_CONTEXT, uiContexts.iterator().next()));
951 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root));
955 public void setRootContext(final NodeContext context) {
956 setRootContext0(context);
959 private void setRoot(NodeContext context) {
961 pendingRoot = context;
962 Display.getDefault().asyncExec(new Runnable() {
965 if (viewer != null && !viewer.getTree().isDisposed())
966 viewer.getTree().redraw();
974 private void setRootContext0(final NodeContext context) {
975 Assert.isNotNull(context, "root must not be null");
976 if (isDisposed() || viewer.getTree().isDisposed())
978 Display display = viewer.getTree().getDisplay();
979 if (display.getThread() == Thread.currentThread()) {
982 display.asyncExec(new Runnable() {
992 public void setFocus() {
993 viewer.getTree().setFocus();
996 @SuppressWarnings("unchecked")
998 public <T> T getControl() {
999 return (T)viewer.getTree();
1004 public boolean isDisposed() {
1008 protected void assertNotDisposed() {
1010 throw new IllegalStateException("disposed");
1014 public boolean isEditable() {
1019 public void setEditable(boolean editable) {
1020 if (!thread.currentThreadAccess())
1021 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
1023 this.editable = editable;
1024 Display display = viewer.getTree().getDisplay();
1025 viewer.getTree().setBackground(editable ? null : display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
1028 private void doDispose() {
1032 // TODO: Since GENodeQueryManager is cached in QueryChache and it refers to this class
1033 // we have to remove all references here to reduce memory consumption.
1035 // Proper fix would be to remove references between QueryCache and GENodeQueryManagers.
1036 explorerContext.dispose();
1037 explorerContext = null;
1039 detachPrimitiveProcessors();
1040 primitiveProcessors.clear();
1041 dataSources.clear();
1042 pendingItems.clear();
1044 mouseListeners.clear();
1045 selectionProvider.clearListeners();
1046 selectionProvider = null;
1047 selectionDataResolver = null;
1048 selectedNodes.clear();
1049 selectedNodes = null;
1050 selectionTransformation = null;
1051 originalFont = null;
1052 localResourceManager.dispose();
1053 localResourceManager = null;
1054 // Must shutdown image loader job before disposing its ResourceManager
1055 imageLoaderJob.dispose();
1056 imageLoaderJob.cancel();
1058 imageLoaderJob.join();
1059 imageLoaderJob = null;
1060 } catch (InterruptedException e) {
1061 ErrorLogger.defaultLogError(e);
1063 resourceManager.dispose();
1064 resourceManager = null;
1065 collapsedNodes.clear();
1066 collapsedNodes = null;
1067 if (rootNode != null) {
1071 contextToNodeMap.clear(); // should be empty at this point.
1072 contextToNodeMap = null;
1073 if (postSelectionProvider != null) {
1074 postSelectionProvider.dispose();
1075 postSelectionProvider = null;
1078 modificationContext = null;
1079 focusService = null;
1080 contextService = null;
1081 serviceLocator = null;
1083 columnKeyToIndex.clear();
1084 columnKeyToIndex = null;
1090 public boolean select(NodeContext context) {
1092 assertNotDisposed();
1094 if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) {
1095 viewer.setSelection(new StructuredSelection());
1096 selectionProvider.setAndFireNonEqualSelection(TreeSelection.EMPTY);
1100 viewer.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0)));
1107 public boolean selectPath(Collection<NodeContext> contexts) {
1109 if(contexts == null) throw new IllegalArgumentException("Null list is not allowed");
1110 if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed");
1112 return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0);
1116 private boolean selectPathInternal(NodeContext[] contexts, int position) {
1118 NodeContext head = contexts[position];
1120 if(position == contexts.length-1) {
1121 return select(head);
1125 setExpanded(head, true);
1126 if(!waitVisible(contexts[position+1])) return false;
1128 return selectPathInternal(contexts, position+1);
1132 private boolean waitVisible(NodeContext context) {
1133 long start = System.nanoTime();
1134 while(!isVisible(context)) {
1135 Display.getCurrent().readAndDispatch();
1136 long duration = System.nanoTime() - start;
1137 if(duration > 10e9) return false;
1143 public boolean isVisible(NodeContext context) {
1144 if (contextToNodeMap.getValuesUnsafe(context).size() == 0)
1147 Object elements[] = viewer.getVisibleExpandedElements();
1148 return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0));
1154 public TransientExplorerState getTransientState() {
1155 if (!thread.currentThreadAccess())
1156 throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread());
1157 return transientState;
1161 public <T> T query(NodeContext context, CacheKey<T> key) {
1162 return this.explorerContext.cache.get(context, key);
1166 * For setting a more local service locator for the explorer than the global
1167 * workbench service locator. Sometimes required to give this implementation
1168 * access to local workbench services like IFocusService.
1171 * Must be invoked during right after construction.
1173 * @param serviceLocator
1174 * a specific service locator or <code>null</code> to use the
1175 * workbench global service locator
1177 public void setServiceLocator(IServiceLocator serviceLocator) {
1178 if (serviceLocator == null && PlatformUI.isWorkbenchRunning())
1179 serviceLocator = PlatformUI.getWorkbench();
1180 this.serviceLocator = serviceLocator;
1181 if (serviceLocator != null) {
1182 this.contextService = (IContextService) serviceLocator.getService(IContextService.class);
1183 this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class);
1187 private void detachPrimitiveProcessors() {
1188 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
1189 if (p instanceof ProcessorLifecycle) {
1190 ((ProcessorLifecycle) p).detached(this);
1195 private void clearPrimitiveProcessors() {
1196 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
1197 if (p instanceof ProcessorLifecycle) {
1198 ((ProcessorLifecycle) p).clear();
1204 public void setExpanded(NodeContext context, boolean expanded) {
1205 viewer.setExpandedState(context, expanded);
1210 public void setAutoExpandLevel(int level) {
1211 this.autoExpandLevel = level;
1212 viewer.setAutoExpandLevel(level);
1215 int maxChildren = GraphExplorerImpl.DEFAULT_MAX_CHILDREN;
1218 public int getMaxChildren() {
1223 public void setMaxChildren(int maxChildren) {
1224 this.maxChildren = maxChildren;
1229 public int getMaxChildren(NodeQueryManager manager, NodeContext context) {
1230 Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN);
1231 //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result);
1232 if (result != null) {
1234 throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result);
1241 public <T> NodeQueryProcessor<T> getProcessor(QueryKey<T> key) {
1242 return explorerContext.getProcessor(key);
1246 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(PrimitiveQueryKey<T> key) {
1247 return explorerContext.getPrimitiveProcessor(key);
1250 private HashSet<UpdateItem> pendingItems = new HashSet<UpdateItem>();
1251 private boolean updating = false;
1252 private int updateCounter = 0;
1253 final ScheduledExecutorService uiUpdateScheduler = ThreadUtils.getNonBlockingWorkExecutor();
1255 private class UpdateItem {
1259 public UpdateItem(TreeNode element) {
1263 public UpdateItem(TreeNode element, int columnIndex) {
1264 this.element = element;
1265 this.columnIndex = columnIndex;
1266 if (element != null && element.isDisposed()) {
1267 throw new IllegalArgumentException("Node is disposed. " + element);
1271 public void update(TreeViewer viewer) {
1272 if (element != null) {
1274 if (element.isDisposed()) {
1277 if (((TreeNode)element).updateChildren()) {
1278 viewer.refresh(element,true);
1280 if (columnIndex >= 0) {
1281 viewer.update(element, new String[]{columns[columnIndex].getKey()});
1283 viewer.refresh(element,true);
1287 if (!element.isDisposed() && autoExpandLevel > 1 && !collapsedNodes.contains(element) && ((TreeNode)element).distanceToRoot() <= autoExpandLevel) {
1289 viewer.setExpandedState(element, true);
1293 if (rootNode.updateChildren())
1294 viewer.refresh(rootNode,true);
1299 public boolean equals(Object obj) {
1302 if (obj.getClass() != getClass())
1304 UpdateItem other = (UpdateItem)obj;
1305 if (columnIndex != other.columnIndex)
1307 if (element != null)
1308 return element.equals(other.element);
1309 return other.element == null;
1313 public int hashCode() {
1314 if (element != null)
1315 return element.hashCode() + columnIndex;
1320 private void update(final TreeNode element, final int columnIndex) {
1321 if (DEBUG)System.out.println("update " + element + " " + columnIndex);
1322 if (viewer.getTree().isDisposed())
1324 synchronized (pendingItems) {
1325 pendingItems.add(new UpdateItem(element, columnIndex));
1326 if (updating) return;
1332 private void update(final TreeNode element) {
1333 if (DEBUG)System.out.println("update " + element);
1334 if (viewer.getTree().isDisposed())
1336 if (element != null && element.isDisposed())
1338 synchronized (pendingItems) {
1340 pendingItems.add(new UpdateItem(element));
1341 if (updating) return;
1347 boolean scheduleUpdater() {
1349 if (viewer.getTree().isDisposed())
1352 if (!pendingItems.isEmpty()) {
1354 int activity = explorerContext.activityInt;
1356 if (activity < 100) {
1357 //System.out.println("Scheduling update immediately.");
1358 } else if (activity < 1000) {
1359 //System.out.println("Scheduling update after 500ms.");
1362 //System.out.println("Scheduling update after 3000ms.");
1368 //System.out.println("Scheduling UI update after " + delay + " ms.");
1369 uiUpdateScheduler.schedule(new Runnable() {
1373 if (viewer == null || viewer.getTree().isDisposed())
1376 if (updateCounter > 0) {
1378 uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS);
1380 viewer.getTree().getDisplay().asyncExec(new UpdateRunner(GraphExplorerImpl2.this, GraphExplorerImpl2.this.explorerContext));
1384 }, delay, TimeUnit.MILLISECONDS);
1394 public String startEditing(NodeContext context, String columnKey) {
1395 assertNotDisposed();
1396 if (!thread.currentThreadAccess())
1397 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
1399 if(columnKey.startsWith("#")) {
1400 columnKey = columnKey.substring(1);
1403 Integer columnIndex = columnKeyToIndex.get(columnKey);
1404 if (columnIndex == null)
1405 return "Rename not supported for selection";
1407 viewer.editElement(context, columnIndex);
1408 if(viewer.isCellEditorActive()) return null;
1409 return "Rename not supported for selection";
1413 public String startEditing(String columnKey) {
1414 ISelection selection = postSelectionProvider.getSelection();
1415 if(selection == null) return "Rename not supported for selection";
1416 NodeContext context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class);
1417 if(context == null) return "Rename not supported for selection";
1419 return startEditing(context, columnKey);
1423 public void setSelection(final ISelection selection, boolean forceControlUpdate) {
1424 assertNotDisposed();
1425 boolean equalsOld = selectionProvider.selectionEquals(selection);
1426 if (equalsOld && !forceControlUpdate) {
1427 // Just set the selection object instance, fire no events nor update
1428 // the viewer selection.
1429 selectionProvider.setSelection(selection);
1431 Collection<NodeContext> coll = AdaptionUtils.adaptToCollection(selection, NodeContext.class);
1432 Collection<TreeNode> nodes = new ArrayList<TreeNode>();
1433 for (NodeContext c : coll) {
1434 List<TreeNode> match = contextToNodeMap.getValuesUnsafe(c);
1435 if(match.size() > 0)
1436 nodes.add(match.get(0));
1438 final ISelection sel = new StructuredSelection(nodes.toArray());
1439 if (coll.size() == 0)
1441 // Schedule viewer and selection update if necessary.
1442 if (viewer.getTree().isDisposed())
1444 Display d = viewer.getTree().getDisplay();
1445 if (d.getThread() == Thread.currentThread()) {
1446 viewer.setSelection(sel);
1448 d.asyncExec(new Runnable() {
1451 if (viewer.getTree().isDisposed())
1453 viewer.setSelection(sel);
1461 public void setModificationContext(ModificationContext modificationContext) {
1462 this.modificationContext = modificationContext;
1466 final ExecutorService queryUpdateScheduler = Threads.getExecutor();
1468 private static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {
1469 // This is for query debugging only.
1471 private GraphExplorerImpl2 ge;
1472 int queryIndent = 0;
1474 GECache2 cache = new GECache2();
1475 AtomicBoolean propagating = new AtomicBoolean(false);
1476 Object propagateList = new Object();
1477 Object propagate = new Object();
1478 List<Runnable> scheduleList = new ArrayList<Runnable>();
1479 final Deque<Integer> activity = new LinkedList<Integer>();
1480 int activityInt = 0;
1482 AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();
1485 * Keeps track of nodes that have already been auto-expanded. After
1486 * being inserted into this set, nodes will not be forced to stay in an
1487 * expanded state after that. This makes it possible for the user to
1488 * close auto-expanded nodes.
1490 Map<NodeContext, Boolean> autoExpanded = new WeakHashMap<NodeContext, Boolean>();
1492 public GeViewerContext(GraphExplorerImpl2 ge) {
1497 protected void doDispose() {
1499 autoExpanded.clear();
1503 public IGECache getCache() {
1508 public int queryIndent() {
1513 public int queryIndent(int offset) {
1514 queryIndent += offset;
1519 @SuppressWarnings("unchecked")
1520 public <T> NodeQueryProcessor<T> getProcessor(Object o) {
1523 return ge.processors.get(o);
1527 @SuppressWarnings("unchecked")
1528 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {
1529 return ge.primitiveProcessors.get(o);
1532 @SuppressWarnings("unchecked")
1534 public <T> DataSource<T> getDataSource(Class<T> clazz) {
1535 return ge.dataSources.get(clazz);
1539 public void update(UIElementReference ref) {
1540 if (ref instanceof ViewerCellReference) {
1541 ViewerCellReference tiref = (ViewerCellReference) ref;
1542 Object element = tiref.getElement();
1543 int columnIndex = tiref.getColumn();
1544 // NOTE: must be called regardless of the the item value.
1545 // A null item is currently used to indicate a tree root update.
1546 ge.update((TreeNode)element,columnIndex);
1547 } else if (ref instanceof ViewerRowReference) {
1548 ViewerRowReference rref = (ViewerRowReference)ref;
1549 Object element = rref.getElement();
1550 ge.update((TreeNode)element);
1552 throw new IllegalArgumentException("Ui Reference is unknkown " + ref);
1557 public Object getPropagateLock() {
1562 public Object getPropagateListLock() {
1563 return propagateList;
1567 public boolean isPropagating() {
1568 return propagating.get();
1572 public void setPropagating(boolean b) {
1573 this.propagating.set(b);
1577 public List<Runnable> getScheduleList() {
1578 return scheduleList;
1582 public void setScheduleList(List<Runnable> list) {
1583 this.scheduleList = list;
1587 public Deque<Integer> getActivity() {
1592 public void setActivityInt(int i) {
1593 this.activityInt = i;
1597 public int getActivityInt() {
1602 public void scheduleQueryUpdate(Runnable r) {
1605 if (ge.isDisposed())
1607 if (currentQueryUpdater.compareAndSet(null, r)) {
1608 ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER);
1612 Runnable QUERY_UPDATE_SCHEDULER = new Runnable() {
1615 Runnable r = currentQueryUpdater.getAndSet(null);
1623 public void dispose() {
1625 cache = new DummyCache();
1626 scheduleList.clear();
1627 autoExpanded.clear();
1628 autoExpanded = null;
1638 private static class GeViewerContentProvider implements ITreeContentProvider {
1640 public Object[] getElements(Object inputElement) {
1641 return getChildren(inputElement);
1645 public Object[] getChildren(Object element) {
1646 TreeNode node = (TreeNode)element;
1647 return node.getChildren().toArray();
1652 public Object getParent(Object element) {
1653 TreeNode node = (TreeNode)element;
1654 return node.getParent();
1658 public boolean hasChildren(Object element) {
1659 return getChildren(element).length > 0;
1663 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
1668 public void dispose() {
1673 private class GeViewerLabelProvider extends CellLabelProvider {
1674 private TreeNode node;
1676 private Labeler labeler;
1677 private Imager imager;
1678 Collection<LabelDecorator> labelDecorators;
1679 Collection<ImageDecorator> imageDecorators;
1681 Map<String, String> labels;
1682 Map<String, String> runtimeLabels;
1684 public void update(ViewerCell cell) {
1685 TreeNode node = (TreeNode)cell.getElement();
1686 NodeContext ctx = node.getContext();
1687 int columnIndex = cell.getColumnIndex();
1688 String columnKey = columns[columnIndex].getKey();
1690 // using columnIndex 0 to refresh data.
1691 // Note that this does not work if ViewerCellReferences are used. (At the moment there is no code that would use them).
1692 if (node != this.node || columnIndex == 0) {
1694 GENodeQueryManager manager = node.getManager();
1695 labeler = manager.query(ctx, BuiltinKeys.SELECTED_LABELER);
1696 imager = manager.query(ctx, BuiltinKeys.SELECTED_IMAGER);
1697 labelDecorators = manager.query(ctx, BuiltinKeys.LABEL_DECORATORS);
1698 imageDecorators = manager.query(ctx, BuiltinKeys.IMAGE_DECORATORS);
1700 if (labeler != null) {
1701 labels = labeler.getLabels();
1702 runtimeLabels = labeler.getRuntimeLabels();
1705 runtimeLabels = null;
1709 //if (DEBUG) System.out.println("GeViewerLabelProvider.update " + context + " " + columnIndex);
1711 setText(cell, columnKey);
1712 setImage(cell, columnKey);
1715 void setImage(ViewerCell cell, String columnKey) {
1716 if (imager != null) {
1717 Object descOrImage = null;
1718 boolean hasUncachedImages = false;
1720 ImageDescriptor desc = imager.getImage(columnKey);
1723 // Attempt to decorate the label
1724 if (!imageDecorators.isEmpty()) {
1725 for (ImageDecorator id : imageDecorators) {
1726 ImageDescriptor ds = id.decorateImage(desc, columnKey, index);
1732 // Try resolving only cached images here and now
1733 Object img = localResourceManager.find(desc);
1735 img = resourceManager.find(desc);
1737 descOrImage = img != null ? img : desc;
1738 hasUncachedImages |= img == null;
1741 if (!hasUncachedImages) {
1742 cell.setImage((Image) descOrImage);
1744 // Schedule loading to another thread to refrain from
1746 // the UI with database operations.
1747 queueImageTask(node, new ImageTask(node, descOrImage));
1750 cell.setImage(null);
1754 private void queueImageTask(TreeNode node, ImageTask task) {
1755 synchronized (imageTasks) {
1756 imageTasks.put(node, task);
1758 imageLoaderJob.scheduleIfNecessary(100);
1761 void setText(ViewerCell cell, String key) {
1762 if (labeler != null) {
1764 if (runtimeLabels != null)
1765 s = runtimeLabels.get(key);
1767 s = labels.get(key);
1768 //if (DEBUG) System.out.println(cell.getElement() + " " + cell.getColumnIndex() + " label:" + s + " key:" + key + " " + labels.size() + " " + (runtimeLabels == null ? "-1" : runtimeLabels.size()));
1770 FontDescriptor font = originalFont;
1771 ColorDescriptor bg = originalBackground;
1772 ColorDescriptor fg = originalForeground;
1774 // Attempt to decorate the label
1775 if (!labelDecorators.isEmpty()) {
1777 for (LabelDecorator ld : labelDecorators) {
1778 String ds = ld.decorateLabel(s, key, index);
1782 FontDescriptor dfont = ld.decorateFont(font, key, index);
1786 ColorDescriptor dbg = ld.decorateBackground(bg, key, index);
1790 ColorDescriptor dfg = ld.decorateForeground(fg, key, index);
1796 if (font != originalFont) {
1797 // System.out.println("set font: " + index + ": " +
1799 cell.setFont((Font) localResourceManager.get(font));
1801 cell.setFont((Font) (originalFont != null ? localResourceManager.get(originalFont) : null));
1803 if (bg != originalBackground)
1804 cell.setBackground((Color) localResourceManager.get(bg));
1806 cell.setBackground((Color) (originalBackground != null ? localResourceManager.get(originalBackground) : null));
1807 if (fg != originalForeground)
1808 cell.setForeground((Color) localResourceManager.get(fg));
1810 cell.setForeground((Color) (originalForeground != null ? localResourceManager.get(originalForeground) : null));
1815 cell.setText(Labeler.NO_LABEL);
1823 private class GeEditingSupport extends EditingSupport {
1824 private Object lastElement;
1826 private Modifier lastModifier;
1828 private int columnIndex;
1829 public GeEditingSupport(ColumnViewer viewer, int columnIndex) {
1831 this.columnIndex = columnIndex;
1835 protected boolean canEdit(Object element) {
1836 if (filterSelectionEdit && !selectedNodes.contains(element)) {
1837 // When item is clicked, canEdit is called before the selection is updated.
1838 // This allows filtering edit attempts when the item is selected.
1841 lastElement = null; // clear cached element + modifier.
1842 Modifier modifier = getModifier((TreeNode)element);
1843 if (modifier == null)
1849 protected CellEditor getCellEditor(Object element) {
1850 TreeNode node = (TreeNode) element;
1851 Modifier modifier = getModifier((TreeNode)element);
1852 NodeContext context = node.getContext();
1853 if (modifier instanceof DialogModifier) {
1854 return performDialogEditing(context, (DialogModifier) modifier);
1855 } else if (modifier instanceof CustomModifier) {
1856 return startCustomEditing(node, (CustomModifier) modifier);
1857 } else if (modifier instanceof EnumerationModifier) {
1858 return startEnumerationEditing((EnumerationModifier) modifier);
1860 return startTextEditing(modifier);
1866 protected Object getValue(Object element) {
1867 Modifier modifier = getModifier((TreeNode)element);
1868 return modifier.getValue();
1871 protected void setValue(Object element, Object value) {
1872 Modifier modifier = getModifier((TreeNode)element);
1873 // CustomModifiers have internal set value mechanism.
1874 if (!(modifier instanceof CustomModifier))
1875 modifier.modify((String)value);
1879 CellEditor startTextEditing( Modifier modifier) {
1880 TextCellEditor editor = new ValidatedTextEditor(viewer.getTree());//new TextCellEditor(viewer.getTree());
1881 editor.setValidator(new ModifierValidator(modifier));
1885 CellEditor startEnumerationEditing(EnumerationModifier modifier) {
1886 if (SimanticsUI.isLinuxGTK()) {
1887 // ComboBoxCellEditor2 does not work when GEImpl2 is embedded into dialog (It does work in SelectionView)
1888 // CBCE2 does not work because it receives a focus lost event when the combo/popup is opened.
1889 return new EnumModifierEditor(viewer.getTree(),modifier);
1891 return new EnumModifierEditor2(viewer.getTree(),modifier);
1895 CellEditor performDialogEditing(final NodeContext context, final DialogModifier modifier) {
1896 DialogCellEditor editor = new DialogCellEditor(viewer.getTree()) {
1899 protected Object openDialogBox(Control cellEditorWindow) {
1901 final Semaphore sem = new Semaphore(1);
1902 Consumer<String> callback = result -> {
1906 String status = modifier.query(cellEditorWindow, null, columnIndex, context, callback);
1911 } catch (InterruptedException e) {
1912 e.printStackTrace();
1919 editor.setValidator(new ModifierValidator(modifier));
1923 CellEditor startCustomEditing(TreeNode node, CustomModifier modifier) {
1924 CustomModifierEditor editor = new CustomModifierEditor(viewer.getTree(), modifier, node, columnIndex);
1928 private Modifier getModifier(TreeNode element) {
1929 if (element == lastElement)
1930 return lastModifier;
1931 lastModifier = GraphExplorerImpl2.this.getModifier(element, columnIndex);
1932 lastElement = element;
1933 return lastModifier;
1939 private Modifier getModifier(TreeNode element, int columnIndex) {
1940 GENodeQueryManager manager = element.getManager();
1941 final NodeContext context = element.getContext();
1942 Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
1943 if (labeler == null)
1945 Column column = columns[columnIndex];
1947 return labeler.getModifier(modificationContext, column.getKey());
1951 static class ImageTask {
1953 Object descsOrImage;
1954 public ImageTask(TreeNode node, Object descsOrImage) {
1956 this.descsOrImage = descsOrImage;
1961 * The job that is used for off-loading image loading tasks (see
1962 * {@link ImageTask} to a worker thread from the main UI thread.
1964 ImageLoaderJob imageLoaderJob;
1966 // Map<NodeContext, ImageTask> imageTasks = new THashMap<NodeContext, ImageTask>();
1967 Map<TreeNode, ImageTask> imageTasks = new THashMap<TreeNode, ImageTask>();
1970 * Invoked in a job worker thread.
1975 protected IStatus setPendingImages(IProgressMonitor monitor) {
1976 ImageTask[] tasks = null;
1977 synchronized (imageTasks) {
1978 tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]);
1982 MultiStatus status = null;
1984 // Load missing images
1985 for (ImageTask task : tasks) {
1986 Object desc = task.descsOrImage;
1987 if (desc instanceof ImageDescriptor) {
1989 desc = resourceManager.get((ImageDescriptor) desc);
1990 task.descsOrImage = desc;
1991 } catch (DeviceResourceException e) {
1993 status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null);
1994 status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e));
2000 // Perform final UI updates in the UI thread.
2001 final ImageTask[] _tasks = tasks;
2002 thread.asyncExec(new Runnable() {
2009 return status != null ? status : Status.OK_STATUS;
2013 void setImages(ImageTask[] tasks) {
2014 for (ImageTask task : tasks)
2019 void setImage(ImageTask task) {
2020 if (!task.node.isDisposed())
2021 update(task.node, 0);
2024 private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider {
2026 private GraphExplorerImpl2 ge;
2028 GraphExplorerPostSelectionProvider(GraphExplorerImpl2 ge) {
2037 public void setSelection(final ISelection selection) {
2038 if(ge == null) return;
2039 ge.setSelection(selection, false);
2045 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
2046 if(ge == null) return;
2047 if(ge.isDisposed()) {
2048 if (DEBUG_SELECTION_LISTENERS)
2049 System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener);
2052 ge.selectionProvider.removeSelectionChangedListener(listener);
2056 public void addPostSelectionChangedListener(ISelectionChangedListener listener) {
2057 if(ge == null) return;
2058 if (!ge.thread.currentThreadAccess())
2059 throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2060 if(ge.isDisposed()) {
2061 System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener);
2064 ge.selectionProvider.addPostSelectionChangedListener(listener);
2068 public void removePostSelectionChangedListener(ISelectionChangedListener listener) {
2069 if(ge == null) return;
2070 if(ge.isDisposed()) {
2071 if (DEBUG_SELECTION_LISTENERS)
2072 System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener);
2075 ge.selectionProvider.removePostSelectionChangedListener(listener);
2080 public void addSelectionChangedListener(ISelectionChangedListener listener) {
2081 if(ge == null) return;
2082 if (!ge.thread.currentThreadAccess())
2083 throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2084 if (ge.viewer.getTree().isDisposed() || ge.selectionProvider == null) {
2085 System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener);
2089 ge.selectionProvider.addSelectionChangedListener(listener);
2094 public ISelection getSelection() {
2095 if(ge == null) return StructuredSelection.EMPTY;
2096 if (!ge.thread.currentThreadAccess())
2097 throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread());
2098 if (ge.viewer.getTree().isDisposed() || ge.selectionProvider == null)
2099 return StructuredSelection.EMPTY;
2100 return ge.selectionProvider.getSelection();
2105 static class ModifierValidator implements ICellEditorValidator {
2106 private Modifier modifier;
2107 public ModifierValidator(Modifier modifier) {
2108 this.modifier = modifier;
2112 public String isValid(Object value) {
2113 return modifier.isValid((String)value);
2117 static class UpdateRunner implements Runnable {
2119 final GraphExplorerImpl2 ge;
2121 UpdateRunner(GraphExplorerImpl2 ge, IGraphExplorerContext geContext) {
2128 } catch (Throwable t) {
2129 t.printStackTrace();
2133 public void doRun() {
2135 if (ge.isDisposed())
2138 HashSet<UpdateItem> items;
2140 ScrollBar verticalBar = ge.viewer.getTree().getVerticalBar();
2143 synchronized (ge.pendingItems) {
2144 items = ge.pendingItems;
2145 ge.pendingItems = new HashSet<UpdateItem>();
2147 if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size());
2149 ge.viewer.getTree().setRedraw(false);
2150 for (UpdateItem item : items) {
2151 item.update(ge.viewer);
2154 // check if vertical scroll bar has become visible and refresh layout.
2155 boolean currentlyVerticalBarVisible = verticalBar.isVisible();
2156 if (ge.verticalBarVisible != currentlyVerticalBarVisible) {
2157 ge.verticalBarVisible = currentlyVerticalBarVisible;
2158 ge.viewer.getTree().getParent().layout();
2161 ge.viewer.getTree().setRedraw(true);
2163 synchronized (ge.pendingItems) {
2164 if (!ge.scheduleUpdater()) {
2165 ge.updating = false;
2170 ge.printTree(ge.rootNode, 0);
2177 private class ValidatedTextEditor extends TextCellEditor {
2180 public ValidatedTextEditor(Composite parent) {
2184 protected void editOccured(org.eclipse.swt.events.ModifyEvent e) {
2185 String value = text.getText();
2186 if (value == null) {
2187 value = "";//$NON-NLS-1$
2189 Object typedValue = value;
2190 boolean oldValidState = isValueValid();
2191 boolean newValidState = isCorrect(typedValue);
2192 if (!newValidState) {
2193 text.setBackground(invalidModificationColor);
2194 text.setToolTipText(getErrorMessage());
2196 text.setBackground(null);
2197 text.setToolTipText(null);
2199 valueChanged(oldValidState, newValidState);
2203 private class EnumModifierEditor2 extends ComboBoxCellEditor2 {
2205 List<String> values;
2206 public EnumModifierEditor2(Composite parent, EnumerationModifier modifier) {
2207 super(parent,modifier.getValues().toArray(new String[modifier.getValues().size()]),SWT.READ_ONLY);
2208 values = modifier.getValues();
2209 setValidator(new ModifierValidator(modifier));
2212 protected void doSetValue(Object value) {
2213 super.doSetValue((Integer)values.indexOf(value));
2217 protected Object doGetValue() {
2218 return values.get((Integer)super.doGetValue());
2222 private class EnumModifierEditor extends ComboBoxCellEditor {
2224 List<String> values;
2225 public EnumModifierEditor(Composite parent, EnumerationModifier modifier) {
2226 super(parent,modifier.getValues().toArray(new String[modifier.getValues().size()]),SWT.READ_ONLY);
2227 values = modifier.getValues();
2228 setValidator(new ModifierValidator(modifier));
2231 protected void doSetValue(Object value) {
2232 super.doSetValue((Integer)values.indexOf(value));
2236 protected Object doGetValue() {
2237 return values.get((Integer)super.doGetValue());
2242 private class CustomModifierEditor extends CellEditor implements ICellEditorValidator, DisposeListener {
2243 private CustomModifier modifier;
2244 private TreeNode node;
2245 private NodeContext context;
2246 private int columnIndex;
2247 private Composite control;
2248 private Control origControl;
2250 public CustomModifierEditor(Composite parent, CustomModifier modifier, TreeNode node, int columnIndex) {
2251 this.modifier = modifier;
2253 this.context = node.getContext();
2254 this.columnIndex = columnIndex;
2260 protected Control createControl(Composite parent) {
2261 control = new Composite(parent, SWT.NONE);
2262 control.setLayout(new FillLayout());
2263 origControl = (Control) modifier.createControl(control, null, columnIndex, context);
2270 protected Object doGetValue() {
2271 return modifier.getValue();
2275 protected void doSetValue(Object value) {
2276 //CustomModifier handles value setting internally.
2280 private void reCreate() {
2281 modifier = (CustomModifier)getModifier(node, columnIndex);
2282 if (control != null && !control.isDisposed()) {
2283 if (!origControl.isDisposed())
2284 origControl.dispose();
2285 origControl = (Control)modifier.createControl(control, null, columnIndex, context);
2286 origControl.addDisposeListener(this);
2289 protected void doSetFocus() {
2290 if (control != null && !control.isDisposed())
2295 public void widgetDisposed(DisposeEvent e) {
2296 if (e.widget == origControl) {
2303 public String isValid(Object value) {
2304 return modifier.isValid((String)value);
2309 private class TreeNode implements IAdaptable {
2310 private NodeContext context;
2312 private TreeNode parent;
2313 private List<TreeNode> children = new ArrayList<TreeNode>();
2314 private GENodeQueryManager manager;
2316 private TreeNode(NodeContext context) {
2317 if (context == null)
2318 throw new NullPointerException();
2319 this.context = context;
2320 contextToNodeMap.add(context, this);
2321 manager = new GENodeQueryManager(explorerContext, null, null, ViewerRowReference.create(this));
2324 public List<TreeNode> getChildren() {
2325 synchronized (children) {
2330 public TreeNode getParent() {
2334 public NodeContext getContext() {
2338 public GENodeQueryManager getManager() {
2342 public TreeNode addChild(NodeContext context) {
2343 TreeNode child = new TreeNode(context);
2344 child.parent = this;
2345 children.add(child);
2346 if (DEBUG) System.out.println("Add " + this + " -> " + child);
2350 public TreeNode addChild(int index, NodeContext context) {
2352 TreeNode child = new TreeNode(context);
2353 child.parent = this;
2354 children.add(index,child);
2355 if (DEBUG) System.out.println("Add " + this + " -> " + child + " at " + index);
2359 public TreeNode setChild(int index, NodeContext context) {
2361 TreeNode child = new TreeNode(context);
2362 child.parent = this;
2363 children.set(index,child);
2364 if (DEBUG) System.out.println("Set " + this + " -> " + child + " at " + index);
2368 public int distanceToRoot() {
2370 TreeNode n = getParent();
2379 public void dispose() {
2381 parent.children.remove(this);
2385 public void dispose2() {
2386 if (DEBUG) System.out.println("dispose " + this);
2388 for (TreeNode n : children) {
2393 contextToNodeMap.remove(context, this);
2399 private void clearCache() {
2400 if (explorerContext != null) {
2401 GECache2 cache = explorerContext.cache;
2403 if (cache != null) {
2404 cache.dispose(context);
2409 public boolean updateChildren() {
2410 if (context == null)
2411 throw new IllegalStateException("Node is disposed.");
2413 NodeContext[] childContexts = manager.query(context, BuiltinKeys.FINAL_CHILDREN);
2415 if (DEBUG) System.out.println("updateChildren " + childContexts.length + " " + this);
2418 boolean modified = false;
2419 synchronized (children) {
2421 int oldCount = children.size();
2422 BijectionMap<Integer, Integer> indexes = new BijectionMap<Integer, Integer>();
2423 Set<Integer> mapped = new HashSet<Integer>();
2424 boolean reorder = false;
2425 // locate matching pairs form old and new children
2426 for (int i = 0; i < oldCount; i++) {
2427 NodeContext oldCtx = children.get(i).context;
2428 for (int j = 0; j <childContexts.length; j++) {
2429 if (mapped.contains(j))
2431 if (oldCtx.equals(childContexts[j])) {
2440 // update children if required
2441 if (childContexts.length != oldCount || reorder || childContexts.length != indexes.size()) {
2443 List<TreeNode> oldChildren = new ArrayList<TreeNode>(oldCount);
2444 oldChildren.addAll(children);
2445 if (childContexts.length >= oldCount) {
2446 for (int i = 0; i < oldCount; i++) {
2447 Integer oldIndex = indexes.getLeft(i);
2448 if (oldIndex == null) {
2449 setChild(i, childContexts[i]);
2451 TreeNode n = oldChildren.get(oldIndex);
2456 for (int i = oldCount; i < childContexts.length; i++) {
2457 addChild(childContexts[i]);
2460 for (int i = 0; i < childContexts.length; i++) {
2461 Integer oldIndex = indexes.getLeft(i);
2462 if (oldIndex == null) {
2463 setChild(i, childContexts[i]);
2465 TreeNode n = oldChildren.get(oldIndex);
2469 for (int i = oldCount -1; i >= childContexts.length; i--) {
2473 for (int i = 0; i < oldChildren.size(); i++) {
2474 if (!indexes.containsLeft(i)) {
2475 oldChildren.get(i).dispose2();
2485 public boolean isDisposed() {
2486 return context == null;
2489 @SuppressWarnings("rawtypes")
2491 public Object getAdapter(Class adapter) {
2492 if (adapter == NodeContext.class)
2494 return context.getAdapter(adapter);
2498 // public String toString() {
2500 // if (manager != null) {
2502 // s+= super.toString() + " ";
2504 // Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
2505 // Map<String,String> labels = labeler.getLabels();
2506 // for (Entry<String, String> l : labels.entrySet()) {
2507 // s+= l.getKey() + " : " + l.getValue() + " ";
2509 // } catch (Exception e) {
2513 // s = super.toString();
2515 // if (context != null)
2516 // s += " context " + context.hashCode();
2523 private static class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor<Boolean> implements
2524 IsExpandedProcessor, ProcessorLifecycle {
2526 * The set of currently expanded node contexts.
2528 private final HashSet<NodeContext> expanded = new HashSet<NodeContext>();
2529 private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();
2533 public TreeNodeIsExpandedProcessor() {
2537 public Object getIdentifier() {
2538 return BuiltinKeys.IS_EXPANDED;
2542 public String toString() {
2543 return "IsExpandedProcessor";
2547 public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Boolean> key) {
2548 boolean isExpanded = expanded.contains(context);
2549 expandedQueries.put(context, updater);
2550 return Boolean.valueOf(isExpanded);
2554 public Collection<NodeContext> getExpanded() {
2555 return new HashSet<NodeContext>(expanded);
2559 public boolean getExpanded(NodeContext context) {
2560 return this.expanded.contains(context);
2564 public boolean setExpanded(NodeContext context, boolean expanded) {
2565 return _setExpanded(context, expanded);
2569 public boolean replaceExpanded(NodeContext context, boolean expanded) {
2570 return nodeStatusChanged(context, expanded);
2573 private boolean _setExpanded(NodeContext context, boolean expanded) {
2575 return this.expanded.add(context);
2577 return this.expanded.remove(context);
2581 Listener treeListener = new Listener() {
2583 public void handleEvent(Event event) {
2584 TreeNode node = (TreeNode) event.item.getData();
2585 NodeContext context = node.getContext();
2586 switch (event.type) {
2588 nodeStatusChanged(context, true);
2591 nodeStatusChanged(context, false);
2597 protected boolean nodeStatusChanged(NodeContext context, boolean expanded) {
2598 boolean result = _setExpanded(context, expanded);
2599 PrimitiveQueryUpdater updater = expandedQueries.get(context);
2600 if (updater != null)
2601 updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded);
2606 public void attached(GraphExplorer explorer) {
2607 Object control = explorer.getControl();
2608 if (control instanceof Tree) {
2609 this.tree = (Tree) control;
2610 tree.addListener(SWT.Expand, treeListener);
2611 tree.addListener(SWT.Collapse, treeListener);
2613 System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control);
2618 public void clear() {
2620 expandedQueries.clear();
2624 public void detached(GraphExplorer explorer) {
2627 tree.removeListener(SWT.Expand, treeListener);
2628 tree.removeListener(SWT.Collapse, treeListener);
2634 private void printTree(TreeNode node, int depth) {
2636 for (int i = 0; i < depth; i++) {
2640 System.out.println(s);
2642 for (TreeNode n : node.getChildren()) {
2649 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used)
2651 final private static class GECacheKey {
2653 private NodeContext context;
2654 private CacheKey<?> key;
2656 GECacheKey(NodeContext context, CacheKey<?> key) {
2657 this.context = context;
2659 if (context == null || key == null)
2660 throw new IllegalArgumentException("Null context or key is not accepted");
2663 GECacheKey(GECacheKey other) {
2664 this.context = other.context;
2665 this.key = other.key;
2666 if (context == null || key == null)
2667 throw new IllegalArgumentException("Null context or key is not accepted");
2670 void setValues(NodeContext context, CacheKey<?> key) {
2671 this.context = context;
2673 if (context == null || key == null)
2674 throw new IllegalArgumentException("Null context or key is not accepted");
2678 public int hashCode() {
2679 return context.hashCode() | key.hashCode();
2683 public boolean equals(Object object) {
2687 else if (object == null)
2690 GECacheKey i = (GECacheKey) object;
2692 return key.equals(i.key) && context.equals(i.context);
2699 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data.
2701 private static class GECache2 implements IGECache {
2703 final HashMap<GECacheKey, IGECacheEntry> entries = new HashMap<GECacheKey, IGECacheEntry>();
2704 final HashMap<GECacheKey, Set<UIElementReference>> treeReferences = new HashMap<GECacheKey, Set<UIElementReference>>();
2705 final HashMap<NodeContext, Set<GECacheKey>> keyRefs = new HashMap<NodeContext, Set<GECacheKey>>();
2708 * This single instance is used for all get operations from the cache. This
2709 * should work since the GE cache is meant to be single-threaded within the
2710 * current UI thread, what ever that thread is. For put operations which
2711 * store the key, this is not used.
2713 NodeContext getNC = new NodeContext() {
2714 @SuppressWarnings("rawtypes")
2716 public Object getAdapter(Class adapter) {
2721 public <T> T getConstant(ConstantKey<T> key) {
2726 public Set<ConstantKey<?>> getKeys() {
2727 return Collections.emptySet();
2730 CacheKey<?> getCK = new CacheKey<Object>() {
2732 public Object processorIdenfitier() {
2736 GECacheKey getKey = new GECacheKey(getNC, getCK);
2739 private void addKey(GECacheKey key) {
2740 Set<GECacheKey> refs = keyRefs.get(key.context);
2744 refs = new HashSet<GECacheKey>();
2746 keyRefs.put(key.context, refs);
2750 private void removeKey(GECacheKey key) {
2751 Set<GECacheKey> refs = keyRefs.get(key.context);
2757 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key, T value) {
2758 // if (DEBUG) System.out.println("Add entry " + context + " " + key);
2759 IGECacheEntry entry = new GECacheEntry(context, key, value);
2760 GECacheKey gekey = new GECacheKey(context, key);
2761 entries.put(gekey, entry);
2766 @SuppressWarnings("unchecked")
2767 public <T> T get(NodeContext context, CacheKey<T> key) {
2768 getKey.setValues(context, key);
2769 IGECacheEntry entry = entries.get(getKey);
2772 return (T) entry.getValue();
2776 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
2777 assert(context != null);
2778 assert(key != null);
2779 getKey.setValues(context, key);
2780 return entries.get(getKey);
2784 public <T> void remove(NodeContext context, CacheKey<T> key) {
2785 // if (DEBUG) System.out.println("Remove entry " + context + " " + key);
2786 getKey.setValues(context, key);
2787 entries.remove(getKey);
2792 public <T> Set<UIElementReference> getTreeReference(NodeContext context, CacheKey<T> key) {
2793 assert(context != null);
2794 assert(key != null);
2795 getKey.setValues(context, key);
2796 return treeReferences.get(getKey);
2800 public <T> void putTreeReference(NodeContext context, CacheKey<T> key, UIElementReference reference) {
2801 assert(context != null);
2802 assert(key != null);
2803 //if (DEBUG) System.out.println("Add tree reference " + context + " " + key);
2804 getKey.setValues(context, key);
2805 Set<UIElementReference> refs = treeReferences.get(getKey);
2807 refs.add(reference);
2809 refs = new HashSet<UIElementReference>(4);
2810 refs.add(reference);
2811 GECacheKey gekey = new GECacheKey(getKey);
2812 treeReferences.put(gekey, refs);
2818 public <T> Set<UIElementReference> removeTreeReference(NodeContext context, CacheKey<T> key) {
2819 assert(context != null);
2820 assert(key != null);
2821 //if (DEBUG) System.out.println("Remove tree reference " + context + " " + key);
2822 getKey.setValues(context, key);
2824 return treeReferences.remove(getKey);
2828 public boolean isShown(NodeContext context) {
2829 return references.get(context) > 0;
2832 private TObjectIntHashMap<NodeContext> references = new TObjectIntHashMap<NodeContext>();
2835 public void incRef(NodeContext context) {
2836 int exist = references.get(context);
2837 references.put(context, exist+1);
2841 public void decRef(NodeContext context) {
2842 int exist = references.get(context);
2843 references.put(context, exist-1);
2845 references.remove(context);
2849 public void dispose() {
2852 treeReferences.clear();
2856 public void dispose(NodeContext context) {
2857 Set<GECacheKey> keys = keyRefs.remove(context);
2859 for (GECacheKey key : keys) {
2860 entries.remove(key);
2861 treeReferences.remove(key);
2869 * Non-functional cache to replace actual cache when GEContext is disposed.
2874 private static class DummyCache extends GECache2 {
2877 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
2882 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key,
2888 public <T> void putTreeReference(NodeContext context, CacheKey<T> key,
2889 UIElementReference reference) {
2893 public <T> T get(NodeContext context, CacheKey<T> key) {
2898 public <T> Set<UIElementReference> getTreeReference(
2899 NodeContext context, CacheKey<T> key) {
2904 public <T> void remove(NodeContext context, CacheKey<T> key) {
2909 public <T> Set<UIElementReference> removeTreeReference(
2910 NodeContext context, CacheKey<T> key) {
2915 public boolean isShown(NodeContext context) {
2920 public void incRef(NodeContext context) {
2925 public void decRef(NodeContext context) {
2930 public void dispose() {
2936 public Object getClicked(Object event) {
2937 MouseEvent e = (MouseEvent)event;
2938 final Tree tree = (Tree) e.getSource();
2939 Point point = new Point(e.x, e.y);
2940 TreeItem item = tree.getItem(point);
2942 // No selectable item at point?
2946 Object data = item.getData();