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.Status;
42 import org.eclipse.core.runtime.jobs.Job;
43 import org.eclipse.jface.layout.GridDataFactory;
44 import org.eclipse.jface.layout.TreeColumnLayout;
45 import org.eclipse.jface.resource.ColorDescriptor;
46 import org.eclipse.jface.resource.DeviceResourceException;
47 import org.eclipse.jface.resource.DeviceResourceManager;
48 import org.eclipse.jface.resource.FontDescriptor;
49 import org.eclipse.jface.resource.ImageDescriptor;
50 import org.eclipse.jface.resource.JFaceResources;
51 import org.eclipse.jface.resource.LocalResourceManager;
52 import org.eclipse.jface.viewers.CellEditor;
53 import org.eclipse.jface.viewers.CellLabelProvider;
54 import org.eclipse.jface.viewers.ColumnViewer;
55 import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
56 import org.eclipse.jface.viewers.ColumnViewerEditorActivationListener;
57 import org.eclipse.jface.viewers.ColumnViewerEditorDeactivationEvent;
58 import org.eclipse.jface.viewers.ColumnWeightData;
59 import org.eclipse.jface.viewers.ComboBoxCellEditor;
60 import org.eclipse.jface.viewers.DialogCellEditor;
61 import org.eclipse.jface.viewers.EditingSupport;
62 import org.eclipse.jface.viewers.ICellEditorValidator;
63 import org.eclipse.jface.viewers.IPostSelectionProvider;
64 import org.eclipse.jface.viewers.ISelection;
65 import org.eclipse.jface.viewers.ISelectionChangedListener;
66 import org.eclipse.jface.viewers.ISelectionProvider;
67 import org.eclipse.jface.viewers.ITreeContentProvider;
68 import org.eclipse.jface.viewers.ITreeViewerListener;
69 import org.eclipse.jface.viewers.SelectionChangedEvent;
70 import org.eclipse.jface.viewers.StructuredSelection;
71 import org.eclipse.jface.viewers.TextCellEditor;
72 import org.eclipse.jface.viewers.TreeExpansionEvent;
73 import org.eclipse.jface.viewers.TreeSelection;
74 import org.eclipse.jface.viewers.TreeViewer;
75 import org.eclipse.jface.viewers.TreeViewerColumn;
76 import org.eclipse.jface.viewers.Viewer;
77 import org.eclipse.jface.viewers.ViewerCell;
78 import org.eclipse.swt.SWT;
79 import org.eclipse.swt.events.DisposeEvent;
80 import org.eclipse.swt.events.DisposeListener;
81 import org.eclipse.swt.events.FocusEvent;
82 import org.eclipse.swt.events.FocusListener;
83 import org.eclipse.swt.events.KeyEvent;
84 import org.eclipse.swt.events.KeyListener;
85 import org.eclipse.swt.events.MouseEvent;
86 import org.eclipse.swt.events.MouseListener;
87 import org.eclipse.swt.events.SelectionListener;
88 import org.eclipse.swt.graphics.Color;
89 import org.eclipse.swt.graphics.Font;
90 import org.eclipse.swt.graphics.Image;
91 import org.eclipse.swt.graphics.Point;
92 import org.eclipse.swt.graphics.RGB;
93 import org.eclipse.swt.layout.FillLayout;
94 import org.eclipse.swt.widgets.Composite;
95 import org.eclipse.swt.widgets.Control;
96 import org.eclipse.swt.widgets.Display;
97 import org.eclipse.swt.widgets.Event;
98 import org.eclipse.swt.widgets.Listener;
99 import org.eclipse.swt.widgets.ScrollBar;
100 import org.eclipse.swt.widgets.Tree;
101 import org.eclipse.swt.widgets.TreeColumn;
102 import org.eclipse.swt.widgets.TreeItem;
103 import org.eclipse.ui.PlatformUI;
104 import org.eclipse.ui.contexts.IContextActivation;
105 import org.eclipse.ui.contexts.IContextService;
106 import org.eclipse.ui.services.IServiceLocator;
107 import org.eclipse.ui.swt.IFocusService;
108 import org.simantics.browsing.ui.BuiltinKeys;
109 import org.simantics.browsing.ui.Column;
110 import org.simantics.browsing.ui.Column.Align;
111 import org.simantics.browsing.ui.DataSource;
112 import org.simantics.browsing.ui.ExplorerState;
113 import org.simantics.browsing.ui.GraphExplorer;
114 import org.simantics.browsing.ui.NodeContext;
115 import org.simantics.browsing.ui.NodeContext.CacheKey;
116 import org.simantics.browsing.ui.NodeContext.PrimitiveQueryKey;
117 import org.simantics.browsing.ui.NodeContext.QueryKey;
118 import org.simantics.browsing.ui.NodeQueryManager;
119 import org.simantics.browsing.ui.NodeQueryProcessor;
120 import org.simantics.browsing.ui.PrimitiveQueryProcessor;
121 import org.simantics.browsing.ui.PrimitiveQueryUpdater;
122 import org.simantics.browsing.ui.SelectionDataResolver;
123 import org.simantics.browsing.ui.SelectionFilter;
124 import org.simantics.browsing.ui.StatePersistor;
125 import org.simantics.browsing.ui.common.AdaptableHintContext;
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.common.state.ExplorerStates;
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.SWTUtils;
178 import org.simantics.utils.ui.jface.BasePostSelectionProvider;
180 import gnu.trove.map.hash.THashMap;
181 import gnu.trove.map.hash.TObjectIntHashMap;
184 * TreeView based GraphExplorer
187 * @author Marko Luukkainen <marko.luukkainen@vtt.fi>
189 public class GraphExplorerImpl2 extends GraphExplorerImplBase implements GraphExplorer {
191 private static final boolean DEBUG_SELECTION_LISTENERS = false;
192 private static final boolean DEBUG = false;
194 private TreeViewer viewer;
196 private LocalResourceManager localResourceManager;
197 private DeviceResourceManager resourceManager;
200 private IThreadWorkQueue thread;
202 @SuppressWarnings({ "rawtypes" })
203 final HashMap<CacheKey<?>, NodeQueryProcessor> processors = new HashMap<CacheKey<?>, NodeQueryProcessor>();
204 @SuppressWarnings({ "rawtypes" })
205 final HashMap<Object, PrimitiveQueryProcessor> primitiveProcessors = new HashMap<Object, PrimitiveQueryProcessor>();
206 @SuppressWarnings({ "rawtypes" })
207 final HashMap<Class, DataSource> dataSources = new HashMap<Class, DataSource>();
209 private FontDescriptor originalFont;
210 protected ColorDescriptor originalForeground;
211 protected ColorDescriptor originalBackground;
212 private Color invalidModificationColor;
214 private Column[] columns;
215 private Map<String,Integer> columnKeyToIndex;
216 private boolean columnsAreVisible = true;
219 private NodeContext rootContext;
220 private TreeNode rootNode;
221 private StatePersistor persistor = null;
223 private boolean editable = true;
225 private boolean disposed = false;
227 private final CopyOnWriteArrayList<FocusListener> focusListeners = new CopyOnWriteArrayList<FocusListener>();
228 private final CopyOnWriteArrayList<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();
229 private final CopyOnWriteArrayList<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>();
231 private int autoExpandLevel = 0;
232 private IServiceLocator serviceLocator;
233 private IContextService contextService = null;
234 private IFocusService focusService = null;
235 private IContextActivation editingContext = null;
237 GeViewerContext explorerContext = new GeViewerContext(this);
239 private GraphExplorerPostSelectionProvider postSelectionProvider = new GraphExplorerPostSelectionProvider(this);
240 private BasePostSelectionProvider selectionProvider = new BasePostSelectionProvider();
241 private SelectionDataResolver selectionDataResolver;
242 private SelectionFilter selectionFilter;
244 private Set<TreeNode> collapsedNodes = new HashSet<TreeNode>();
245 private MapList<NodeContext, TreeNode> contextToNodeMap = new MapList<NodeContext, TreeNode>();
247 private ModificationContext modificationContext = null;
249 private boolean filterSelectionEdit = true;
251 private TreeColumnLayout treeColumnLayout;
253 private boolean expand;
254 private boolean verticalBarVisible = false;
256 private BiFunction<GraphExplorer, Object[], Object[]> selectionTransformation = new BiFunction<GraphExplorer, Object[], Object[]>() {
259 public Object[] apply(GraphExplorer explorer, Object[] objects) {
260 Object[] result = new Object[objects.length];
261 for (int i = 0; i < objects.length; i++) {
262 IHintContext context = new AdaptableHintContext(SelectionHints.KEY_MAIN);
263 context.setHint(SelectionHints.KEY_MAIN, objects[i]);
271 static class TransientStateImpl implements TransientExplorerState {
273 private Integer activeColumn = null;
276 public synchronized Integer getActiveColumn() {
280 public synchronized void setActiveColumn(Integer column) {
281 activeColumn = column;
286 private TransientStateImpl transientState = new TransientStateImpl();
289 public GraphExplorerImpl2(Composite parent) {
290 this(parent, SWT.BORDER | SWT.MULTI );
293 public GraphExplorerImpl2(Composite parent, int style) {
294 this.localResourceManager = new LocalResourceManager(JFaceResources.getResources());
295 this.resourceManager = new DeviceResourceManager(parent.getDisplay());
297 this.imageLoaderJob = new ImageLoaderJob(this);
298 this.imageLoaderJob.setPriority(Job.DECORATE);
300 invalidModificationColor = (Color) localResourceManager.get(ColorDescriptor.createFrom(new RGB(255, 128, 128)));
302 this.thread = SWTThread.getThreadAccess(parent);
304 for (int i = 0; i < 10; i++)
305 explorerContext.activity.push(0);
307 boolean useLayout = true;
308 // FIXME: hack, GraphExplorerComposite uses its own TreeColumnLayout.
309 if (useLayout && !(parent.getLayout() instanceof TreeColumnLayout)) {
311 Composite rootTreeComposite = new Composite(parent, SWT.NONE);
312 treeColumnLayout = new TreeColumnLayout();
313 rootTreeComposite.setLayout(treeColumnLayout);
315 viewer = new TreeViewer(rootTreeComposite,style|SWT.H_SCROLL|SWT.V_SCROLL);
317 GridDataFactory.fillDefaults().grab(true, true).span(3,1).applyTo(rootTreeComposite);
320 viewer = new TreeViewer(parent,style | SWT.H_SCROLL | SWT.V_SCROLL);
323 viewer.getColumnViewerEditor().addEditorActivationListener(new ColumnViewerEditorActivationListener() {
326 public void beforeEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {
331 public void beforeEditorActivated(ColumnViewerEditorActivationEvent event) {
332 // cancel editor activation for double click events.
333 // TODO: this may not work similarly to GraphExplorerImpl
334 if ((event.time - focusGainedAt) < 250L) {
340 public void afterEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {
345 public void afterEditorActivated(ColumnViewerEditorActivationEvent event) {
350 viewer.setUseHashlookup(true);
351 viewer.setContentProvider(new GeViewerContentProvider());
355 originalFont = JFaceResources.getDefaultFontDescriptor();
357 viewer.getTree().setFont((Font) localResourceManager.get(originalFont));
360 setDefaultProcessors();
362 viewer.getTree().addDisposeListener(new DisposeListener() {
365 public void widgetDisposed(DisposeEvent e) {
372 // Add listener to tree for delayed tree population.
374 Listener listener = new Listener() {
377 public void handleEvent(Event event) {
379 switch (event.type) {
395 viewer.getTree().addListener(SWT.Activate, listener);
396 viewer.getTree().addListener(SWT.Deactivate, listener);
397 viewer.getTree().addListener(SWT.Show, listener);
398 viewer.getTree().addListener(SWT.Hide, listener);
399 viewer.getTree().addListener(SWT.Paint,listener);
402 viewer.addTreeListener(new ITreeViewerListener() {
405 public void treeExpanded(TreeExpansionEvent event) {
410 public void treeCollapsed(TreeExpansionEvent event) {
411 collapsedNodes.add((TreeNode)event.getElement());
415 setColumns( new Column[] { new Column(ColumnKeys.SINGLE) });
418 private long focusGainedAt = 0L;
419 private boolean visible = false;
421 private Collection<TreeNode> selectedNodes = new ArrayList<TreeNode>();
423 protected void setBasicListeners() {
424 Tree tree = viewer.getTree();
426 tree.addFocusListener(new FocusListener() {
428 public void focusGained(FocusEvent e) {
429 focusGainedAt = ((long) e.time) & 0xFFFFFFFFL;
430 for (FocusListener listener : focusListeners)
431 listener.focusGained(e);
434 public void focusLost(FocusEvent e) {
435 for (FocusListener listener : focusListeners)
436 listener.focusLost(e);
439 tree.addMouseListener(new MouseListener() {
441 public void mouseDoubleClick(MouseEvent e) {
442 for (MouseListener listener : mouseListeners) {
443 listener.mouseDoubleClick(e);
447 public void mouseDown(MouseEvent e) {
448 for (MouseListener listener : mouseListeners) {
449 listener.mouseDown(e);
453 public void mouseUp(MouseEvent e) {
454 for (MouseListener listener : mouseListeners) {
459 tree.addKeyListener(new KeyListener() {
461 public void keyPressed(KeyEvent e) {
462 for (KeyListener listener : keyListeners) {
463 listener.keyPressed(e);
467 public void keyReleased(KeyEvent e) {
468 for (KeyListener listener : keyListeners) {
469 listener.keyReleased(e);
474 viewer.addSelectionChangedListener(new ISelectionChangedListener() {
477 public void selectionChanged(SelectionChangedEvent event) {
478 //System.out.println("GraphExplorerImpl2.fireSelection");
479 selectedNodes = AdaptionUtils.adaptToCollection(event.getSelection(), TreeNode.class);
480 Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);
481 selectionProvider.setAndFireSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));
485 viewer.addPostSelectionChangedListener(new ISelectionChangedListener() {
488 public void selectionChanged(SelectionChangedEvent event) {
489 //System.out.println("GraphExplorerImpl2.firePostSelection");
490 Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);
491 selectionProvider.firePostSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));
498 private NodeContext pendingRoot;
500 private void activate() {
501 if (pendingRoot != null && !expand) {
502 doSetRoot(pendingRoot);
508 * Invoke only from SWT thread to reset the root of the graph explorer tree.
512 private void doSetRoot(NodeContext root) {
513 Display display = viewer.getTree().getDisplay();
514 if (display.getThread() != Thread.currentThread()) {
515 throw new RuntimeException("Invoke from SWT thread only");
517 // System.out.println("doSetRoot " + root);
520 if (viewer.getTree().isDisposed())
522 if (root.getConstant(BuiltinKeys.INPUT) == null) {
523 ErrorLogger.defaultLogError("root node context does not contain BuiltinKeys.INPUT key. Node = " + root, new Exception("trace"));
529 // Empty caches, release queries.
530 if (rootNode != null) {
533 GeViewerContext oldContext = explorerContext;
534 GeViewerContext newContext = new GeViewerContext(this);
535 this.explorerContext = newContext;
536 oldContext.safeDispose();
538 // Need to empty these or otherwise they won't be emptied until the
539 // explorer is disposed which would mean that many unwanted references
540 // will be held by this map.
541 clearPrimitiveProcessors();
543 this.rootContext = root.getConstant(BuiltinKeys.IS_ROOT) != null ? root
544 : NodeContextUtil.withConstant(root, BuiltinKeys.IS_ROOT, Boolean.TRUE);
546 explorerContext.getCache().incRef(this.rootContext);
552 //refreshColumnSizes();
553 rootNode = new TreeNode(rootContext);
554 if (DEBUG) System.out.println("setRoot " + rootNode);
556 viewer.setInput(rootNode);
558 // Delay content reading.
560 // This is required for cases when GEImpl2 is attached to selection view. Reading content
561 // instantly could stagnate SWT thread under rapid changes in selection. By delaying the
562 // content reading we give the system a change to dispose the GEImpl2 before the content is read.
563 display.asyncExec(new Runnable() {
567 if (rootNode != null) {
568 rootNode.updateChildren();
575 private void initializeState() {
576 if (persistor == null)
578 ExplorerStates.scheduleRead(getRoot(), persistor)
579 .thenAccept(state -> SWTUtils.asyncExec(viewer.getTree(), () -> restoreState(state)));
582 private void restoreState(ExplorerState state) {
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.replaceExpanded(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<>();
721 for (TreeViewerColumn c : treeViewerColumns) {
722 Column col = (Column) (c.getColumn().getData());
724 prevWidths.put(col.getKey(), c.getColumn().getWidth());
725 c.getColumn().dispose();
728 treeViewerColumns.clear();
730 HashMap<String, Integer> keyToIndex = new HashMap<String, Integer>();
731 for (int i = 0; i < cols.length; ++i) {
732 keyToIndex.put(cols[i].getKey(), i);
735 this.columns = Arrays.copyOf(cols, cols.length);
736 //this.columns[cols.length] = FILLER_COLUMN;
737 this.columnKeyToIndex = keyToIndex;
739 Map<Column, Object> map = new HashMap<Column, Object>();
741 // FIXME : temporary workaround for ModelBrowser.
742 viewer.getTree().setHeaderVisible(columns.length == 1 ? false : columnsAreVisible);
746 for (Column column : columns) {
747 TreeViewerColumn tvc = new TreeViewerColumn(viewer, toSWT(column.getAlignment()));
748 treeViewerColumns.add(tvc);
749 tvc.setLabelProvider(cellLabelProvider);
750 EditingSupport support = null;
751 if (editingSupports.size() > columnIndex)
752 support = editingSupports.get(columnIndex);
754 support = new GeEditingSupport(viewer, columnIndex);
755 editingSupports.add(support);
758 tvc.setEditingSupport(support);
760 TreeColumn c = tvc.getColumn();
763 c.setText(column.getLabel());
764 c.setToolTipText(column.getTooltip());
766 int cw = column.getWidth();
768 // Try to keep previous widths
769 Integer w = prevWidths.get(column.getKey());
772 else if (cw != Column.DEFAULT_CONTROL_WIDTH)
774 else if (columns.length == 1) {
775 // FIXME : how to handle single column properly?
779 // Go for some kind of default settings then...
780 if (ColumnKeys.PROPERTY.equals(column.getKey()))
785 if (treeColumnLayout != null) {
786 treeColumnLayout.setColumnData(c, new ColumnWeightData(column.getWeight(), true));
789 // if (!column.hasGrab() && !FILLER.equals(column.getKey())) {
790 // c.addListener(SWT.Resize, resizeListener);
791 // c.setResizable(true);
793 // //c.setResizable(false);
800 if(callback != null) callback.accept(map);
803 int toSWT(Align alignment) {
805 case LEFT: return SWT.LEFT;
806 case CENTER: return SWT.CENTER;
807 case RIGHT: return SWT.RIGHT;
808 default: throw new Error("unhandled alignment: " + alignment);
813 public <T> void setProcessor(NodeQueryProcessor<T> processor) {
815 if (processor == null)
816 throw new IllegalArgumentException("null processor");
818 processors.put(processor.getIdentifier(), processor);
822 public <T> void setPrimitiveProcessor(PrimitiveQueryProcessor<T> processor) {
824 if (processor == null)
825 throw new IllegalArgumentException("null processor");
827 PrimitiveQueryProcessor<?> oldProcessor = primitiveProcessors.put(
828 processor.getIdentifier(), processor);
830 if (oldProcessor instanceof ProcessorLifecycle)
831 ((ProcessorLifecycle) oldProcessor).detached(this);
832 if (processor instanceof ProcessorLifecycle)
833 ((ProcessorLifecycle) processor).attached(this);
837 public <T> void setDataSource(DataSource<T> provider) {
839 if (provider == null)
840 throw new IllegalArgumentException("null provider");
841 dataSources.put(provider.getProvidedClass(), provider);
844 @SuppressWarnings("unchecked")
846 public <T> DataSource<T> removeDataSource(Class<T> forProvidedClass) {
848 if (forProvidedClass == null)
849 throw new IllegalArgumentException("null class");
850 return dataSources.remove(forProvidedClass);
854 public void setPersistor(StatePersistor persistor) {
855 this.persistor = persistor;
859 public SelectionDataResolver getSelectionDataResolver() {
860 return selectionDataResolver;
864 public void setSelectionDataResolver(SelectionDataResolver r) {
865 this.selectionDataResolver = r;
869 public SelectionFilter getSelectionFilter() {
870 return selectionFilter;
874 public void setSelectionFilter(SelectionFilter f) {
875 this.selectionFilter = f;
876 // TODO: re-filter current selection?
879 protected ISelection constructSelection(NodeContext... contexts) {
880 if (contexts == null)
881 throw new IllegalArgumentException("null contexts");
882 if (contexts.length == 0)
883 return StructuredSelection.EMPTY;
884 if (selectionFilter == null)
885 return new StructuredSelection(transformSelection(contexts));
886 return new StructuredSelection( transformSelection(filter(selectionFilter, contexts)) );
889 protected Object[] transformSelection(Object[] objects) {
890 return selectionTransformation.apply(this, objects);
893 protected static Object[] filter(SelectionFilter filter, NodeContext[] contexts) {
894 int len = contexts.length;
895 Object[] objects = new Object[len];
896 for (int i = 0; i < len; ++i)
897 objects[i] = filter.filter(contexts[i]);
902 public void setSelectionTransformation(
903 BiFunction<GraphExplorer, Object[], Object[]> f) {
904 this.selectionTransformation = f;
907 public ISelection getWidgetSelection() {
908 return viewer.getSelection();
912 public <T> void addListener(T listener) {
913 if (listener instanceof FocusListener) {
914 focusListeners.add((FocusListener) listener);
915 } else if (listener instanceof MouseListener) {
916 mouseListeners.add((MouseListener) listener);
917 } else if (listener instanceof KeyListener) {
918 keyListeners.add((KeyListener) listener);
923 public <T> void removeListener(T listener) {
924 if (listener instanceof FocusListener) {
925 focusListeners.remove(listener);
926 } else if (listener instanceof MouseListener) {
927 mouseListeners.remove(listener);
928 } else if (listener instanceof KeyListener) {
929 keyListeners.remove(listener);
933 public void addSelectionListener(SelectionListener listener) {
934 viewer.getTree().addSelectionListener(listener);
937 public void removeSelectionListener(SelectionListener listener) {
938 viewer.getTree().removeSelectionListener(listener);
941 private Set<String> uiContexts;
944 public void setUIContexts(Set<String> contexts) {
945 this.uiContexts = contexts;
949 public void setRoot(final Object root) {
950 if(uiContexts != null && uiContexts.size() == 1)
951 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root, BuiltinKeys.UI_CONTEXT, uiContexts.iterator().next()));
953 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root));
957 public void setRootContext(final NodeContext context) {
958 setRootContext0(context);
961 private void setRoot(NodeContext context) {
963 pendingRoot = context;
964 Display.getDefault().asyncExec(new Runnable() {
967 if (viewer != null && !viewer.getTree().isDisposed())
968 viewer.getTree().redraw();
976 private void setRootContext0(final NodeContext context) {
977 Assert.isNotNull(context, "root must not be null");
978 if (isDisposed() || viewer.getTree().isDisposed())
980 Display display = viewer.getTree().getDisplay();
981 if (display.getThread() == Thread.currentThread()) {
984 display.asyncExec(new Runnable() {
994 public void setFocus() {
995 viewer.getTree().setFocus();
998 @SuppressWarnings("unchecked")
1000 public <T> T getControl() {
1001 return (T)viewer.getTree();
1006 public boolean isDisposed() {
1010 protected void assertNotDisposed() {
1012 throw new IllegalStateException("disposed");
1016 public boolean isEditable() {
1021 public void setEditable(boolean editable) {
1022 if (!thread.currentThreadAccess())
1023 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
1025 this.editable = editable;
1026 Display display = viewer.getTree().getDisplay();
1027 viewer.getTree().setBackground(editable ? null : display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
1030 private void doDispose() {
1034 // TODO: Since GENodeQueryManager is cached in QueryChache and it refers to this class
1035 // we have to remove all references here to reduce memory consumption.
1037 // Proper fix would be to remove references between QueryCache and GENodeQueryManagers.
1038 explorerContext.dispose();
1039 explorerContext = null;
1041 detachPrimitiveProcessors();
1042 primitiveProcessors.clear();
1043 dataSources.clear();
1044 pendingItems.clear();
1046 mouseListeners.clear();
1047 selectionProvider.clearListeners();
1048 selectionProvider = null;
1049 selectionDataResolver = null;
1050 selectedNodes.clear();
1051 selectedNodes = null;
1052 selectionTransformation = null;
1053 originalFont = null;
1054 localResourceManager.dispose();
1055 localResourceManager = null;
1056 // Must shutdown image loader job before disposing its ResourceManager
1057 imageLoaderJob.dispose();
1058 imageLoaderJob.cancel();
1060 imageLoaderJob.join();
1061 imageLoaderJob = null;
1062 } catch (InterruptedException e) {
1063 ErrorLogger.defaultLogError(e);
1065 resourceManager.dispose();
1066 resourceManager = null;
1067 collapsedNodes.clear();
1068 collapsedNodes = null;
1069 if (rootNode != null) {
1073 contextToNodeMap.clear(); // should be empty at this point.
1074 contextToNodeMap = null;
1075 if (postSelectionProvider != null) {
1076 postSelectionProvider.dispose();
1077 postSelectionProvider = null;
1080 modificationContext = null;
1081 focusService = null;
1082 contextService = null;
1083 serviceLocator = null;
1085 columnKeyToIndex.clear();
1086 columnKeyToIndex = null;
1092 public boolean select(NodeContext context) {
1094 assertNotDisposed();
1096 if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) {
1097 viewer.setSelection(new StructuredSelection());
1098 selectionProvider.setAndFireNonEqualSelection(TreeSelection.EMPTY);
1102 viewer.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0)));
1109 public boolean selectPath(Collection<NodeContext> contexts) {
1111 if(contexts == null) throw new IllegalArgumentException("Null list is not allowed");
1112 if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed");
1114 return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0);
1118 private boolean selectPathInternal(NodeContext[] contexts, int position) {
1120 NodeContext head = contexts[position];
1122 if(position == contexts.length-1) {
1123 return select(head);
1127 setExpanded(head, true);
1128 if(!waitVisible(contexts[position+1])) return false;
1130 return selectPathInternal(contexts, position+1);
1134 private boolean waitVisible(NodeContext context) {
1135 long start = System.nanoTime();
1136 while(!isVisible(context)) {
1137 Display.getCurrent().readAndDispatch();
1138 long duration = System.nanoTime() - start;
1139 if(duration > 10e9) return false;
1145 public boolean isVisible(NodeContext context) {
1146 if (contextToNodeMap.getValuesUnsafe(context).size() == 0)
1149 Object elements[] = viewer.getVisibleExpandedElements();
1150 return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0));
1156 public TransientExplorerState getTransientState() {
1157 if (!thread.currentThreadAccess())
1158 throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread());
1159 return transientState;
1163 public <T> T query(NodeContext context, CacheKey<T> key) {
1164 return this.explorerContext.cache.get(context, key);
1168 * For setting a more local service locator for the explorer than the global
1169 * workbench service locator. Sometimes required to give this implementation
1170 * access to local workbench services like IFocusService.
1173 * Must be invoked during right after construction.
1175 * @param serviceLocator
1176 * a specific service locator or <code>null</code> to use the
1177 * workbench global service locator
1179 public void setServiceLocator(IServiceLocator serviceLocator) {
1180 if (serviceLocator == null && PlatformUI.isWorkbenchRunning())
1181 serviceLocator = PlatformUI.getWorkbench();
1182 this.serviceLocator = serviceLocator;
1183 if (serviceLocator != null) {
1184 this.contextService = (IContextService) serviceLocator.getService(IContextService.class);
1185 this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class);
1189 private void detachPrimitiveProcessors() {
1190 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
1191 if (p instanceof ProcessorLifecycle) {
1192 ((ProcessorLifecycle) p).detached(this);
1197 private void clearPrimitiveProcessors() {
1198 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
1199 if (p instanceof ProcessorLifecycle) {
1200 ((ProcessorLifecycle) p).clear();
1206 public void setExpanded(NodeContext context, boolean expanded) {
1207 viewer.setExpandedState(context, expanded);
1212 public void setAutoExpandLevel(int level) {
1213 this.autoExpandLevel = level;
1214 viewer.setAutoExpandLevel(level);
1217 int maxChildren = GraphExplorerImpl.DEFAULT_MAX_CHILDREN;
1220 public int getMaxChildren() {
1225 public void setMaxChildren(int maxChildren) {
1226 this.maxChildren = maxChildren;
1231 public int getMaxChildren(NodeQueryManager manager, NodeContext context) {
1232 Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN);
1233 //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result);
1234 if (result != null) {
1236 throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result);
1243 public <T> NodeQueryProcessor<T> getProcessor(QueryKey<T> key) {
1244 return explorerContext.getProcessor(key);
1248 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(PrimitiveQueryKey<T> key) {
1249 return explorerContext.getPrimitiveProcessor(key);
1252 private HashSet<UpdateItem> pendingItems = new HashSet<UpdateItem>();
1253 private boolean updating = false;
1254 private int updateCounter = 0;
1255 final ScheduledExecutorService uiUpdateScheduler = ThreadUtils.getNonBlockingWorkExecutor();
1257 private class UpdateItem {
1261 public UpdateItem(TreeNode element) {
1265 public UpdateItem(TreeNode element, int columnIndex) {
1266 this.element = element;
1267 this.columnIndex = columnIndex;
1268 if (element != null && element.isDisposed()) {
1269 throw new IllegalArgumentException("Node is disposed. " + element);
1273 public void update(TreeViewer viewer) {
1274 if (element != null) {
1276 if (element.isDisposed()) {
1279 if (((TreeNode)element).updateChildren()) {
1280 viewer.refresh(element,true);
1282 if (columnIndex >= 0) {
1283 viewer.update(element, new String[]{columns[columnIndex].getKey()});
1285 viewer.refresh(element,true);
1289 if (!element.isDisposed() && autoExpandLevel > 1 && !collapsedNodes.contains(element) && ((TreeNode)element).distanceToRoot() <= autoExpandLevel) {
1291 viewer.setExpandedState(element, true);
1295 if (rootNode.updateChildren())
1296 viewer.refresh(rootNode,true);
1301 public boolean equals(Object obj) {
1304 if (obj.getClass() != getClass())
1306 UpdateItem other = (UpdateItem)obj;
1307 if (columnIndex != other.columnIndex)
1309 if (element != null)
1310 return element.equals(other.element);
1311 return other.element == null;
1315 public int hashCode() {
1316 if (element != null)
1317 return element.hashCode() + columnIndex;
1322 private void update(final TreeNode element, final int columnIndex) {
1323 if (DEBUG)System.out.println("update " + element + " " + columnIndex);
1324 if (viewer.getTree().isDisposed())
1326 synchronized (pendingItems) {
1327 pendingItems.add(new UpdateItem(element, columnIndex));
1328 if (updating) return;
1334 private void update(final TreeNode element) {
1335 if (DEBUG)System.out.println("update " + element);
1336 if (viewer.getTree().isDisposed())
1338 if (element != null && element.isDisposed())
1340 synchronized (pendingItems) {
1342 pendingItems.add(new UpdateItem(element));
1343 if (updating) return;
1349 boolean scheduleUpdater() {
1351 if (viewer.getTree().isDisposed())
1354 if (!pendingItems.isEmpty()) {
1356 int activity = explorerContext.activityInt;
1358 if (activity < 100) {
1359 //System.out.println("Scheduling update immediately.");
1360 } else if (activity < 1000) {
1361 //System.out.println("Scheduling update after 500ms.");
1364 //System.out.println("Scheduling update after 3000ms.");
1370 //System.out.println("Scheduling UI update after " + delay + " ms.");
1371 uiUpdateScheduler.schedule(new Runnable() {
1375 if (viewer == null || viewer.getTree().isDisposed())
1378 if (updateCounter > 0) {
1380 uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS);
1382 viewer.getTree().getDisplay().asyncExec(new UpdateRunner(GraphExplorerImpl2.this, GraphExplorerImpl2.this.explorerContext));
1386 }, delay, TimeUnit.MILLISECONDS);
1396 public String startEditing(NodeContext context, String columnKey) {
1397 assertNotDisposed();
1398 if (!thread.currentThreadAccess())
1399 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
1401 if(columnKey.startsWith("#")) {
1402 columnKey = columnKey.substring(1);
1405 Integer columnIndex = columnKeyToIndex.get(columnKey);
1406 if (columnIndex == null)
1407 return "Rename not supported for selection";
1409 viewer.editElement(context, columnIndex);
1410 if(viewer.isCellEditorActive()) return null;
1411 return "Rename not supported for selection";
1415 public String startEditing(String columnKey) {
1416 ISelection selection = postSelectionProvider.getSelection();
1417 if(selection == null) return "Rename not supported for selection";
1418 NodeContext context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class);
1419 if(context == null) return "Rename not supported for selection";
1421 return startEditing(context, columnKey);
1425 public void setSelection(final ISelection selection, boolean forceControlUpdate) {
1426 assertNotDisposed();
1427 boolean equalsOld = selectionProvider.selectionEquals(selection);
1428 if (equalsOld && !forceControlUpdate) {
1429 // Just set the selection object instance, fire no events nor update
1430 // the viewer selection.
1431 selectionProvider.setSelection(selection);
1433 Collection<NodeContext> coll = AdaptionUtils.adaptToCollection(selection, NodeContext.class);
1434 Collection<TreeNode> nodes = new ArrayList<TreeNode>();
1435 for (NodeContext c : coll) {
1436 List<TreeNode> match = contextToNodeMap.getValuesUnsafe(c);
1437 if(match.size() > 0)
1438 nodes.add(match.get(0));
1440 final ISelection sel = new StructuredSelection(nodes.toArray());
1441 if (coll.size() == 0)
1443 // Schedule viewer and selection update if necessary.
1444 if (viewer.getTree().isDisposed())
1446 Display d = viewer.getTree().getDisplay();
1447 if (d.getThread() == Thread.currentThread()) {
1448 viewer.setSelection(sel);
1450 d.asyncExec(new Runnable() {
1453 if (viewer.getTree().isDisposed())
1455 viewer.setSelection(sel);
1463 public void setModificationContext(ModificationContext modificationContext) {
1464 this.modificationContext = modificationContext;
1468 final ExecutorService queryUpdateScheduler = Threads.getExecutor();
1470 private static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {
1471 // This is for query debugging only.
1473 private GraphExplorerImpl2 ge;
1474 int queryIndent = 0;
1476 GECache2 cache = new GECache2();
1477 AtomicBoolean propagating = new AtomicBoolean(false);
1478 Object propagateList = new Object();
1479 Object propagate = new Object();
1480 List<Runnable> scheduleList = new ArrayList<Runnable>();
1481 final Deque<Integer> activity = new LinkedList<Integer>();
1482 int activityInt = 0;
1484 AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();
1487 * Keeps track of nodes that have already been auto-expanded. After
1488 * being inserted into this set, nodes will not be forced to stay in an
1489 * expanded state after that. This makes it possible for the user to
1490 * close auto-expanded nodes.
1492 Map<NodeContext, Boolean> autoExpanded = new WeakHashMap<NodeContext, Boolean>();
1494 public GeViewerContext(GraphExplorerImpl2 ge) {
1499 protected void doDispose() {
1501 autoExpanded.clear();
1505 public IGECache getCache() {
1510 public int queryIndent() {
1515 public int queryIndent(int offset) {
1516 queryIndent += offset;
1521 @SuppressWarnings("unchecked")
1522 public <T> NodeQueryProcessor<T> getProcessor(Object o) {
1525 return ge.processors.get(o);
1529 @SuppressWarnings("unchecked")
1530 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {
1531 return ge.primitiveProcessors.get(o);
1534 @SuppressWarnings("unchecked")
1536 public <T> DataSource<T> getDataSource(Class<T> clazz) {
1537 return ge.dataSources.get(clazz);
1541 public void update(UIElementReference ref) {
1542 if (ref instanceof ViewerCellReference) {
1543 ViewerCellReference tiref = (ViewerCellReference) ref;
1544 Object element = tiref.getElement();
1545 int columnIndex = tiref.getColumn();
1546 // NOTE: must be called regardless of the the item value.
1547 // A null item is currently used to indicate a tree root update.
1548 ge.update((TreeNode)element,columnIndex);
1549 } else if (ref instanceof ViewerRowReference) {
1550 ViewerRowReference rref = (ViewerRowReference)ref;
1551 Object element = rref.getElement();
1552 ge.update((TreeNode)element);
1554 throw new IllegalArgumentException("Ui Reference is unknkown " + ref);
1559 public Object getPropagateLock() {
1564 public Object getPropagateListLock() {
1565 return propagateList;
1569 public boolean isPropagating() {
1570 return propagating.get();
1574 public void setPropagating(boolean b) {
1575 this.propagating.set(b);
1579 public List<Runnable> getScheduleList() {
1580 return scheduleList;
1584 public void setScheduleList(List<Runnable> list) {
1585 this.scheduleList = list;
1589 public Deque<Integer> getActivity() {
1594 public void setActivityInt(int i) {
1595 this.activityInt = i;
1599 public int getActivityInt() {
1604 public void scheduleQueryUpdate(Runnable r) {
1607 if (ge.isDisposed())
1609 if (currentQueryUpdater.compareAndSet(null, r)) {
1610 ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER);
1614 Runnable QUERY_UPDATE_SCHEDULER = new Runnable() {
1617 Runnable r = currentQueryUpdater.getAndSet(null);
1625 public void dispose() {
1627 cache = new DummyCache();
1628 scheduleList.clear();
1629 autoExpanded.clear();
1630 autoExpanded = null;
1640 private static class GeViewerContentProvider implements ITreeContentProvider {
1642 public Object[] getElements(Object inputElement) {
1643 return getChildren(inputElement);
1647 public Object[] getChildren(Object element) {
1648 TreeNode node = (TreeNode)element;
1649 return node.getChildren().toArray();
1654 public Object getParent(Object element) {
1655 TreeNode node = (TreeNode)element;
1656 return node.getParent();
1660 public boolean hasChildren(Object element) {
1661 return getChildren(element).length > 0;
1665 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
1670 public void dispose() {
1675 private class GeViewerLabelProvider extends CellLabelProvider {
1676 private TreeNode node;
1678 private Labeler labeler;
1679 private Imager imager;
1680 Collection<LabelDecorator> labelDecorators;
1681 Collection<ImageDecorator> imageDecorators;
1683 Map<String, String> labels;
1684 Map<String, String> runtimeLabels;
1686 public void update(ViewerCell cell) {
1687 TreeNode node = (TreeNode)cell.getElement();
1688 NodeContext ctx = node.getContext();
1689 int columnIndex = cell.getColumnIndex();
1690 String columnKey = columns[columnIndex].getKey();
1692 // using columnIndex 0 to refresh data.
1693 // Note that this does not work if ViewerCellReferences are used. (At the moment there is no code that would use them).
1694 if (node != this.node || columnIndex == 0) {
1696 GENodeQueryManager manager = node.getManager();
1697 labeler = manager.query(ctx, BuiltinKeys.SELECTED_LABELER);
1698 imager = manager.query(ctx, BuiltinKeys.SELECTED_IMAGER);
1699 labelDecorators = manager.query(ctx, BuiltinKeys.LABEL_DECORATORS);
1700 imageDecorators = manager.query(ctx, BuiltinKeys.IMAGE_DECORATORS);
1702 if (labeler != null) {
1703 labels = labeler.getLabels();
1704 runtimeLabels = labeler.getRuntimeLabels();
1707 runtimeLabels = null;
1711 //if (DEBUG) System.out.println("GeViewerLabelProvider.update " + context + " " + columnIndex);
1713 setText(cell, columnKey);
1714 setImage(cell, columnKey);
1717 void setImage(ViewerCell cell, String columnKey) {
1718 if (imager != null) {
1719 Object descOrImage = null;
1720 boolean hasUncachedImages = false;
1722 ImageDescriptor desc = imager.getImage(columnKey);
1725 // Attempt to decorate the label
1726 if (!imageDecorators.isEmpty()) {
1727 for (ImageDecorator id : imageDecorators) {
1728 ImageDescriptor ds = id.decorateImage(desc, columnKey, index);
1734 // Try resolving only cached images here and now
1735 Object img = localResourceManager.find(desc);
1737 img = resourceManager.find(desc);
1739 descOrImage = img != null ? img : desc;
1740 hasUncachedImages |= img == null;
1743 if (!hasUncachedImages) {
1744 cell.setImage((Image) descOrImage);
1746 // Schedule loading to another thread to refrain from
1748 // the UI with database operations.
1749 queueImageTask(node, new ImageTask(node, descOrImage));
1752 cell.setImage(null);
1756 private void queueImageTask(TreeNode node, ImageTask task) {
1757 synchronized (imageTasks) {
1758 imageTasks.put(node, task);
1760 imageLoaderJob.scheduleIfNecessary(100);
1763 void setText(ViewerCell cell, String key) {
1764 if (labeler != null) {
1766 if (runtimeLabels != null)
1767 s = runtimeLabels.get(key);
1769 s = labels.get(key);
1770 //if (DEBUG) System.out.println(cell.getElement() + " " + cell.getColumnIndex() + " label:" + s + " key:" + key + " " + labels.size() + " " + (runtimeLabels == null ? "-1" : runtimeLabels.size()));
1772 FontDescriptor font = originalFont;
1773 ColorDescriptor bg = originalBackground;
1774 ColorDescriptor fg = originalForeground;
1776 // Attempt to decorate the label
1777 if (!labelDecorators.isEmpty()) {
1779 for (LabelDecorator ld : labelDecorators) {
1780 String ds = ld.decorateLabel(s, key, index);
1784 FontDescriptor dfont = ld.decorateFont(font, key, index);
1788 ColorDescriptor dbg = ld.decorateBackground(bg, key, index);
1792 ColorDescriptor dfg = ld.decorateForeground(fg, key, index);
1798 if (font != originalFont) {
1799 // System.out.println("set font: " + index + ": " +
1801 cell.setFont((Font) localResourceManager.get(font));
1803 cell.setFont((Font) (originalFont != null ? localResourceManager.get(originalFont) : null));
1805 if (bg != originalBackground)
1806 cell.setBackground((Color) localResourceManager.get(bg));
1808 cell.setBackground((Color) (originalBackground != null ? localResourceManager.get(originalBackground) : null));
1809 if (fg != originalForeground)
1810 cell.setForeground((Color) localResourceManager.get(fg));
1812 cell.setForeground((Color) (originalForeground != null ? localResourceManager.get(originalForeground) : null));
1817 cell.setText(Labeler.NO_LABEL);
1825 private class GeEditingSupport extends EditingSupport {
1826 private Object lastElement;
1828 private Modifier lastModifier;
1830 private int columnIndex;
1831 public GeEditingSupport(ColumnViewer viewer, int columnIndex) {
1833 this.columnIndex = columnIndex;
1837 protected boolean canEdit(Object element) {
1838 if (filterSelectionEdit && !selectedNodes.contains(element)) {
1839 // When item is clicked, canEdit is called before the selection is updated.
1840 // This allows filtering edit attempts when the item is selected.
1843 lastElement = null; // clear cached element + modifier.
1844 Modifier modifier = getModifier((TreeNode)element);
1845 if (modifier == null)
1851 protected CellEditor getCellEditor(Object element) {
1852 TreeNode node = (TreeNode) element;
1853 Modifier modifier = getModifier((TreeNode)element);
1854 NodeContext context = node.getContext();
1855 if (modifier instanceof DialogModifier) {
1856 return performDialogEditing(context, (DialogModifier) modifier);
1857 } else if (modifier instanceof CustomModifier) {
1858 return startCustomEditing(node, (CustomModifier) modifier);
1859 } else if (modifier instanceof EnumerationModifier) {
1860 return startEnumerationEditing((EnumerationModifier) modifier);
1862 return startTextEditing(modifier);
1868 protected Object getValue(Object element) {
1869 Modifier modifier = getModifier((TreeNode)element);
1870 return modifier.getValue();
1873 protected void setValue(Object element, Object value) {
1874 Modifier modifier = getModifier((TreeNode)element);
1875 // CustomModifiers have internal set value mechanism.
1876 if (!(modifier instanceof CustomModifier))
1877 modifier.modify((String)value);
1881 CellEditor startTextEditing( Modifier modifier) {
1882 TextCellEditor editor = new ValidatedTextEditor(viewer.getTree());//new TextCellEditor(viewer.getTree());
1883 editor.setValidator(new ModifierValidator(modifier));
1887 CellEditor startEnumerationEditing(EnumerationModifier modifier) {
1888 if (SimanticsUI.isLinuxGTK()) {
1889 // ComboBoxCellEditor2 does not work when GEImpl2 is embedded into dialog (It does work in SelectionView)
1890 // CBCE2 does not work because it receives a focus lost event when the combo/popup is opened.
1891 return new EnumModifierEditor(viewer.getTree(),modifier);
1893 return new EnumModifierEditor2(viewer.getTree(),modifier);
1897 CellEditor performDialogEditing(final NodeContext context, final DialogModifier modifier) {
1898 DialogCellEditor editor = new DialogCellEditor(viewer.getTree()) {
1901 protected Object openDialogBox(Control cellEditorWindow) {
1903 final Semaphore sem = new Semaphore(1);
1904 Consumer<String> callback = result -> {
1908 String status = modifier.query(cellEditorWindow, null, columnIndex, context, callback);
1913 } catch (InterruptedException e) {
1914 e.printStackTrace();
1921 editor.setValidator(new ModifierValidator(modifier));
1925 CellEditor startCustomEditing(TreeNode node, CustomModifier modifier) {
1926 CustomModifierEditor editor = new CustomModifierEditor(viewer.getTree(), modifier, node, columnIndex);
1930 private Modifier getModifier(TreeNode element) {
1931 if (element == lastElement)
1932 return lastModifier;
1933 lastModifier = GraphExplorerImpl2.this.getModifier(element, columnIndex);
1934 lastElement = element;
1935 return lastModifier;
1941 private Modifier getModifier(TreeNode element, int columnIndex) {
1942 GENodeQueryManager manager = element.getManager();
1943 final NodeContext context = element.getContext();
1944 Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
1945 if (labeler == null)
1947 Column column = columns[columnIndex];
1949 return labeler.getModifier(modificationContext, column.getKey());
1953 static class ImageTask {
1955 Object descsOrImage;
1956 public ImageTask(TreeNode node, Object descsOrImage) {
1958 this.descsOrImage = descsOrImage;
1963 * The job that is used for off-loading image loading tasks (see
1964 * {@link ImageTask} to a worker thread from the main UI thread.
1966 ImageLoaderJob imageLoaderJob;
1968 // Map<NodeContext, ImageTask> imageTasks = new THashMap<NodeContext, ImageTask>();
1969 Map<TreeNode, ImageTask> imageTasks = new THashMap<TreeNode, ImageTask>();
1972 * Invoked in a job worker thread.
1977 protected IStatus setPendingImages(IProgressMonitor monitor) {
1978 ImageTask[] tasks = null;
1979 synchronized (imageTasks) {
1980 tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]);
1984 MultiStatus status = null;
1986 // Load missing images
1987 for (ImageTask task : tasks) {
1988 Object desc = task.descsOrImage;
1989 if (desc instanceof ImageDescriptor) {
1991 desc = resourceManager.get((ImageDescriptor) desc);
1992 task.descsOrImage = desc;
1993 } catch (DeviceResourceException e) {
1995 status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null);
1996 status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e));
2002 // Perform final UI updates in the UI thread.
2003 final ImageTask[] _tasks = tasks;
2004 thread.asyncExec(new Runnable() {
2011 return status != null ? status : Status.OK_STATUS;
2015 void setImages(ImageTask[] tasks) {
2016 for (ImageTask task : tasks)
2021 void setImage(ImageTask task) {
2022 if (!task.node.isDisposed())
2023 update(task.node, 0);
2026 private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider {
2028 private GraphExplorerImpl2 ge;
2030 GraphExplorerPostSelectionProvider(GraphExplorerImpl2 ge) {
2039 public void setSelection(final ISelection selection) {
2040 if(ge == null) return;
2041 ge.setSelection(selection, false);
2047 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
2048 if(ge == null) return;
2049 if(ge.isDisposed()) {
2050 if (DEBUG_SELECTION_LISTENERS)
2051 System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener);
2054 ge.selectionProvider.removeSelectionChangedListener(listener);
2058 public void addPostSelectionChangedListener(ISelectionChangedListener listener) {
2059 if(ge == null) return;
2060 if (!ge.thread.currentThreadAccess())
2061 throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2062 if(ge.isDisposed()) {
2063 System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener);
2066 ge.selectionProvider.addPostSelectionChangedListener(listener);
2070 public void removePostSelectionChangedListener(ISelectionChangedListener listener) {
2071 if(ge == null) return;
2072 if(ge.isDisposed()) {
2073 if (DEBUG_SELECTION_LISTENERS)
2074 System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener);
2077 ge.selectionProvider.removePostSelectionChangedListener(listener);
2082 public void addSelectionChangedListener(ISelectionChangedListener listener) {
2083 if(ge == null) return;
2084 if (!ge.thread.currentThreadAccess())
2085 throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2086 if (ge.viewer.getTree().isDisposed() || ge.selectionProvider == null) {
2087 System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener);
2091 ge.selectionProvider.addSelectionChangedListener(listener);
2096 public ISelection getSelection() {
2097 if(ge == null) return StructuredSelection.EMPTY;
2098 if (!ge.thread.currentThreadAccess())
2099 throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread());
2100 if (ge.viewer.getTree().isDisposed() || ge.selectionProvider == null)
2101 return StructuredSelection.EMPTY;
2102 return ge.selectionProvider.getSelection();
2107 static class ModifierValidator implements ICellEditorValidator {
2108 private Modifier modifier;
2109 public ModifierValidator(Modifier modifier) {
2110 this.modifier = modifier;
2114 public String isValid(Object value) {
2115 return modifier.isValid((String)value);
2119 static class UpdateRunner implements Runnable {
2121 final GraphExplorerImpl2 ge;
2123 UpdateRunner(GraphExplorerImpl2 ge, IGraphExplorerContext geContext) {
2130 } catch (Throwable t) {
2131 t.printStackTrace();
2135 public void doRun() {
2137 if (ge.isDisposed())
2140 HashSet<UpdateItem> items;
2142 ScrollBar verticalBar = ge.viewer.getTree().getVerticalBar();
2145 synchronized (ge.pendingItems) {
2146 items = ge.pendingItems;
2147 ge.pendingItems = new HashSet<UpdateItem>();
2149 if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size());
2151 ge.viewer.getTree().setRedraw(false);
2152 for (UpdateItem item : items) {
2153 item.update(ge.viewer);
2156 // check if vertical scroll bar has become visible and refresh layout.
2157 boolean currentlyVerticalBarVisible = verticalBar.isVisible();
2158 if (ge.verticalBarVisible != currentlyVerticalBarVisible) {
2159 ge.verticalBarVisible = currentlyVerticalBarVisible;
2160 ge.viewer.getTree().getParent().layout();
2163 ge.viewer.getTree().setRedraw(true);
2165 synchronized (ge.pendingItems) {
2166 if (!ge.scheduleUpdater()) {
2167 ge.updating = false;
2172 ge.printTree(ge.rootNode, 0);
2179 private class ValidatedTextEditor extends TextCellEditor {
2182 public ValidatedTextEditor(Composite parent) {
2186 protected void editOccured(org.eclipse.swt.events.ModifyEvent e) {
2187 String value = text.getText();
2188 if (value == null) {
2189 value = "";//$NON-NLS-1$
2191 Object typedValue = value;
2192 boolean oldValidState = isValueValid();
2193 boolean newValidState = isCorrect(typedValue);
2194 if (!newValidState) {
2195 text.setBackground(invalidModificationColor);
2196 text.setToolTipText(getErrorMessage());
2198 text.setBackground(null);
2199 text.setToolTipText(null);
2201 valueChanged(oldValidState, newValidState);
2205 private class EnumModifierEditor2 extends ComboBoxCellEditor2 {
2207 List<String> values;
2208 public EnumModifierEditor2(Composite parent, EnumerationModifier modifier) {
2209 super(parent,modifier.getValues().toArray(new String[modifier.getValues().size()]),SWT.READ_ONLY);
2210 values = modifier.getValues();
2211 setValidator(new ModifierValidator(modifier));
2214 protected void doSetValue(Object value) {
2215 super.doSetValue((Integer)values.indexOf(value));
2219 protected Object doGetValue() {
2220 return values.get((Integer)super.doGetValue());
2224 private class EnumModifierEditor extends ComboBoxCellEditor {
2226 List<String> values;
2227 public EnumModifierEditor(Composite parent, EnumerationModifier modifier) {
2228 super(parent,modifier.getValues().toArray(new String[modifier.getValues().size()]),SWT.READ_ONLY);
2229 values = modifier.getValues();
2230 setValidator(new ModifierValidator(modifier));
2233 protected void doSetValue(Object value) {
2234 super.doSetValue((Integer)values.indexOf(value));
2238 protected Object doGetValue() {
2239 return values.get((Integer)super.doGetValue());
2244 private class CustomModifierEditor extends CellEditor implements ICellEditorValidator, DisposeListener {
2245 private CustomModifier modifier;
2246 private TreeNode node;
2247 private NodeContext context;
2248 private int columnIndex;
2249 private Composite control;
2250 private Control origControl;
2252 public CustomModifierEditor(Composite parent, CustomModifier modifier, TreeNode node, int columnIndex) {
2253 this.modifier = modifier;
2255 this.context = node.getContext();
2256 this.columnIndex = columnIndex;
2262 protected Control createControl(Composite parent) {
2263 control = new Composite(parent, SWT.NONE);
2264 control.setLayout(new FillLayout());
2265 origControl = (Control) modifier.createControl(control, null, columnIndex, context);
2272 protected Object doGetValue() {
2273 return modifier.getValue();
2277 protected void doSetValue(Object value) {
2278 //CustomModifier handles value setting internally.
2282 private void reCreate() {
2283 modifier = (CustomModifier)getModifier(node, columnIndex);
2284 if (control != null && !control.isDisposed()) {
2285 if (!origControl.isDisposed())
2286 origControl.dispose();
2287 origControl = (Control)modifier.createControl(control, null, columnIndex, context);
2288 origControl.addDisposeListener(this);
2291 protected void doSetFocus() {
2292 if (control != null && !control.isDisposed())
2297 public void widgetDisposed(DisposeEvent e) {
2298 if (e.widget == origControl) {
2305 public String isValid(Object value) {
2306 return modifier.isValid((String)value);
2311 private class TreeNode implements IAdaptable {
2312 private NodeContext context;
2314 private TreeNode parent;
2315 private List<TreeNode> children = new ArrayList<TreeNode>();
2316 private GENodeQueryManager manager;
2318 private TreeNode(NodeContext context) {
2319 if (context == null)
2320 throw new NullPointerException();
2321 this.context = context;
2322 contextToNodeMap.add(context, this);
2323 manager = new GENodeQueryManager(explorerContext, null, null, ViewerRowReference.create(this));
2326 public List<TreeNode> getChildren() {
2327 synchronized (children) {
2332 public TreeNode getParent() {
2336 public NodeContext getContext() {
2340 public GENodeQueryManager getManager() {
2344 public TreeNode addChild(NodeContext context) {
2345 TreeNode child = new TreeNode(context);
2346 child.parent = this;
2347 children.add(child);
2348 if (DEBUG) System.out.println("Add " + this + " -> " + child);
2352 public TreeNode addChild(int index, NodeContext context) {
2354 TreeNode child = new TreeNode(context);
2355 child.parent = this;
2356 children.add(index,child);
2357 if (DEBUG) System.out.println("Add " + this + " -> " + child + " at " + index);
2361 public TreeNode setChild(int index, NodeContext context) {
2363 TreeNode child = new TreeNode(context);
2364 child.parent = this;
2365 children.set(index,child);
2366 if (DEBUG) System.out.println("Set " + this + " -> " + child + " at " + index);
2370 public int distanceToRoot() {
2372 TreeNode n = getParent();
2381 public void dispose() {
2383 parent.children.remove(this);
2387 public void dispose2() {
2388 if (DEBUG) System.out.println("dispose " + this);
2390 for (TreeNode n : children) {
2395 contextToNodeMap.remove(context, this);
2401 private void clearCache() {
2402 if (explorerContext != null) {
2403 GECache2 cache = explorerContext.cache;
2405 if (cache != null) {
2406 cache.dispose(context);
2411 public boolean updateChildren() {
2412 if (context == null)
2413 throw new IllegalStateException("Node is disposed.");
2415 NodeContext[] childContexts = manager.query(context, BuiltinKeys.FINAL_CHILDREN);
2417 if (DEBUG) System.out.println("updateChildren " + childContexts.length + " " + this);
2420 boolean modified = false;
2421 synchronized (children) {
2423 int oldCount = children.size();
2424 BijectionMap<Integer, Integer> indexes = new BijectionMap<Integer, Integer>();
2425 Set<Integer> mapped = new HashSet<Integer>();
2426 boolean reorder = false;
2427 // locate matching pairs form old and new children
2428 for (int i = 0; i < oldCount; i++) {
2429 NodeContext oldCtx = children.get(i).context;
2430 for (int j = 0; j <childContexts.length; j++) {
2431 if (mapped.contains(j))
2433 if (oldCtx.equals(childContexts[j])) {
2442 // update children if required
2443 if (childContexts.length != oldCount || reorder || childContexts.length != indexes.size()) {
2445 List<TreeNode> oldChildren = new ArrayList<TreeNode>(oldCount);
2446 oldChildren.addAll(children);
2447 if (childContexts.length >= oldCount) {
2448 for (int i = 0; i < oldCount; i++) {
2449 Integer oldIndex = indexes.getLeft(i);
2450 if (oldIndex == null) {
2451 setChild(i, childContexts[i]);
2453 TreeNode n = oldChildren.get(oldIndex);
2458 for (int i = oldCount; i < childContexts.length; i++) {
2459 addChild(childContexts[i]);
2462 for (int i = 0; i < childContexts.length; i++) {
2463 Integer oldIndex = indexes.getLeft(i);
2464 if (oldIndex == null) {
2465 setChild(i, childContexts[i]);
2467 TreeNode n = oldChildren.get(oldIndex);
2471 for (int i = oldCount -1; i >= childContexts.length; i--) {
2475 for (int i = 0; i < oldChildren.size(); i++) {
2476 if (!indexes.containsLeft(i)) {
2477 oldChildren.get(i).dispose2();
2487 public boolean isDisposed() {
2488 return context == null;
2491 @SuppressWarnings("unchecked")
2493 public <T> T getAdapter(Class<T> adapter) {
2494 if (adapter == NodeContext.class)
2496 return context.getAdapter(adapter);
2500 // public String toString() {
2502 // if (manager != null) {
2504 // s+= super.toString() + " ";
2506 // Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
2507 // Map<String,String> labels = labeler.getLabels();
2508 // for (Entry<String, String> l : labels.entrySet()) {
2509 // s+= l.getKey() + " : " + l.getValue() + " ";
2511 // } catch (Exception e) {
2515 // s = super.toString();
2517 // if (context != null)
2518 // s += " context " + context.hashCode();
2525 private static class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor<Boolean> implements
2526 IsExpandedProcessor, ProcessorLifecycle {
2528 * The set of currently expanded node contexts.
2530 private final HashSet<NodeContext> expanded = new HashSet<NodeContext>();
2531 private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();
2535 public TreeNodeIsExpandedProcessor() {
2539 public Object getIdentifier() {
2540 return BuiltinKeys.IS_EXPANDED;
2544 public String toString() {
2545 return "IsExpandedProcessor";
2549 public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Boolean> key) {
2550 boolean isExpanded = expanded.contains(context);
2551 expandedQueries.put(context, updater);
2552 return Boolean.valueOf(isExpanded);
2556 public Collection<NodeContext> getExpanded() {
2557 return new HashSet<NodeContext>(expanded);
2561 public boolean getExpanded(NodeContext context) {
2562 return this.expanded.contains(context);
2566 public boolean setExpanded(NodeContext context, boolean expanded) {
2567 return _setExpanded(context, expanded);
2571 public boolean replaceExpanded(NodeContext context, boolean expanded) {
2572 return nodeStatusChanged(context, expanded);
2575 private boolean _setExpanded(NodeContext context, boolean expanded) {
2577 return this.expanded.add(context);
2579 return this.expanded.remove(context);
2583 Listener treeListener = new Listener() {
2585 public void handleEvent(Event event) {
2586 TreeNode node = (TreeNode) event.item.getData();
2587 NodeContext context = node.getContext();
2588 switch (event.type) {
2590 nodeStatusChanged(context, true);
2593 nodeStatusChanged(context, false);
2599 protected boolean nodeStatusChanged(NodeContext context, boolean expanded) {
2600 boolean result = _setExpanded(context, expanded);
2601 PrimitiveQueryUpdater updater = expandedQueries.get(context);
2602 if (updater != null)
2603 updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded);
2608 public void attached(GraphExplorer explorer) {
2609 Object control = explorer.getControl();
2610 if (control instanceof Tree) {
2611 this.tree = (Tree) control;
2612 tree.addListener(SWT.Expand, treeListener);
2613 tree.addListener(SWT.Collapse, treeListener);
2615 System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control);
2620 public void clear() {
2622 expandedQueries.clear();
2626 public void detached(GraphExplorer explorer) {
2629 tree.removeListener(SWT.Expand, treeListener);
2630 tree.removeListener(SWT.Collapse, treeListener);
2636 private void printTree(TreeNode node, int depth) {
2638 for (int i = 0; i < depth; i++) {
2642 System.out.println(s);
2644 for (TreeNode n : node.getChildren()) {
2651 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used)
2653 final private static class GECacheKey {
2655 private NodeContext context;
2656 private CacheKey<?> key;
2658 GECacheKey(NodeContext context, CacheKey<?> key) {
2659 this.context = context;
2661 if (context == null || key == null)
2662 throw new IllegalArgumentException("Null context or key is not accepted");
2665 GECacheKey(GECacheKey other) {
2666 this.context = other.context;
2667 this.key = other.key;
2668 if (context == null || key == null)
2669 throw new IllegalArgumentException("Null context or key is not accepted");
2672 void setValues(NodeContext context, CacheKey<?> key) {
2673 this.context = context;
2675 if (context == null || key == null)
2676 throw new IllegalArgumentException("Null context or key is not accepted");
2680 public int hashCode() {
2681 return context.hashCode() | key.hashCode();
2685 public boolean equals(Object object) {
2689 else if (object == null)
2692 GECacheKey i = (GECacheKey) object;
2694 return key.equals(i.key) && context.equals(i.context);
2701 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data.
2703 private static class GECache2 implements IGECache {
2705 final HashMap<GECacheKey, IGECacheEntry> entries = new HashMap<GECacheKey, IGECacheEntry>();
2706 final HashMap<GECacheKey, Set<UIElementReference>> treeReferences = new HashMap<GECacheKey, Set<UIElementReference>>();
2707 final HashMap<NodeContext, Set<GECacheKey>> keyRefs = new HashMap<NodeContext, Set<GECacheKey>>();
2710 * This single instance is used for all get operations from the cache. This
2711 * should work since the GE cache is meant to be single-threaded within the
2712 * current UI thread, what ever that thread is. For put operations which
2713 * store the key, this is not used.
2715 NodeContext getNC = new NodeContext() {
2717 public <T> T getAdapter(Class<T> 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();