1 package org.simantics.browsing.ui.nattable;
\r
3 import java.util.ArrayList;
\r
4 import java.util.Arrays;
\r
5 import java.util.Collection;
\r
6 import java.util.Collections;
\r
7 import java.util.Deque;
\r
8 import java.util.HashMap;
\r
9 import java.util.HashSet;
\r
10 import java.util.LinkedList;
\r
11 import java.util.List;
\r
12 import java.util.Map;
\r
13 import java.util.Set;
\r
14 import java.util.WeakHashMap;
\r
15 import java.util.concurrent.CopyOnWriteArrayList;
\r
16 import java.util.concurrent.ExecutorService;
\r
17 import java.util.concurrent.ScheduledExecutorService;
\r
18 import java.util.concurrent.Semaphore;
\r
19 import java.util.concurrent.TimeUnit;
\r
20 import java.util.concurrent.atomic.AtomicBoolean;
\r
21 import java.util.concurrent.atomic.AtomicReference;
\r
22 import java.util.function.Consumer;
\r
24 import org.eclipse.core.runtime.Assert;
\r
25 import org.eclipse.core.runtime.IProgressMonitor;
\r
26 import org.eclipse.core.runtime.IStatus;
\r
27 import org.eclipse.core.runtime.MultiStatus;
\r
28 import org.eclipse.core.runtime.Platform;
\r
29 import org.eclipse.core.runtime.Status;
\r
30 import org.eclipse.core.runtime.jobs.Job;
\r
31 import org.eclipse.jface.layout.GridDataFactory;
\r
32 import org.eclipse.jface.layout.TreeColumnLayout;
\r
33 import org.eclipse.jface.resource.ColorDescriptor;
\r
34 import org.eclipse.jface.resource.DeviceResourceException;
\r
35 import org.eclipse.jface.resource.DeviceResourceManager;
\r
36 import org.eclipse.jface.resource.FontDescriptor;
\r
37 import org.eclipse.jface.resource.ImageDescriptor;
\r
38 import org.eclipse.jface.resource.JFaceResources;
\r
39 import org.eclipse.jface.resource.LocalResourceManager;
\r
40 import org.eclipse.jface.viewers.ColumnWeightData;
\r
41 import org.eclipse.jface.viewers.ICellEditorValidator;
\r
42 import org.eclipse.jface.viewers.IPostSelectionProvider;
\r
43 import org.eclipse.jface.viewers.ISelection;
\r
44 import org.eclipse.jface.viewers.ISelectionChangedListener;
\r
45 import org.eclipse.jface.viewers.ISelectionProvider;
\r
46 import org.eclipse.jface.viewers.SelectionChangedEvent;
\r
47 import org.eclipse.jface.viewers.StructuredSelection;
\r
48 import org.eclipse.jface.window.Window;
\r
49 import org.eclipse.nebula.widgets.nattable.NatTable;
\r
50 import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;
\r
51 import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
\r
52 import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
\r
53 import org.eclipse.nebula.widgets.nattable.config.IEditableRule;
\r
54 import org.eclipse.nebula.widgets.nattable.coordinate.Range;
\r
55 import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
\r
56 import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
\r
57 import org.eclipse.nebula.widgets.nattable.data.convert.DefaultDisplayConverter;
\r
58 import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;
\r
59 import org.eclipse.nebula.widgets.nattable.edit.EditConfigHelper;
\r
60 import org.eclipse.nebula.widgets.nattable.edit.ICellEditHandler;
\r
61 import org.eclipse.nebula.widgets.nattable.edit.config.DefaultEditBindings;
\r
62 import org.eclipse.nebula.widgets.nattable.edit.config.DefaultEditConfiguration;
\r
63 import org.eclipse.nebula.widgets.nattable.edit.editor.AbstractCellEditor;
\r
64 import org.eclipse.nebula.widgets.nattable.edit.editor.ComboBoxCellEditor;
\r
65 import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor;
\r
66 import org.eclipse.nebula.widgets.nattable.edit.editor.IEditErrorHandler;
\r
67 import org.eclipse.nebula.widgets.nattable.edit.editor.TextCellEditor;
\r
68 import org.eclipse.nebula.widgets.nattable.edit.gui.AbstractDialogCellEditor;
\r
69 import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
\r
70 import org.eclipse.nebula.widgets.nattable.grid.cell.AlternatingRowConfigLabelAccumulator;
\r
71 import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
\r
72 import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;
\r
73 import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
\r
74 import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
\r
75 import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultColumnHeaderDataLayer;
\r
76 import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;
\r
77 import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
\r
78 import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
\r
79 import org.eclipse.nebula.widgets.nattable.hideshow.ColumnHideShowLayer;
\r
80 import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent;
\r
81 import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent;
\r
82 import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
\r
83 import org.eclipse.nebula.widgets.nattable.layer.ILayerListener;
\r
84 import org.eclipse.nebula.widgets.nattable.layer.LabelStack;
\r
85 import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator;
\r
86 import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
\r
87 import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;
\r
88 import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter;
\r
89 import org.eclipse.nebula.widgets.nattable.reorder.ColumnReorderLayer;
\r
90 import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
\r
91 import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum;
\r
92 import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration;
\r
93 import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
\r
94 import org.eclipse.nebula.widgets.nattable.ui.menu.AbstractHeaderMenuConfiguration;
\r
95 import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder;
\r
96 import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
\r
97 import org.eclipse.nebula.widgets.nattable.widget.EditModeEnum;
\r
98 import org.eclipse.swt.SWT;
\r
99 import org.eclipse.swt.events.DisposeEvent;
\r
100 import org.eclipse.swt.events.DisposeListener;
\r
101 import org.eclipse.swt.events.FocusEvent;
\r
102 import org.eclipse.swt.events.FocusListener;
\r
103 import org.eclipse.swt.events.KeyEvent;
\r
104 import org.eclipse.swt.events.KeyListener;
\r
105 import org.eclipse.swt.events.MouseEvent;
\r
106 import org.eclipse.swt.events.MouseListener;
\r
107 import org.eclipse.swt.events.SelectionListener;
\r
108 import org.eclipse.swt.graphics.Color;
\r
109 import org.eclipse.swt.graphics.GC;
\r
110 import org.eclipse.swt.graphics.Point;
\r
111 import org.eclipse.swt.graphics.RGB;
\r
112 import org.eclipse.swt.graphics.Rectangle;
\r
113 import org.eclipse.swt.widgets.Composite;
\r
114 import org.eclipse.swt.widgets.Control;
\r
115 import org.eclipse.swt.widgets.Display;
\r
116 import org.eclipse.swt.widgets.Event;
\r
117 import org.eclipse.swt.widgets.Listener;
\r
118 import org.eclipse.swt.widgets.ScrollBar;
\r
119 import org.eclipse.ui.PlatformUI;
\r
120 import org.eclipse.ui.contexts.IContextActivation;
\r
121 import org.eclipse.ui.contexts.IContextService;
\r
122 import org.eclipse.ui.services.IServiceLocator;
\r
123 import org.eclipse.ui.swt.IFocusService;
\r
124 import org.simantics.browsing.ui.BuiltinKeys;
\r
125 import org.simantics.browsing.ui.Column;
\r
126 import org.simantics.browsing.ui.Column.Align;
\r
127 import org.simantics.browsing.ui.DataSource;
\r
128 import org.simantics.browsing.ui.ExplorerState;
\r
129 import org.simantics.browsing.ui.GraphExplorer;
\r
130 import org.simantics.browsing.ui.NodeContext;
\r
131 import org.simantics.browsing.ui.NodeContext.CacheKey;
\r
132 import org.simantics.browsing.ui.NodeContext.PrimitiveQueryKey;
\r
133 import org.simantics.browsing.ui.NodeContext.QueryKey;
\r
134 import org.simantics.browsing.ui.NodeQueryManager;
\r
135 import org.simantics.browsing.ui.NodeQueryProcessor;
\r
136 import org.simantics.browsing.ui.PrimitiveQueryProcessor;
\r
137 import org.simantics.browsing.ui.PrimitiveQueryUpdater;
\r
138 import org.simantics.browsing.ui.SelectionDataResolver;
\r
139 import org.simantics.browsing.ui.SelectionFilter;
\r
140 import org.simantics.browsing.ui.StatePersistor;
\r
141 import org.simantics.browsing.ui.common.ColumnKeys;
\r
142 import org.simantics.browsing.ui.common.ErrorLogger;
\r
143 import org.simantics.browsing.ui.common.NodeContextBuilder;
\r
144 import org.simantics.browsing.ui.common.NodeContextUtil;
\r
145 import org.simantics.browsing.ui.common.internal.GENodeQueryManager;
\r
146 import org.simantics.browsing.ui.common.internal.IGECache;
\r
147 import org.simantics.browsing.ui.common.internal.IGraphExplorerContext;
\r
148 import org.simantics.browsing.ui.common.internal.UIElementReference;
\r
149 import org.simantics.browsing.ui.common.processors.AbstractPrimitiveQueryProcessor;
\r
150 import org.simantics.browsing.ui.common.processors.DefaultCheckedStateProcessor;
\r
151 import org.simantics.browsing.ui.common.processors.DefaultComparableChildrenProcessor;
\r
152 import org.simantics.browsing.ui.common.processors.DefaultFinalChildrenProcessor;
\r
153 import org.simantics.browsing.ui.common.processors.DefaultImageDecoratorProcessor;
\r
154 import org.simantics.browsing.ui.common.processors.DefaultImagerFactoriesProcessor;
\r
155 import org.simantics.browsing.ui.common.processors.DefaultImagerProcessor;
\r
156 import org.simantics.browsing.ui.common.processors.DefaultLabelDecoratorProcessor;
\r
157 import org.simantics.browsing.ui.common.processors.DefaultLabelerFactoriesProcessor;
\r
158 import org.simantics.browsing.ui.common.processors.DefaultLabelerProcessor;
\r
159 import org.simantics.browsing.ui.common.processors.DefaultPrunedChildrenProcessor;
\r
160 import org.simantics.browsing.ui.common.processors.DefaultSelectedImageDecoratorFactoriesProcessor;
\r
161 import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelDecoratorFactoriesProcessor;
\r
162 import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelerProcessor;
\r
163 import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointFactoryProcessor;
\r
164 import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointProcessor;
\r
165 import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionProcessor;
\r
166 import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionsProcessor;
\r
167 import org.simantics.browsing.ui.common.processors.DefaultViewpointProcessor;
\r
168 import org.simantics.browsing.ui.common.processors.IsExpandedProcessor;
\r
169 import org.simantics.browsing.ui.common.processors.NoSelectionRequestProcessor;
\r
170 import org.simantics.browsing.ui.common.processors.ProcessorLifecycle;
\r
171 import org.simantics.browsing.ui.content.Labeler;
\r
172 import org.simantics.browsing.ui.content.Labeler.CustomModifier;
\r
173 import org.simantics.browsing.ui.content.Labeler.DialogModifier;
\r
174 import org.simantics.browsing.ui.content.Labeler.EnumerationModifier;
\r
175 import org.simantics.browsing.ui.content.Labeler.Modifier;
\r
176 import org.simantics.browsing.ui.nattable.override.DefaultTreeLayerConfiguration2;
\r
177 import org.simantics.browsing.ui.swt.Activator;
\r
178 import org.simantics.browsing.ui.swt.AdaptableHintContext;
\r
179 import org.simantics.browsing.ui.swt.DefaultImageDecoratorsProcessor;
\r
180 import org.simantics.browsing.ui.swt.DefaultIsExpandedProcessor;
\r
181 import org.simantics.browsing.ui.swt.DefaultLabelDecoratorsProcessor;
\r
182 import org.simantics.browsing.ui.swt.DefaultSelectedImagerProcessor;
\r
183 import org.simantics.browsing.ui.swt.DefaultShowMaxChildrenProcessor;
\r
184 import org.simantics.browsing.ui.swt.GraphExplorerImplBase;
\r
185 import org.simantics.browsing.ui.swt.ImageLoaderJob;
\r
186 import org.simantics.browsing.ui.swt.ViewerCellReference;
\r
187 import org.simantics.browsing.ui.swt.ViewerRowReference;
\r
188 import org.simantics.browsing.ui.swt.internal.Threads;
\r
189 import org.simantics.db.layer0.SelectionHints;
\r
190 import org.simantics.utils.datastructures.BinaryFunction;
\r
191 import org.simantics.utils.datastructures.MapList;
\r
192 import org.simantics.utils.datastructures.disposable.AbstractDisposable;
\r
193 import org.simantics.utils.datastructures.hints.IHintContext;
\r
194 import org.simantics.utils.threads.IThreadWorkQueue;
\r
195 import org.simantics.utils.threads.SWTThread;
\r
196 import org.simantics.utils.threads.ThreadUtils;
\r
197 import org.simantics.utils.ui.AdaptionUtils;
\r
198 import org.simantics.utils.ui.ISelectionUtils;
\r
199 import org.simantics.utils.ui.jface.BasePostSelectionProvider;
\r
201 import gnu.trove.map.hash.THashMap;
\r
202 import gnu.trove.map.hash.TObjectIntHashMap;
\r
205 * NatTable base GraphExplorer
\r
208 * FIXME : asynchronous node loading does not work properly + check expanded/collapsed sate handling
\r
209 * TODO: InputValidators + input errors
\r
210 * TODO: ability to hide headers
\r
211 * TODO: code cleanup (copied from GraphExplorerImpl2)
\r
213 * @author Marko Luukkainen <marko.luukkainen@vtt.fi>
\r
216 public class NatTableGraphExplorer extends GraphExplorerImplBase implements GraphExplorer{
\r
217 public static final int DEFAULT_MAX_CHILDREN = 1000;
\r
218 private static final boolean DEBUG_SELECTION_LISTENERS = false;
\r
219 private static final boolean DEBUG = false;
\r
221 private Composite composite;
\r
222 private NatTable natTable;
\r
224 private GETreeLayer treeLayer;
\r
225 private DataLayer dataLayer;
\r
226 private ViewportLayer viewportLayer;
\r
227 private SelectionLayer selectionLayer;
\r
228 private GEColumnHeaderDataProvider columnHeaderDataProvider;
\r
229 private GEColumnAccessor columnAccessor;
\r
230 private DefaultRowHeaderDataLayer rowHeaderDataLayer;
\r
231 private DataLayer columnHeaderDataLayer;
\r
232 private DataLayer cornerDataLayer;
\r
234 private List<TreeNode> list = new ArrayList<>();
\r
236 private NatTableSelectionAdaptor selectionAdaptor;
\r
237 private NatTableColumnLayout layout;
\r
239 LocalResourceManager localResourceManager;
\r
240 DeviceResourceManager resourceManager;
\r
243 private IThreadWorkQueue thread;
\r
245 @SuppressWarnings({ "rawtypes" })
\r
246 final HashMap<CacheKey<?>, NodeQueryProcessor> processors = new HashMap<CacheKey<?>, NodeQueryProcessor>();
\r
247 @SuppressWarnings({ "rawtypes" })
\r
248 final HashMap<Object, PrimitiveQueryProcessor> primitiveProcessors = new HashMap<Object, PrimitiveQueryProcessor>();
\r
249 @SuppressWarnings({ "rawtypes" })
\r
250 final HashMap<Class, DataSource> dataSources = new HashMap<Class, DataSource>();
\r
252 FontDescriptor originalFont;
\r
253 protected ColorDescriptor originalForeground;
\r
254 protected ColorDescriptor originalBackground;
\r
255 private Color invalidModificationColor;
\r
257 private Column[] columns;
\r
258 private Map<String,Integer> columnKeyToIndex;
\r
259 private boolean columnsAreVisible = true;
\r
261 private NodeContext rootContext;
\r
262 private TreeNode rootNode;
\r
263 private StatePersistor persistor = null;
\r
265 private boolean editable = true;
\r
267 private boolean disposed = false;
\r
269 private final CopyOnWriteArrayList<FocusListener> focusListeners = new CopyOnWriteArrayList<FocusListener>();
\r
270 private final CopyOnWriteArrayList<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();
\r
271 private final CopyOnWriteArrayList<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>();
\r
273 private int autoExpandLevel = 0;
\r
274 private IServiceLocator serviceLocator;
\r
275 private IContextService contextService = null;
\r
276 private IFocusService focusService = null;
\r
277 private IContextActivation editingContext = null;
\r
279 GeViewerContext explorerContext = new GeViewerContext(this);
\r
281 private GraphExplorerPostSelectionProvider postSelectionProvider = new GraphExplorerPostSelectionProvider(this);
\r
282 private BasePostSelectionProvider selectionProvider = new BasePostSelectionProvider();
\r
283 private SelectionDataResolver selectionDataResolver;
\r
284 private SelectionFilter selectionFilter;
\r
286 private MapList<NodeContext, TreeNode> contextToNodeMap;
\r
288 private ModificationContext modificationContext = null;
\r
290 private boolean filterSelectionEdit = true;
\r
292 private boolean expand;
\r
293 private boolean verticalBarVisible = false;
\r
295 private BinaryFunction<Object[], GraphExplorer, Object[]> selectionTransformation = new BinaryFunction<Object[], GraphExplorer, Object[]>() {
\r
298 public Object[] call(GraphExplorer explorer, Object[] objects) {
\r
299 Object[] result = new Object[objects.length];
\r
300 for (int i = 0; i < objects.length; i++) {
\r
301 IHintContext context = new AdaptableHintContext(SelectionHints.KEY_MAIN);
\r
302 context.setHint(SelectionHints.KEY_MAIN, objects[i]);
\r
303 result[i] = context;
\r
310 static class TransientStateImpl implements TransientExplorerState {
\r
312 private Integer activeColumn = null;
\r
315 public synchronized Integer getActiveColumn() {
\r
316 return activeColumn;
\r
319 public synchronized void setActiveColumn(Integer column) {
\r
320 activeColumn = column;
\r
325 private TransientStateImpl transientState = new TransientStateImpl();
\r
327 public NatTableGraphExplorer(Composite parent) {
\r
328 this(parent, SWT.BORDER | SWT.MULTI );
\r
331 public NatTableGraphExplorer(Composite parent, int style) {
\r
332 this.composite = parent;
\r
335 this.localResourceManager = new LocalResourceManager(JFaceResources.getResources());
\r
336 this.resourceManager = new DeviceResourceManager(parent.getDisplay());
\r
338 this.imageLoaderJob = new ImageLoaderJob(this);
\r
339 this.imageLoaderJob.setPriority(Job.DECORATE);
\r
340 contextToNodeMap = new MapList<NodeContext, TreeNode>();
\r
342 invalidModificationColor = (Color) localResourceManager.get(ColorDescriptor.createFrom(new RGB(255, 128, 128)));
\r
344 this.thread = SWTThread.getThreadAccess(parent);
\r
346 for (int i = 0; i < 10; i++)
\r
347 explorerContext.activity.push(0);
\r
349 originalFont = JFaceResources.getDefaultFontDescriptor();
\r
351 columns = new Column[0];
\r
353 layout = new NatTableColumnLayout(natTable, columnHeaderDataProvider, rowHeaderDataLayer);
\r
354 this.composite.setLayout(layout);
\r
356 setBasicListeners();
\r
357 setDefaultProcessors();
\r
359 natTable.addDisposeListener(new DisposeListener() {
\r
362 public void widgetDisposed(DisposeEvent e) {
\r
368 Listener listener = new Listener() {
\r
371 public void handleEvent(Event event) {
\r
373 switch (event.type) {
\r
382 case SWT.Deactivate:
\r
389 natTable.addListener(SWT.Activate, listener);
\r
390 natTable.addListener(SWT.Deactivate, listener);
\r
391 natTable.addListener(SWT.Show, listener);
\r
392 natTable.addListener(SWT.Hide, listener);
\r
393 natTable.addListener(SWT.Paint,listener);
\r
395 setColumns( new Column[] { new Column(ColumnKeys.SINGLE) });
\r
399 private long focusGainedAt = 0L;
\r
400 private boolean visible = false;
\r
401 private Collection<TreeNode> selectedNodes = new ArrayList<TreeNode>();
\r
403 protected void setBasicListeners() {
\r
405 natTable.addFocusListener(new FocusListener() {
\r
407 public void focusGained(FocusEvent e) {
\r
408 focusGainedAt = ((long) e.time) & 0xFFFFFFFFL;
\r
409 for (FocusListener listener : focusListeners)
\r
410 listener.focusGained(e);
\r
413 public void focusLost(FocusEvent e) {
\r
414 for (FocusListener listener : focusListeners)
\r
415 listener.focusLost(e);
\r
418 natTable.addMouseListener(new MouseListener() {
\r
420 public void mouseDoubleClick(MouseEvent e) {
\r
421 for (MouseListener listener : mouseListeners) {
\r
422 listener.mouseDoubleClick(e);
\r
426 public void mouseDown(MouseEvent e) {
\r
427 for (MouseListener listener : mouseListeners) {
\r
428 listener.mouseDown(e);
\r
432 public void mouseUp(MouseEvent e) {
\r
433 for (MouseListener listener : mouseListeners) {
\r
434 listener.mouseUp(e);
\r
438 natTable.addKeyListener(new KeyListener() {
\r
440 public void keyPressed(KeyEvent e) {
\r
441 for (KeyListener listener : keyListeners) {
\r
442 listener.keyPressed(e);
\r
446 public void keyReleased(KeyEvent e) {
\r
447 for (KeyListener listener : keyListeners) {
\r
448 listener.keyReleased(e);
\r
453 selectionAdaptor.addSelectionChangedListener(new ISelectionChangedListener() {
\r
456 public void selectionChanged(SelectionChangedEvent event) {
\r
457 //System.out.println("GraphExplorerImpl2.fireSelection");
\r
458 selectedNodes = AdaptionUtils.adaptToCollection(event.getSelection(), TreeNode.class);
\r
459 Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);
\r
460 selectionProvider.setAndFireSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));
\r
464 selectionAdaptor.addPostSelectionChangedListener(new ISelectionChangedListener() {
\r
467 public void selectionChanged(SelectionChangedEvent event) {
\r
468 //System.out.println("GraphExplorerImpl2.firePostSelection");
\r
469 Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);
\r
470 selectionProvider.firePostSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));
\r
477 private NodeContext pendingRoot;
\r
479 private void activate() {
\r
480 if (pendingRoot != null && !expand) {
\r
481 doSetRoot(pendingRoot);
\r
482 pendingRoot = null;
\r
487 * Invoke only from SWT thread to reset the root of the graph explorer tree.
\r
491 private void doSetRoot(NodeContext root) {
\r
492 Display display = composite.getDisplay();
\r
493 if (display.getThread() != Thread.currentThread()) {
\r
494 throw new RuntimeException("Invoke from SWT thread only");
\r
496 // System.out.println("doSetRoot " + root);
\r
499 if (natTable.isDisposed())
\r
501 if (root.getConstant(BuiltinKeys.INPUT) == null) {
\r
502 ErrorLogger.defaultLogError("root node context does not contain BuiltinKeys.INPUT key. Node = " + root, new Exception("trace"));
\r
508 // Empty caches, release queries.
\r
509 if (rootNode != null) {
\r
510 rootNode.dispose();
\r
512 GeViewerContext oldContext = explorerContext;
\r
513 GeViewerContext newContext = new GeViewerContext(this);
\r
514 this.explorerContext = newContext;
\r
515 oldContext.safeDispose();
\r
517 // Need to empty these or otherwise they won't be emptied until the
\r
518 // explorer is disposed which would mean that many unwanted references
\r
519 // will be held by this map.
\r
520 clearPrimitiveProcessors();
\r
522 this.rootContext = root.getConstant(BuiltinKeys.IS_ROOT) != null ? root
\r
523 : NodeContextUtil.withConstant(root, BuiltinKeys.IS_ROOT, Boolean.TRUE);
\r
525 explorerContext.getCache().incRef(this.rootContext);
\r
530 select(rootContext);
\r
531 //refreshColumnSizes();
\r
532 rootNode = new TreeNode(rootContext, explorerContext);
\r
533 if (DEBUG) System.out.println("setRoot " + rootNode);
\r
535 // viewer.setInput(rootNode);
\r
537 // Delay content reading.
\r
539 // This is required for cases when GEImpl2 is attached to selection view. Reading content
\r
540 // instantly could stagnate SWT thread under rapid changes in selection. By delaying the
\r
541 // content reading we give the system a change to dispose the GEImpl2 before the content is read.
\r
542 display.asyncExec(new Runnable() {
\r
545 public void run() {
\r
546 if (rootNode != null) {
\r
547 rootNode.updateChildren();
\r
549 natTable.refresh(true);
\r
556 private synchronized void listReIndex() {
\r
558 for (TreeNode c : rootNode.getChildren())
\r
562 private void _insertToList(TreeNode n) {
\r
563 n.setListIndex(list.size());
\r
565 for (TreeNode c : n.getChildren()) {
\r
570 private void initializeState() {
\r
571 if (persistor == null)
\r
574 ExplorerState state = persistor.deserialize(
\r
575 Platform.getStateLocation(Activator.getDefault().getBundle()).toFile(),
\r
579 Object processor = getPrimitiveProcessor(BuiltinKeys.IS_EXPANDED);
\r
580 if (processor instanceof DefaultIsExpandedProcessor) {
\r
581 DefaultIsExpandedProcessor isExpandedProcessor = (DefaultIsExpandedProcessor)processor;
\r
582 for(NodeContext expanded : state.expandedNodes) {
\r
583 isExpandedProcessor.setExpanded(expanded, true);
\r
589 public NodeContext getRoot() {
\r
590 return rootContext;
\r
594 public IThreadWorkQueue getThread() {
\r
599 public NodeContext getParentContext(NodeContext context) {
\r
601 throw new IllegalStateException("disposed");
\r
602 if (!thread.currentThreadAccess())
\r
603 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
\r
605 List<TreeNode> nodes = contextToNodeMap.getValuesUnsafe(context);
\r
606 for (int i = 0; i < nodes.size(); i++) {
\r
607 if (nodes.get(i).getParent() != null)
\r
608 return nodes.get(i).getParent().getContext();
\r
615 @SuppressWarnings("unchecked")
\r
617 public <T> T getAdapter(Class<T> adapter) {
\r
618 if(ISelectionProvider.class == adapter) return (T) postSelectionProvider;
\r
619 else if(IPostSelectionProvider.class == adapter) return (T) postSelectionProvider;
\r
624 protected void setDefaultProcessors() {
\r
625 // Add a simple IMAGER query processor that always returns null.
\r
626 // With this processor no images will ever be shown.
\r
627 // setPrimitiveProcessor(new StaticImagerProcessor(null));
\r
629 setProcessor(new DefaultComparableChildrenProcessor());
\r
630 setProcessor(new DefaultLabelDecoratorsProcessor());
\r
631 setProcessor(new DefaultImageDecoratorsProcessor());
\r
632 setProcessor(new DefaultSelectedLabelerProcessor());
\r
633 setProcessor(new DefaultLabelerFactoriesProcessor());
\r
634 setProcessor(new DefaultSelectedImagerProcessor());
\r
635 setProcessor(new DefaultImagerFactoriesProcessor());
\r
636 setPrimitiveProcessor(new DefaultLabelerProcessor());
\r
637 setPrimitiveProcessor(new DefaultCheckedStateProcessor());
\r
638 setPrimitiveProcessor(new DefaultImagerProcessor());
\r
639 setPrimitiveProcessor(new DefaultLabelDecoratorProcessor());
\r
640 setPrimitiveProcessor(new DefaultImageDecoratorProcessor());
\r
641 setPrimitiveProcessor(new NoSelectionRequestProcessor());
\r
643 setProcessor(new DefaultFinalChildrenProcessor(this));
\r
645 setProcessor(new DefaultPrunedChildrenProcessor());
\r
646 setProcessor(new DefaultSelectedViewpointProcessor());
\r
647 setProcessor(new DefaultSelectedLabelDecoratorFactoriesProcessor());
\r
648 setProcessor(new DefaultSelectedImageDecoratorFactoriesProcessor());
\r
649 setProcessor(new DefaultViewpointContributionsProcessor());
\r
651 setPrimitiveProcessor(new DefaultViewpointProcessor());
\r
652 setPrimitiveProcessor(new DefaultViewpointContributionProcessor());
\r
653 setPrimitiveProcessor(new DefaultSelectedViewpointFactoryProcessor());
\r
654 setPrimitiveProcessor(new TreeNodeIsExpandedProcessor());
\r
655 setPrimitiveProcessor(new DefaultShowMaxChildrenProcessor());
\r
659 public Column[] getColumns() {
\r
660 return Arrays.copyOf(columns, columns.length);
\r
664 public void setColumnsVisible(boolean visible) {
\r
665 columnsAreVisible = visible;
\r
666 //FIXME if(natTable != null) this.columnHeaderDataLayer.setHeaderVisible(columnsAreVisible);
\r
670 public void setColumns(final Column[] columns) {
\r
671 setColumns(columns, null);
\r
675 public void setColumns(final Column[] columns, Consumer<Map<Column, Object>> callback) {
\r
676 assertNotDisposed();
\r
677 checkUniqueColumnKeys(columns);
\r
679 Display d = composite.getDisplay();
\r
680 if (d.getThread() == Thread.currentThread()) {
\r
681 doSetColumns(columns, callback);
\r
682 natTable.refresh(true);
\r
684 d.asyncExec(new Runnable() {
\r
686 public void run() {
\r
687 if (natTable == null)
\r
689 if (natTable.isDisposed())
\r
691 doSetColumns(columns, callback);
\r
692 natTable.refresh(true);
\r
693 natTable.getParent().layout();
\r
698 private void checkUniqueColumnKeys(Column[] cols) {
\r
699 Set<String> usedColumnKeys = new HashSet<String>();
\r
700 List<Column> duplicateColumns = new ArrayList<Column>();
\r
701 for (Column c : cols) {
\r
702 if (!usedColumnKeys.add(c.getKey()))
\r
703 duplicateColumns.add(c);
\r
705 if (!duplicateColumns.isEmpty()) {
\r
706 throw new IllegalArgumentException("All columns do not have unique keys: " + cols + ", overlapping: " + duplicateColumns);
\r
710 private void doSetColumns(Column[] cols, Consumer<Map<Column, Object>> callback) {
\r
712 HashMap<String, Integer> keyToIndex = new HashMap<String, Integer>();
\r
713 for (int i = 0; i < cols.length; ++i) {
\r
714 keyToIndex.put(cols[i].getKey(), i);
\r
717 this.columns = Arrays.copyOf(cols, cols.length);
\r
718 //this.columns[cols.length] = FILLER_COLUMN;
\r
719 this.columnKeyToIndex = keyToIndex;
\r
721 columnHeaderDataProvider.updateColumnSizes();
\r
723 Map<Column, Object> map = new HashMap<Column, Object>();
\r
725 // FIXME : temporary workaround for ModelBrowser.
\r
726 // natTable.setHeaderVisible(columns.length == 1 ? false : columnsAreVisible);
\r
728 int columnIndex = 0;
\r
730 for (Column column : columns) {
\r
731 int width = column.getWidth();
\r
732 if(column.hasGrab()) {
\r
735 layout.setColumnData(columnIndex, new ColumnWeightData(column.getWeight(), width));
\r
740 layout.setColumnData(columnIndex, new ColumnWeightData(columns.length > 1 ? 0 : 1, width));
\r
748 if(callback != null) callback.accept(map);
\r
751 int toSWT(Align alignment) {
\r
752 switch (alignment) {
\r
753 case LEFT: return SWT.LEFT;
\r
754 case CENTER: return SWT.CENTER;
\r
755 case RIGHT: return SWT.RIGHT;
\r
756 default: throw new Error("unhandled alignment: " + alignment);
\r
761 public <T> void setProcessor(NodeQueryProcessor<T> processor) {
\r
762 assertNotDisposed();
\r
763 if (processor == null)
\r
764 throw new IllegalArgumentException("null processor");
\r
766 processors.put(processor.getIdentifier(), processor);
\r
770 public <T> void setPrimitiveProcessor(PrimitiveQueryProcessor<T> processor) {
\r
771 assertNotDisposed();
\r
772 if (processor == null)
\r
773 throw new IllegalArgumentException("null processor");
\r
775 PrimitiveQueryProcessor<?> oldProcessor = primitiveProcessors.put(
\r
776 processor.getIdentifier(), processor);
\r
778 if (oldProcessor instanceof ProcessorLifecycle)
\r
779 ((ProcessorLifecycle) oldProcessor).detached(this);
\r
780 if (processor instanceof ProcessorLifecycle)
\r
781 ((ProcessorLifecycle) processor).attached(this);
\r
785 public <T> void setDataSource(DataSource<T> provider) {
\r
786 assertNotDisposed();
\r
787 if (provider == null)
\r
788 throw new IllegalArgumentException("null provider");
\r
789 dataSources.put(provider.getProvidedClass(), provider);
\r
792 @SuppressWarnings("unchecked")
\r
794 public <T> DataSource<T> removeDataSource(Class<T> forProvidedClass) {
\r
795 assertNotDisposed();
\r
796 if (forProvidedClass == null)
\r
797 throw new IllegalArgumentException("null class");
\r
798 return dataSources.remove(forProvidedClass);
\r
802 public void setPersistor(StatePersistor persistor) {
\r
803 this.persistor = persistor;
\r
807 public SelectionDataResolver getSelectionDataResolver() {
\r
808 return selectionDataResolver;
\r
812 public void setSelectionDataResolver(SelectionDataResolver r) {
\r
813 this.selectionDataResolver = r;
\r
817 public SelectionFilter getSelectionFilter() {
\r
818 return selectionFilter;
\r
822 public void setSelectionFilter(SelectionFilter f) {
\r
823 this.selectionFilter = f;
\r
824 // TODO: re-filter current selection?
\r
827 protected ISelection constructSelection(NodeContext... contexts) {
\r
828 if (contexts == null)
\r
829 throw new IllegalArgumentException("null contexts");
\r
830 if (contexts.length == 0)
\r
831 return StructuredSelection.EMPTY;
\r
832 if (selectionFilter == null)
\r
833 return new StructuredSelection(transformSelection(contexts));
\r
834 return new StructuredSelection( transformSelection(filter(selectionFilter, contexts)) );
\r
837 protected Object[] transformSelection(Object[] objects) {
\r
838 return selectionTransformation.call(this, objects);
\r
841 protected static Object[] filter(SelectionFilter filter, NodeContext[] contexts) {
\r
842 int len = contexts.length;
\r
843 Object[] objects = new Object[len];
\r
844 for (int i = 0; i < len; ++i)
\r
845 objects[i] = filter.filter(contexts[i]);
\r
850 public void setSelectionTransformation(
\r
851 BinaryFunction<Object[], GraphExplorer, Object[]> f) {
\r
852 this.selectionTransformation = f;
\r
855 public ISelection getWidgetSelection() {
\r
856 return selectionAdaptor.getSelection();
\r
860 public <T> void addListener(T listener) {
\r
861 if (listener instanceof FocusListener) {
\r
862 focusListeners.add((FocusListener) listener);
\r
863 } else if (listener instanceof MouseListener) {
\r
864 mouseListeners.add((MouseListener) listener);
\r
865 } else if (listener instanceof KeyListener) {
\r
866 keyListeners.add((KeyListener) listener);
\r
871 public <T> void removeListener(T listener) {
\r
872 if (listener instanceof FocusListener) {
\r
873 focusListeners.remove(listener);
\r
874 } else if (listener instanceof MouseListener) {
\r
875 mouseListeners.remove(listener);
\r
876 } else if (listener instanceof KeyListener) {
\r
877 keyListeners.remove(listener);
\r
881 public void addSelectionListener(SelectionListener listener) {
\r
882 selectionAdaptor.addSelectionListener(listener);
\r
885 public void removeSelectionListener(SelectionListener listener) {
\r
886 selectionAdaptor.removeSelectionListener(listener);
\r
889 private Set<String> uiContexts;
\r
892 public void setUIContexts(Set<String> contexts) {
\r
893 this.uiContexts = contexts;
\r
897 public void setRoot(final Object root) {
\r
898 if(uiContexts != null && uiContexts.size() == 1)
\r
899 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root, BuiltinKeys.UI_CONTEXT, uiContexts.iterator().next()));
\r
901 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root));
\r
905 public void setRootContext(final NodeContext context) {
\r
906 setRootContext0(context);
\r
909 private void setRoot(NodeContext context) {
\r
911 pendingRoot = context;
\r
912 Display.getDefault().asyncExec(new Runnable() {
\r
914 public void run() {
\r
915 if (natTable!= null && !natTable.isDisposed())
\r
921 doSetRoot(context);
\r
924 private void setRootContext0(final NodeContext context) {
\r
925 Assert.isNotNull(context, "root must not be null");
\r
926 if (isDisposed() || natTable.isDisposed())
\r
928 Display display = natTable.getDisplay();
\r
929 if (display.getThread() == Thread.currentThread()) {
\r
932 display.asyncExec(new Runnable() {
\r
934 public void run() {
\r
942 public void setFocus() {
\r
943 natTable.setFocus();
\r
946 @SuppressWarnings("unchecked")
\r
948 public <T> T getControl() {
\r
949 return (T)natTable;
\r
954 public boolean isDisposed() {
\r
958 protected void assertNotDisposed() {
\r
960 throw new IllegalStateException("disposed");
\r
964 public boolean isEditable() {
\r
969 public void setEditable(boolean editable) {
\r
970 if (!thread.currentThreadAccess())
\r
971 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
\r
973 this.editable = editable;
\r
974 Display display = natTable.getDisplay();
\r
975 natTable.setBackground(editable ? null : display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
\r
978 private void doDispose() {
\r
982 // TODO: Since GENodeQueryManager is cached in QueryChache and it refers to this class
\r
983 // we have to remove all references here to reduce memory consumption.
\r
985 // Proper fix would be to remove references between QueryCache and GENodeQueryManagers.
\r
986 if (rootNode != null) {
\r
987 rootNode.dispose();
\r
990 explorerContext.dispose();
\r
991 explorerContext = null;
\r
992 processors.clear();
\r
993 detachPrimitiveProcessors();
\r
994 primitiveProcessors.clear();
\r
995 dataSources.clear();
\r
996 pendingItems.clear();
\r
997 rootContext = null;
\r
998 mouseListeners.clear();
\r
999 selectionProvider.clearListeners();
\r
1000 selectionProvider = null;
\r
1001 selectionDataResolver = null;
\r
1002 selectedNodes.clear();
\r
1003 selectedNodes = null;
\r
1004 selectionTransformation = null;
\r
1005 originalFont = null;
\r
1006 localResourceManager.dispose();
\r
1007 localResourceManager = null;
\r
1008 // Must shutdown image loader job before disposing its ResourceManager
\r
1009 imageLoaderJob.dispose();
\r
1010 imageLoaderJob.cancel();
\r
1012 imageLoaderJob.join();
\r
1013 imageLoaderJob = null;
\r
1014 } catch (InterruptedException e) {
\r
1015 ErrorLogger.defaultLogError(e);
\r
1017 resourceManager.dispose();
\r
1018 resourceManager = null;
\r
1020 contextToNodeMap.clear(); // should be empty at this point.
\r
1021 contextToNodeMap = null;
\r
1022 if (postSelectionProvider != null) {
\r
1023 postSelectionProvider.dispose();
\r
1024 postSelectionProvider = null;
\r
1026 imageTasks = null;
\r
1027 modificationContext = null;
\r
1028 focusService = null;
\r
1029 contextService = null;
\r
1030 serviceLocator = null;
\r
1032 columnKeyToIndex.clear();
\r
1033 columnKeyToIndex = null;
\r
1034 // if (natTable != null) {
\r
1035 // natTable.dispose();
\r
1036 // natTable = null;
\r
1040 viewportLayer = null;
\r
1041 selectionLayer = null;
\r
1042 columnHeaderDataProvider = null;
\r
1043 columnAccessor = null;
\r
1044 rowHeaderDataLayer = null;
\r
1045 columnHeaderDataLayer = null;
\r
1046 cornerDataLayer = null;
\r
1051 public boolean select(NodeContext context) {
\r
1053 assertNotDisposed();
\r
1055 if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) {
\r
1056 StructuredSelection s = new StructuredSelection();
\r
1057 selectionAdaptor.setSelection(s);
\r
1058 selectionProvider.setAndFireNonEqualSelection(s);
\r
1062 selectionAdaptor.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0)));
\r
1069 public boolean selectPath(Collection<NodeContext> contexts) {
\r
1071 if(contexts == null) throw new IllegalArgumentException("Null list is not allowed");
\r
1072 if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed");
\r
1074 return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0);
\r
1078 private boolean selectPathInternal(NodeContext[] contexts, int position) {
\r
1080 NodeContext head = contexts[position];
\r
1082 if(position == contexts.length-1) {
\r
1083 return select(head);
\r
1087 setExpanded(head, true);
\r
1088 if(!waitVisible(contexts[position+1])) return false;
\r
1090 return selectPathInternal(contexts, position+1);
\r
1094 private boolean waitVisible(NodeContext context) {
\r
1095 long start = System.nanoTime();
\r
1096 while(!isVisible(context)) {
\r
1097 Display.getCurrent().readAndDispatch();
\r
1098 long duration = System.nanoTime() - start;
\r
1099 if(duration > 10e9) return false;
\r
1105 public boolean isVisible(NodeContext context) {
\r
1106 if (contextToNodeMap.getValuesUnsafe(context).size() == 0)
\r
1109 return true; //FIXME
\r
1110 // Object elements[] = viewer.getVisibleExpandedElements();
\r
1111 // return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0));
\r
1117 public TransientExplorerState getTransientState() {
\r
1118 if (!thread.currentThreadAccess())
\r
1119 throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread());
\r
1120 return transientState;
\r
1124 public <T> T query(NodeContext context, CacheKey<T> key) {
\r
1125 return this.explorerContext.cache.get(context, key);
\r
1129 * For setting a more local service locator for the explorer than the global
\r
1130 * workbench service locator. Sometimes required to give this implementation
\r
1131 * access to local workbench services like IFocusService.
\r
1134 * Must be invoked during right after construction.
\r
1136 * @param serviceLocator
\r
1137 * a specific service locator or <code>null</code> to use the
\r
1138 * workbench global service locator
\r
1140 public void setServiceLocator(IServiceLocator serviceLocator) {
\r
1141 if (serviceLocator == null && PlatformUI.isWorkbenchRunning())
\r
1142 serviceLocator = PlatformUI.getWorkbench();
\r
1143 this.serviceLocator = serviceLocator;
\r
1144 if (serviceLocator != null) {
\r
1145 this.contextService = (IContextService) serviceLocator.getService(IContextService.class);
\r
1146 this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class);
\r
1150 private void detachPrimitiveProcessors() {
\r
1151 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
\r
1152 if (p instanceof ProcessorLifecycle) {
\r
1153 ((ProcessorLifecycle) p).detached(this);
\r
1158 private void clearPrimitiveProcessors() {
\r
1159 for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
\r
1160 if (p instanceof ProcessorLifecycle) {
\r
1161 ((ProcessorLifecycle) p).clear();
\r
1167 public void setExpanded(NodeContext context, boolean expanded) {
\r
1168 for (TreeNode n : contextToNodeMap.getValues(context)) {
\r
1170 treeLayer.expandTreeRow(n.getListIndex());
\r
1172 treeLayer.collapseTreeRow(n.getListIndex());
\r
1174 //viewer.setExpandedState(context, expanded);
\r
1179 public void setAutoExpandLevel(int level) {
\r
1180 this.autoExpandLevel = level;
\r
1181 treeLayer.expandAllToLevel(level);
\r
1182 //viewer.setAutoExpandLevel(level);
\r
1185 int maxChildren = DEFAULT_MAX_CHILDREN;
\r
1188 public int getMaxChildren() {
\r
1189 return maxChildren;
\r
1193 public void setMaxChildren(int maxChildren) {
\r
1194 this.maxChildren = maxChildren;
\r
1199 public int getMaxChildren(NodeQueryManager manager, NodeContext context) {
\r
1200 Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN);
\r
1201 //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result);
\r
1202 if (result != null) {
\r
1204 throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result);
\r
1207 return maxChildren;
\r
1211 public <T> NodeQueryProcessor<T> getProcessor(QueryKey<T> key) {
\r
1212 return explorerContext.getProcessor(key);
\r
1216 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(PrimitiveQueryKey<T> key) {
\r
1217 return explorerContext.getPrimitiveProcessor(key);
\r
1220 private HashSet<UpdateItem> pendingItems = new HashSet<UpdateItem>();
\r
1221 private boolean updating = false;
\r
1222 private int updateCounter = 0;
\r
1223 final ScheduledExecutorService uiUpdateScheduler = ThreadUtils.getNonBlockingWorkExecutor();
\r
1225 private class UpdateItem {
\r
1229 public UpdateItem(TreeNode element) {
\r
1233 public UpdateItem(TreeNode element, int columnIndex) {
\r
1234 this.element = element;
\r
1235 this.columnIndex = columnIndex;
\r
1236 if (element != null && element.isDisposed()) {
\r
1237 throw new IllegalArgumentException("Node is disposed. " + element);
\r
1241 public void update(NatTable natTable) {
\r
1242 if (element != null) {
\r
1244 if (element.isDisposed()) {
\r
1247 if (((TreeNode)element).updateChildren()) {
\r
1249 natTable.refresh(true);
\r
1250 //viewer.refresh(element,true);
\r
1252 if (columnIndex >= 0) {
\r
1253 natTable.redraw();
\r
1254 //viewer.update(element, new String[]{columns[columnIndex].getKey()});
\r
1256 natTable.redraw();
\r
1257 //viewer.refresh(element,true);
\r
1261 if (!element.isDisposed() && autoExpandLevel > 1 && !element.isExpanded() && element.getDepth() <= autoExpandLevel) {
\r
1263 treeLayer.expandTreeRow(element.getListIndex());
\r
1264 //viewer.setExpandedState(element, true);
\r
1268 if (rootNode.updateChildren()) {
\r
1270 natTable.refresh(true);
\r
1271 //viewer.refresh(rootNode,true);
\r
1277 public boolean equals(Object obj) {
\r
1280 if (obj.getClass() != getClass())
\r
1282 UpdateItem other = (UpdateItem)obj;
\r
1283 if (columnIndex != other.columnIndex)
\r
1285 if (element != null)
\r
1286 return element.equals(other.element);
\r
1287 return other.element == null;
\r
1291 public int hashCode() {
\r
1292 if (element != null)
\r
1293 return element.hashCode() + columnIndex;
\r
1298 private void update(final TreeNode element, final int columnIndex) {
\r
1299 if (DEBUG)System.out.println("update " + element + " " + columnIndex);
\r
1300 if (natTable.isDisposed())
\r
1302 synchronized (pendingItems) {
\r
1303 pendingItems.add(new UpdateItem(element, columnIndex));
\r
1304 if (updating) return;
\r
1306 scheduleUpdater();
\r
1310 private void update(final TreeNode element) {
\r
1311 if (DEBUG)System.out.println("update " + element);
\r
1312 if (natTable.isDisposed())
\r
1314 if (element != null && element.isDisposed())
\r
1316 synchronized (pendingItems) {
\r
1318 pendingItems.add(new UpdateItem(element));
\r
1319 if (updating) return;
\r
1321 scheduleUpdater();
\r
1325 boolean scheduleUpdater() {
\r
1327 if (natTable.isDisposed())
\r
1330 if (!pendingItems.isEmpty()) {
\r
1332 int activity = explorerContext.activityInt;
\r
1334 if (activity < 100) {
\r
1335 //System.out.println("Scheduling update immediately.");
\r
1336 } else if (activity < 1000) {
\r
1337 //System.out.println("Scheduling update after 500ms.");
\r
1340 //System.out.println("Scheduling update after 3000ms.");
\r
1344 updateCounter = 0;
\r
1346 //System.out.println("Scheduling UI update after " + delay + " ms.");
\r
1347 uiUpdateScheduler.schedule(new Runnable() {
\r
1349 public void run() {
\r
1351 if (natTable == null || natTable.isDisposed())
\r
1354 if (updateCounter > 0) {
\r
1355 updateCounter = 0;
\r
1356 uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS);
\r
1358 natTable.getDisplay().asyncExec(new UpdateRunner(NatTableGraphExplorer.this, NatTableGraphExplorer.this.explorerContext));
\r
1362 }, delay, TimeUnit.MILLISECONDS);
\r
1372 public String startEditing(NodeContext context, String columnKey) {
\r
1373 assertNotDisposed();
\r
1374 if (!thread.currentThreadAccess())
\r
1375 throw new IllegalStateException("not in SWT display thread " + thread.getThread());
\r
1377 if(columnKey.startsWith("#")) {
\r
1378 columnKey = columnKey.substring(1);
\r
1381 Integer columnIndex = columnKeyToIndex.get(columnKey);
\r
1382 if (columnIndex == null)
\r
1383 return "Rename not supported for selection";
\r
1385 // viewer.editElement(context, columnIndex);
\r
1386 // if(viewer.isCellEditorActive()) return null;
\r
1387 return "Rename not supported for selection";
\r
1391 public String startEditing(String columnKey) {
\r
1392 ISelection selection = postSelectionProvider.getSelection();
\r
1393 if(selection == null) return "Rename not supported for selection";
\r
1394 NodeContext context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class);
\r
1395 if(context == null) return "Rename not supported for selection";
\r
1397 return startEditing(context, columnKey);
\r
1401 public void setSelection(final ISelection selection, boolean forceControlUpdate) {
\r
1402 assertNotDisposed();
\r
1403 boolean equalsOld = selectionProvider.selectionEquals(selection);
\r
1404 if (equalsOld && !forceControlUpdate) {
\r
1405 // Just set the selection object instance, fire no events nor update
\r
1406 // the viewer selection.
\r
1407 selectionProvider.setSelection(selection);
\r
1409 Collection<NodeContext> coll = AdaptionUtils.adaptToCollection(selection, NodeContext.class);
\r
1410 Collection<TreeNode> nodes = new ArrayList<TreeNode>();
\r
1411 for (NodeContext c : coll) {
\r
1412 List<TreeNode> match = contextToNodeMap.getValuesUnsafe(c);
\r
1413 if(match.size() > 0)
\r
1414 nodes.add(match.get(0));
\r
1416 final ISelection sel = new StructuredSelection(nodes.toArray());
\r
1417 if (coll.size() == 0)
\r
1419 // Schedule viewer and selection update if necessary.
\r
1420 if (natTable.isDisposed())
\r
1422 Display d = natTable.getDisplay();
\r
1423 if (d.getThread() == Thread.currentThread()) {
\r
1424 selectionAdaptor.setSelection(sel);
\r
1426 d.asyncExec(new Runnable() {
\r
1428 public void run() {
\r
1429 if (natTable.isDisposed())
\r
1431 selectionAdaptor.setSelection(sel);
\r
1439 public void setModificationContext(ModificationContext modificationContext) {
\r
1440 this.modificationContext = modificationContext;
\r
1444 final ExecutorService queryUpdateScheduler = Threads.getExecutor();
\r
1447 private double getDisplayScale() {
\r
1448 Point dpi = Display.getCurrent().getDPI();
\r
1449 return (double)dpi.x/96.0;
\r
1452 private void createNatTable() {
\r
1453 GETreeData treeData = new GETreeData(list);
\r
1454 GETreeRowModel<TreeNode> treeRowModel = new GETreeRowModel<TreeNode>(treeData);
\r
1455 columnAccessor = new GEColumnAccessor(this);
\r
1457 IDataProvider dataProvider = new ListDataProvider<TreeNode>(list, columnAccessor);
\r
1459 int defaultFontSize = 12;
\r
1460 int height = (int)Math.ceil(((double)(defaultFontSize))*getDisplayScale()) + DataLayer.DEFAULT_ROW_HEIGHT-defaultFontSize;
\r
1461 dataLayer = new DataLayer(dataProvider, DataLayer.DEFAULT_COLUMN_WIDTH, height);
\r
1463 // resizable rows are unnecessary in Sulca report.
\r
1464 dataLayer.setRowsResizableByDefault(false);
\r
1466 // Row header layer
\r
1467 DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(dataProvider);
\r
1468 rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider);
\r
1470 // adjust row header column width so that row numbers fit into the column.
\r
1471 //adjustRowHeaderWidth(list.size());
\r
1473 // Column header layer
\r
1474 columnHeaderDataProvider = new GEColumnHeaderDataProvider(this, dataLayer);
\r
1475 columnHeaderDataLayer = new DefaultColumnHeaderDataLayer(columnHeaderDataProvider);
\r
1476 columnHeaderDataLayer.setDefaultRowHeight(height);
\r
1477 columnHeaderDataProvider.updateColumnSizes();
\r
1479 //ISortModel sortModel = new EcoSortModel(this, generator,dataLayer);
\r
1481 // Column re-order + hide
\r
1482 ColumnReorderLayer columnReorderLayer = new ColumnReorderLayer(dataLayer);
\r
1483 ColumnHideShowLayer columnHideShowLayer = new ColumnHideShowLayer(columnReorderLayer);
\r
1486 treeLayer = new GETreeLayer(columnHideShowLayer, treeRowModel, false);
\r
1488 selectionLayer = new SelectionLayer(treeLayer);
\r
1490 viewportLayer = new ViewportLayer(selectionLayer);
\r
1492 ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, viewportLayer, selectionLayer);
\r
1493 // Note: The column header layer is wrapped in a filter row composite.
\r
1494 // This plugs in the filter row functionality
\r
1496 ColumnOverrideLabelAccumulator labelAccumulator = new ColumnOverrideLabelAccumulator(columnHeaderDataLayer);
\r
1497 columnHeaderDataLayer.setConfigLabelAccumulator(labelAccumulator);
\r
1499 // Register labels
\r
1500 //SortHeaderLayer<TreeNode> sortHeaderLayer = new SortHeaderLayer<TreeNode>(columnHeaderLayer, sortModel, false);
\r
1502 RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, viewportLayer, selectionLayer);
\r
1505 DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider);
\r
1506 cornerDataLayer = new DataLayer(cornerDataProvider);
\r
1507 //CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, sortHeaderLayer);
\r
1508 CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, columnHeaderLayer);
\r
1511 //GridLayer gridLayer = new GridLayer(viewportLayer,sortHeaderLayer,rowHeaderLayer, cornerLayer);
\r
1512 GridLayer gridLayer = new GridLayer(viewportLayer, columnHeaderLayer,rowHeaderLayer, cornerLayer, false);
\r
1514 /* Since 1.4.0, alternative row rendering uses row indexes in the original data list.
\r
1515 When combined with collapsed tree rows, rows with odd or even index may end up next to each other,
\r
1516 which defeats the purpose of alternating colors. This overrides that and returns the functionality
\r
1517 that we had with 1.0.1. */
\r
1518 gridLayer.setConfigLabelAccumulatorForRegion(GridRegion.BODY, new RelativeAlternatingRowConfigLabelAccumulator());
\r
1519 gridLayer.addConfiguration(new DefaultEditConfiguration());
\r
1520 //gridLayer.addConfiguration(new DefaultEditBindings());
\r
1521 gridLayer.addConfiguration(new GEEditBindings());
\r
1523 natTable = new NatTable(composite,gridLayer,false);
\r
1525 //selectionLayer.registerCommandHandler(new EcoCopyDataCommandHandler(selectionLayer,columnHeaderDataLayer,columnAccessor, columnHeaderDataProvider));
\r
1527 natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable));
\r
1528 natTable.addConfiguration(new DefaultTreeLayerConfiguration2(treeLayer));
\r
1529 natTable.addConfiguration(new SingleClickSortConfiguration());
\r
1530 //natTable.addLayerListener(this);
\r
1532 natTable.addConfiguration(new GENatTableThemeConfiguration(treeData));
\r
1533 natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable));
\r
1535 natTable.addConfiguration(new AbstractRegistryConfiguration() {
\r
1538 public void configureRegistry(IConfigRegistry configRegistry) {
\r
1539 configRegistry.registerConfigAttribute(
\r
1540 EditConfigAttributes.CELL_EDITABLE_RULE,
\r
1541 new IEditableRule() {
\r
1544 public boolean isEditable(ILayerCell cell,
\r
1545 IConfigRegistry configRegistry) {
\r
1546 int col = cell.getColumnIndex();
\r
1547 int row = cell.getRowIndex();
\r
1548 TreeNode node = list.get(row);
\r
1549 Modifier modifier = getModifier(node,col);
\r
1550 return modifier != null;
\r
1555 public boolean isEditable(int columnIndex, int rowIndex) {
\r
1556 // there are no callers?
\r
1561 configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, new AdaptableCellEditor());
\r
1562 configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, new DefaultDisplayConverter(),DisplayMode.EDIT);
\r
1563 // configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_PAINTER, new GECellPainter(),DisplayMode.NORMAL);
\r
1569 natTable.configure();
\r
1571 // natTable.addListener(SWT.MenuDetect, new NatTableMenuListener());
\r
1573 // DefaultToolTip toolTip = new EcoCellToolTip(natTable, columnAccessor);
\r
1574 // toolTip.setBackgroundColor(natTable.getDisplay().getSystemColor(SWT.COLOR_WHITE));
\r
1575 // toolTip.setPopupDelay(500);
\r
1576 // toolTip.activate();
\r
1577 // toolTip.setShift(new Point(10, 10));
\r
1580 // menuManager.createContextMenu(composite);
\r
1581 // natTable.setMenu(getMenuManager().getMenu());
\r
1583 selectionAdaptor = new NatTableSelectionAdaptor(natTable, selectionLayer, treeData);
\r
1586 Modifier getModifier(TreeNode element, int columnIndex) {
\r
1587 GENodeQueryManager manager = element.getManager();
\r
1588 final NodeContext context = element.getContext();
\r
1589 Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
\r
1590 if (labeler == null)
\r
1592 Column column = columns[columnIndex];
\r
1594 return labeler.getModifier(modificationContext, column.getKey());
\r
1598 private class AdaptableCellEditor implements ICellEditor {
\r
1599 ICellEditor editor;
\r
1602 public Control activateCell(Composite parent, Object originalCanonicalValue, EditModeEnum editMode,
\r
1603 ICellEditHandler editHandler, ILayerCell cell, IConfigRegistry configRegistry) {
\r
1604 int col = cell.getColumnIndex();
\r
1605 int row = cell.getRowIndex();
\r
1606 TreeNode node = list.get(row);
\r
1607 Modifier modifier = getModifier(node, col);
\r
1608 if (modifier == null)
\r
1612 if (modifier instanceof DialogModifier) {
\r
1613 DialogModifier mod = (DialogModifier)modifier;
\r
1614 editor = new DialogCellEditor(node, col, mod);
\r
1615 } else if (modifier instanceof CustomModifier) {
\r
1616 CustomModifier mod = (CustomModifier)modifier;
\r
1617 editor = new CustomCellEditor(node, col, mod);
\r
1618 } else if (modifier instanceof EnumerationModifier) {
\r
1619 EnumerationModifier mod = (EnumerationModifier)modifier;
\r
1620 editor = new ComboBoxCellEditor(mod.getValues());
\r
1622 editor = new TextCellEditor();
\r
1625 return editor.activateCell(parent, originalCanonicalValue, editMode, editHandler, cell, configRegistry);
\r
1629 public int getColumnIndex() {
\r
1630 return editor.getColumnIndex();
\r
1634 public int getRowIndex() {
\r
1635 return editor.getRowIndex();
\r
1639 public int getColumnPosition() {
\r
1640 return editor.getColumnPosition();
\r
1644 public int getRowPosition() {
\r
1645 return editor.getRowPosition();
\r
1649 public Object getEditorValue() {
\r
1650 return editor.getEditorValue();
\r
1654 public void setEditorValue(Object value) {
\r
1655 editor.setEditorValue(value);
\r
1660 public Object getCanonicalValue() {
\r
1661 return editor.getCanonicalValue();
\r
1665 public Object getCanonicalValue(IEditErrorHandler conversionErrorHandler) {
\r
1666 return editor.getCanonicalValue();
\r
1670 public void setCanonicalValue(Object canonicalValue) {
\r
1671 editor.setCanonicalValue(canonicalValue);
\r
1676 public boolean validateCanonicalValue(Object canonicalValue) {
\r
1677 return editor.validateCanonicalValue(canonicalValue);
\r
1681 public boolean validateCanonicalValue(Object canonicalValue, IEditErrorHandler validationErrorHandler) {
\r
1682 return editor.validateCanonicalValue(canonicalValue, validationErrorHandler);
\r
1686 public boolean commit(MoveDirectionEnum direction) {
\r
1687 return editor.commit(direction);
\r
1691 public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit) {
\r
1692 return editor.commit(direction, closeAfterCommit);
\r
1696 public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit, boolean skipValidation) {
\r
1697 return editor.commit(direction, closeAfterCommit, skipValidation);
\r
1701 public void close() {
\r
1707 public boolean isClosed() {
\r
1708 return editor.isClosed();
\r
1712 public Control getEditorControl() {
\r
1713 return editor.getEditorControl();
\r
1717 public Control createEditorControl(Composite parent) {
\r
1718 return editor.createEditorControl(parent);
\r
1722 public boolean openInline(IConfigRegistry configRegistry, List<String> configLabels) {
\r
1723 return EditConfigHelper.openInline(configRegistry, configLabels);
\r
1727 public boolean supportMultiEdit(IConfigRegistry configRegistry, List<String> configLabels) {
\r
1728 return editor.supportMultiEdit(configRegistry, configLabels);
\r
1732 public boolean openMultiEditDialog() {
\r
1733 return editor.openMultiEditDialog();
\r
1737 public boolean openAdjacentEditor() {
\r
1738 return editor.openAdjacentEditor();
\r
1742 public boolean activateAtAnyPosition() {
\r
1747 public boolean activateOnTraversal(IConfigRegistry configRegistry, List<String> configLabels) {
\r
1748 return editor.activateOnTraversal(configRegistry, configLabels);
\r
1752 public void addEditorControlListeners() {
\r
1753 editor.addEditorControlListeners();
\r
1758 public void removeEditorControlListeners() {
\r
1759 editor.removeEditorControlListeners();
\r
1764 public Rectangle calculateControlBounds(Rectangle cellBounds) {
\r
1765 return editor.calculateControlBounds(cellBounds);
\r
1771 private class CustomCellEditor extends AbstractCellEditor {
\r
1773 CustomModifier customModifier;
\r
1777 public CustomCellEditor(TreeNode node, int column, CustomModifier customModifier) {
\r
1778 this.customModifier = customModifier;
\r
1780 this.column = column;
\r
1784 public Object getEditorValue() {
\r
1785 return customModifier.getValue();
\r
1789 public void setEditorValue(Object value) {
\r
1790 customModifier.modify(value.toString());
\r
1795 public Control getEditorControl() {
\r
1800 public Control createEditorControl(Composite parent) {
\r
1801 return (Control)customModifier.createControl(parent, null, column, node.getContext());
\r
1806 protected Control activateCell(Composite parent, Object originalCanonicalValue) {
\r
1807 this.control = createEditorControl(parent);
\r
1814 private class DialogCellEditor extends AbstractDialogCellEditor {
\r
1816 DialogModifier dialogModifier;
\r
1819 String res = null;
\r
1822 boolean closed = false;
\r
1824 public DialogCellEditor(TreeNode node, int column, DialogModifier dialogModifier) {
\r
1825 this.dialogModifier = dialogModifier;
\r
1827 this.column = column;
\r
1831 public int open() {
\r
1832 sem = new Semaphore(1);
\r
1833 Consumer<String> callback = result -> {
\r
1837 String status = dialogModifier.query(this.parent.getShell(), null, column, node.getContext(), callback);
\r
1838 if (status != null) {
\r
1840 return Window.CANCEL;
\r
1845 } catch (InterruptedException e) {
\r
1846 e.printStackTrace();
\r
1853 public DialogModifier createDialogInstance() {
\r
1855 return dialogModifier;
\r
1859 public Object getDialogInstance() {
\r
1860 return (DialogModifier)this.dialog;
\r
1864 public void close() {
\r
1869 public Object getEditorValue() {
\r
1874 public void setEditorValue(Object value) {
\r
1875 // dialog modifier handles this internally
\r
1879 public boolean isClosed() {
\r
1887 * The job that is used for off-loading image loading tasks (see
\r
1888 * {@link ImageTask} to a worker thread from the main UI thread.
\r
1890 ImageLoaderJob imageLoaderJob;
\r
1892 // Map<NodeContext, ImageTask> imageTasks = new THashMap<NodeContext, ImageTask>();
\r
1893 Map<TreeNode, ImageTask> imageTasks = new THashMap<TreeNode, ImageTask>();
\r
1895 void queueImageTask(TreeNode node, ImageTask task) {
\r
1896 synchronized (imageTasks) {
\r
1897 imageTasks.put(node, task);
\r
1899 imageLoaderJob.scheduleIfNecessary(100);
\r
1903 * Invoked in a job worker thread.
\r
1908 protected IStatus setPendingImages(IProgressMonitor monitor) {
\r
1909 ImageTask[] tasks = null;
\r
1910 synchronized (imageTasks) {
\r
1911 tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]);
\r
1912 imageTasks.clear();
\r
1915 MultiStatus status = null;
\r
1917 // Load missing images
\r
1918 for (ImageTask task : tasks) {
\r
1919 Object desc = task.descsOrImage;
\r
1920 if (desc instanceof ImageDescriptor) {
\r
1922 desc = resourceManager.get((ImageDescriptor) desc);
\r
1923 task.descsOrImage = desc;
\r
1924 } catch (DeviceResourceException e) {
\r
1925 if (status == null)
\r
1926 status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null);
\r
1927 status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e));
\r
1933 // Perform final UI updates in the UI thread.
\r
1934 final ImageTask[] _tasks = tasks;
\r
1935 thread.asyncExec(new Runnable() {
\r
1937 public void run() {
\r
1938 setImages(_tasks);
\r
1942 return status != null ? status : Status.OK_STATUS;
\r
1946 void setImages(ImageTask[] tasks) {
\r
1947 for (ImageTask task : tasks)
\r
1952 void setImage(ImageTask task) {
\r
1953 if (!task.node.isDisposed())
\r
1954 update(task.node, 0);
\r
1957 private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider {
\r
1959 private NatTableGraphExplorer ge;
\r
1961 GraphExplorerPostSelectionProvider(NatTableGraphExplorer ge) {
\r
1970 public void setSelection(final ISelection selection) {
\r
1971 if(ge == null) return;
\r
1972 ge.setSelection(selection, false);
\r
1978 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
\r
1979 if(ge == null) return;
\r
1980 if(ge.isDisposed()) {
\r
1981 if (DEBUG_SELECTION_LISTENERS)
\r
1982 System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener);
\r
1985 ge.selectionProvider.removeSelectionChangedListener(listener);
\r
1989 public void addPostSelectionChangedListener(ISelectionChangedListener listener) {
\r
1990 if(ge == null) return;
\r
1991 if (!ge.thread.currentThreadAccess())
\r
1992 throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
\r
1993 if(ge.isDisposed()) {
\r
1994 System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener);
\r
1997 ge.selectionProvider.addPostSelectionChangedListener(listener);
\r
2001 public void removePostSelectionChangedListener(ISelectionChangedListener listener) {
\r
2002 if(ge == null) return;
\r
2003 if(ge.isDisposed()) {
\r
2004 if (DEBUG_SELECTION_LISTENERS)
\r
2005 System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener);
\r
2008 ge.selectionProvider.removePostSelectionChangedListener(listener);
\r
2013 public void addSelectionChangedListener(ISelectionChangedListener listener) {
\r
2014 if(ge == null) return;
\r
2015 if (!ge.thread.currentThreadAccess())
\r
2016 throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
\r
2017 if (ge.natTable.isDisposed() || ge.selectionProvider == null) {
\r
2018 System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener);
\r
2022 ge.selectionProvider.addSelectionChangedListener(listener);
\r
2027 public ISelection getSelection() {
\r
2028 if(ge == null) return StructuredSelection.EMPTY;
\r
2029 if (!ge.thread.currentThreadAccess())
\r
2030 throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread());
\r
2031 if (ge.natTable.isDisposed() || ge.selectionProvider == null)
\r
2032 return StructuredSelection.EMPTY;
\r
2033 return ge.selectionProvider.getSelection();
\r
2038 static class ModifierValidator implements ICellEditorValidator {
\r
2039 private Modifier modifier;
\r
2040 public ModifierValidator(Modifier modifier) {
\r
2041 this.modifier = modifier;
\r
2045 public String isValid(Object value) {
\r
2046 return modifier.isValid((String)value);
\r
2050 static class UpdateRunner implements Runnable {
\r
2052 final NatTableGraphExplorer ge;
\r
2054 UpdateRunner(NatTableGraphExplorer ge, IGraphExplorerContext geContext) {
\r
2058 public void run() {
\r
2061 } catch (Throwable t) {
\r
2062 t.printStackTrace();
\r
2066 public void doRun() {
\r
2068 if (ge.isDisposed())
\r
2071 HashSet<UpdateItem> items;
\r
2073 ScrollBar verticalBar = ge.natTable.getVerticalBar();
\r
2076 synchronized (ge.pendingItems) {
\r
2077 items = ge.pendingItems;
\r
2078 ge.pendingItems = new HashSet<UpdateItem>();
\r
2080 if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size());
\r
2082 ge.natTable.setRedraw(false);
\r
2083 for (UpdateItem item : items) {
\r
2084 item.update(ge.natTable);
\r
2087 // check if vertical scroll bar has become visible and refresh layout.
\r
2088 boolean currentlyVerticalBarVisible = verticalBar.isVisible();
\r
2089 if (ge.verticalBarVisible != currentlyVerticalBarVisible) {
\r
2090 ge.verticalBarVisible = currentlyVerticalBarVisible;
\r
2091 ge.natTable.getParent().layout();
\r
2094 ge.natTable.setRedraw(true);
\r
2096 synchronized (ge.pendingItems) {
\r
2097 if (!ge.scheduleUpdater()) {
\r
2098 ge.updating = false;
\r
2102 if (!ge.updating) {
\r
2103 ge.printTree(ge.rootNode, 0);
\r
2112 public static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {
\r
2113 // This is for query debugging only.
\r
2115 private NatTableGraphExplorer ge;
\r
2116 int queryIndent = 0;
\r
2118 GECache2 cache = new GECache2();
\r
2119 AtomicBoolean propagating = new AtomicBoolean(false);
\r
2120 Object propagateList = new Object();
\r
2121 Object propagate = new Object();
\r
2122 List<Runnable> scheduleList = new ArrayList<Runnable>();
\r
2123 final Deque<Integer> activity = new LinkedList<Integer>();
\r
2124 int activityInt = 0;
\r
2126 AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();
\r
2129 * Keeps track of nodes that have already been auto-expanded. After
\r
2130 * being inserted into this set, nodes will not be forced to stay in an
\r
2131 * expanded state after that. This makes it possible for the user to
\r
2132 * close auto-expanded nodes.
\r
2134 Map<NodeContext, Boolean> autoExpanded = new WeakHashMap<NodeContext, Boolean>();
\r
2136 public GeViewerContext(NatTableGraphExplorer ge) {
\r
2140 public MapList<NodeContext,TreeNode> getContextToNodeMap() {
\r
2143 return ge.contextToNodeMap;
\r
2146 public NatTableGraphExplorer getGe() {
\r
2151 protected void doDispose() {
\r
2153 autoExpanded.clear();
\r
2157 public IGECache getCache() {
\r
2162 public int queryIndent() {
\r
2163 return queryIndent;
\r
2167 public int queryIndent(int offset) {
\r
2168 queryIndent += offset;
\r
2169 return queryIndent;
\r
2173 @SuppressWarnings("unchecked")
\r
2174 public <T> NodeQueryProcessor<T> getProcessor(Object o) {
\r
2177 return ge.processors.get(o);
\r
2181 @SuppressWarnings("unchecked")
\r
2182 public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {
\r
2183 return ge.primitiveProcessors.get(o);
\r
2186 @SuppressWarnings("unchecked")
\r
2188 public <T> DataSource<T> getDataSource(Class<T> clazz) {
\r
2189 return ge.dataSources.get(clazz);
\r
2193 public void update(UIElementReference ref) {
\r
2194 if (ref instanceof ViewerCellReference) {
\r
2195 ViewerCellReference tiref = (ViewerCellReference) ref;
\r
2196 Object element = tiref.getElement();
\r
2197 int columnIndex = tiref.getColumn();
\r
2198 // NOTE: must be called regardless of the the item value.
\r
2199 // A null item is currently used to indicate a tree root update.
\r
2200 ge.update((TreeNode)element,columnIndex);
\r
2201 } else if (ref instanceof ViewerRowReference) {
\r
2202 ViewerRowReference rref = (ViewerRowReference)ref;
\r
2203 Object element = rref.getElement();
\r
2204 ge.update((TreeNode)element);
\r
2206 throw new IllegalArgumentException("Ui Reference is unknkown " + ref);
\r
2211 public Object getPropagateLock() {
\r
2216 public Object getPropagateListLock() {
\r
2217 return propagateList;
\r
2221 public boolean isPropagating() {
\r
2222 return propagating.get();
\r
2226 public void setPropagating(boolean b) {
\r
2227 this.propagating.set(b);
\r
2231 public List<Runnable> getScheduleList() {
\r
2232 return scheduleList;
\r
2236 public void setScheduleList(List<Runnable> list) {
\r
2237 this.scheduleList = list;
\r
2241 public Deque<Integer> getActivity() {
\r
2246 public void setActivityInt(int i) {
\r
2247 this.activityInt = i;
\r
2251 public int getActivityInt() {
\r
2252 return activityInt;
\r
2256 public void scheduleQueryUpdate(Runnable r) {
\r
2259 if (ge.isDisposed())
\r
2261 if (currentQueryUpdater.compareAndSet(null, r)) {
\r
2262 ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER);
\r
2266 Runnable QUERY_UPDATE_SCHEDULER = new Runnable() {
\r
2268 public void run() {
\r
2269 Runnable r = currentQueryUpdater.getAndSet(null);
\r
2277 public void dispose() {
\r
2279 cache = new DummyCache();
\r
2280 scheduleList.clear();
\r
2281 autoExpanded.clear();
\r
2282 autoExpanded = null;
\r
2288 private class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor<Boolean> implements
\r
2289 IsExpandedProcessor, ProcessorLifecycle {
\r
2291 * The set of currently expanded node contexts.
\r
2293 private final HashSet<NodeContext> expanded = new HashSet<NodeContext>();
\r
2294 private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();
\r
2296 private NatTable natTable;
\r
2297 private List<TreeNode> list;
\r
2299 public TreeNodeIsExpandedProcessor() {
\r
2303 public Object getIdentifier() {
\r
2304 return BuiltinKeys.IS_EXPANDED;
\r
2308 public String toString() {
\r
2309 return "IsExpandedProcessor";
\r
2313 public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Boolean> key) {
\r
2314 boolean isExpanded = expanded.contains(context);
\r
2315 expandedQueries.put(context, updater);
\r
2316 return Boolean.valueOf(isExpanded);
\r
2320 public Collection<NodeContext> getExpanded() {
\r
2321 return new HashSet<NodeContext>(expanded);
\r
2325 public boolean getExpanded(NodeContext context) {
\r
2326 return this.expanded.contains(context);
\r
2330 public boolean setExpanded(NodeContext context, boolean expanded) {
\r
2331 return _setExpanded(context, expanded);
\r
2335 public boolean replaceExpanded(NodeContext context, boolean expanded) {
\r
2336 return nodeStatusChanged(context, expanded);
\r
2339 private boolean _setExpanded(NodeContext context, boolean expanded) {
\r
2341 return this.expanded.add(context);
\r
2343 return this.expanded.remove(context);
\r
2347 ILayerListener treeListener = new ILayerListener() {
\r
2350 public void handleLayerEvent(ILayerEvent event) {
\r
2351 // TODO Auto-generated method stub
\r
2352 if (event instanceof ShowRowPositionsEvent) {
\r
2353 ShowRowPositionsEvent e = (ShowRowPositionsEvent)event;
\r
2354 for (Range r : e.getRowPositionRanges()) {
\r
2355 int expanded = viewportLayer.getRowIndexByPosition(r.start-2)+1;
\r
2356 //System.out.println("ex " + expanded);
\r
2357 if (expanded < 0) {
\r
2360 nodeStatusChanged(list.get(expanded).getContext(), false);
\r
2362 } else if (event instanceof HideRowPositionsEvent) {
\r
2363 HideRowPositionsEvent e = (HideRowPositionsEvent)event;
\r
2364 for (Range r : e.getRowPositionRanges()) {
\r
2365 int collapsed = viewportLayer.getRowIndexByPosition(r.start-2)+1;
\r
2366 //System.out.println("col " + collapsed);
\r
2367 if (collapsed < 0) {
\r
2370 nodeStatusChanged(list.get(collapsed).getContext(), false);
\r
2377 protected boolean nodeStatusChanged(NodeContext context, boolean expanded) {
\r
2378 boolean result = _setExpanded(context, expanded);
\r
2379 PrimitiveQueryUpdater updater = expandedQueries.get(context);
\r
2380 if (updater != null)
\r
2381 updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded);
\r
2386 public void attached(GraphExplorer explorer) {
\r
2387 Object control = explorer.getControl();
\r
2388 if (control instanceof NatTable) {
\r
2389 this.natTable = (NatTable) control;
\r
2390 this.list = ((NatTableGraphExplorer)explorer).list;
\r
2391 natTable.addLayerListener(treeListener);
\r
2394 System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control);
\r
2399 public void clear() {
\r
2401 expandedQueries.clear();
\r
2405 public void detached(GraphExplorer explorer) {
\r
2407 if (natTable != null) {
\r
2408 natTable.removeLayerListener(treeListener);
\r
2409 // natTable.removeListener(SWT.Expand, treeListener);
\r
2410 // natTable.removeListener(SWT.Collapse, treeListener);
\r
2416 private void printTree(TreeNode node, int depth) {
\r
2418 for (int i = 0; i < depth; i++) {
\r
2422 System.out.println(s);
\r
2424 for (TreeNode n : node.getChildren()) {
\r
2431 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used)
\r
2433 final private static class GECacheKey {
\r
2435 private NodeContext context;
\r
2436 private CacheKey<?> key;
\r
2438 GECacheKey(NodeContext context, CacheKey<?> key) {
\r
2439 this.context = context;
\r
2441 if (context == null || key == null)
\r
2442 throw new IllegalArgumentException("Null context or key is not accepted");
\r
2445 GECacheKey(GECacheKey other) {
\r
2446 this.context = other.context;
\r
2447 this.key = other.key;
\r
2448 if (context == null || key == null)
\r
2449 throw new IllegalArgumentException("Null context or key is not accepted");
\r
2452 void setValues(NodeContext context, CacheKey<?> key) {
\r
2453 this.context = context;
\r
2455 if (context == null || key == null)
\r
2456 throw new IllegalArgumentException("Null context or key is not accepted");
\r
2460 public int hashCode() {
\r
2461 return context.hashCode() | key.hashCode();
\r
2465 public boolean equals(Object object) {
\r
2467 if (this == object)
\r
2469 else if (object == null)
\r
2472 GECacheKey i = (GECacheKey) object;
\r
2474 return key.equals(i.key) && context.equals(i.context);
\r
2481 * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data.
\r
2483 public static class GECache2 implements IGECache {
\r
2485 final HashMap<GECacheKey, IGECacheEntry> entries = new HashMap<GECacheKey, IGECacheEntry>();
\r
2486 final HashMap<GECacheKey, Set<UIElementReference>> treeReferences = new HashMap<GECacheKey, Set<UIElementReference>>();
\r
2487 final HashMap<NodeContext, Set<GECacheKey>> keyRefs = new HashMap<NodeContext, Set<GECacheKey>>();
\r
2490 * This single instance is used for all get operations from the cache. This
\r
2491 * should work since the GE cache is meant to be single-threaded within the
\r
2492 * current UI thread, what ever that thread is. For put operations which
\r
2493 * store the key, this is not used.
\r
2495 NodeContext getNC = new NodeContext() {
\r
2496 @SuppressWarnings("rawtypes")
\r
2498 public Object getAdapter(Class adapter) {
\r
2503 public <T> T getConstant(ConstantKey<T> key) {
\r
2508 public Set<ConstantKey<?>> getKeys() {
\r
2509 return Collections.emptySet();
\r
2512 CacheKey<?> getCK = new CacheKey<Object>() {
\r
2514 public Object processorIdenfitier() {
\r
2518 GECacheKey getKey = new GECacheKey(getNC, getCK);
\r
2521 private void addKey(GECacheKey key) {
\r
2522 Set<GECacheKey> refs = keyRefs.get(key.context);
\r
2523 if (refs != null) {
\r
2526 refs = new HashSet<GECacheKey>();
\r
2528 keyRefs.put(key.context, refs);
\r
2532 private void removeKey(GECacheKey key) {
\r
2533 Set<GECacheKey> refs = keyRefs.get(key.context);
\r
2534 if (refs != null) {
\r
2539 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key, T value) {
\r
2540 // if (DEBUG) System.out.println("Add entry " + context + " " + key);
\r
2541 IGECacheEntry entry = new GECacheEntry(context, key, value);
\r
2542 GECacheKey gekey = new GECacheKey(context, key);
\r
2543 entries.put(gekey, entry);
\r
2548 @SuppressWarnings("unchecked")
\r
2549 public <T> T get(NodeContext context, CacheKey<T> key) {
\r
2550 getKey.setValues(context, key);
\r
2551 IGECacheEntry entry = entries.get(getKey);
\r
2552 if (entry == null)
\r
2554 return (T) entry.getValue();
\r
2558 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
\r
2559 assert(context != null);
\r
2560 assert(key != null);
\r
2561 getKey.setValues(context, key);
\r
2562 return entries.get(getKey);
\r
2566 public <T> void remove(NodeContext context, CacheKey<T> key) {
\r
2567 // if (DEBUG) System.out.println("Remove entry " + context + " " + key);
\r
2568 getKey.setValues(context, key);
\r
2569 entries.remove(getKey);
\r
2570 removeKey(getKey);
\r
2574 public <T> Set<UIElementReference> getTreeReference(NodeContext context, CacheKey<T> key) {
\r
2575 assert(context != null);
\r
2576 assert(key != null);
\r
2577 getKey.setValues(context, key);
\r
2578 return treeReferences.get(getKey);
\r
2582 public <T> void putTreeReference(NodeContext context, CacheKey<T> key, UIElementReference reference) {
\r
2583 assert(context != null);
\r
2584 assert(key != null);
\r
2585 //if (DEBUG) System.out.println("Add tree reference " + context + " " + key);
\r
2586 getKey.setValues(context, key);
\r
2587 Set<UIElementReference> refs = treeReferences.get(getKey);
\r
2588 if (refs != null) {
\r
2589 refs.add(reference);
\r
2591 refs = new HashSet<UIElementReference>(4);
\r
2592 refs.add(reference);
\r
2593 GECacheKey gekey = new GECacheKey(getKey);
\r
2594 treeReferences.put(gekey, refs);
\r
2600 public <T> Set<UIElementReference> removeTreeReference(NodeContext context, CacheKey<T> key) {
\r
2601 assert(context != null);
\r
2602 assert(key != null);
\r
2603 //if (DEBUG) System.out.println("Remove tree reference " + context + " " + key);
\r
2604 getKey.setValues(context, key);
\r
2605 removeKey(getKey);
\r
2606 return treeReferences.remove(getKey);
\r
2610 public boolean isShown(NodeContext context) {
\r
2611 return references.get(context) > 0;
\r
2614 private TObjectIntHashMap<NodeContext> references = new TObjectIntHashMap<NodeContext>();
\r
2617 public void incRef(NodeContext context) {
\r
2618 int exist = references.get(context);
\r
2619 references.put(context, exist+1);
\r
2623 public void decRef(NodeContext context) {
\r
2624 int exist = references.get(context);
\r
2625 references.put(context, exist-1);
\r
2627 references.remove(context);
\r
2631 public void dispose() {
\r
2632 references.clear();
\r
2634 treeReferences.clear();
\r
2638 public void dispose(NodeContext context) {
\r
2639 Set<GECacheKey> keys = keyRefs.remove(context);
\r
2640 if (keys != null) {
\r
2641 for (GECacheKey key : keys) {
\r
2642 entries.remove(key);
\r
2643 treeReferences.remove(key);
\r
2651 * Non-functional cache to replace actual cache when GEContext is disposed.
\r
2656 private static class DummyCache extends GECache2 {
\r
2659 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
\r
2664 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key,
\r
2670 public <T> void putTreeReference(NodeContext context, CacheKey<T> key,
\r
2671 UIElementReference reference) {
\r
2675 public <T> T get(NodeContext context, CacheKey<T> key) {
\r
2680 public <T> Set<UIElementReference> getTreeReference(
\r
2681 NodeContext context, CacheKey<T> key) {
\r
2686 public <T> void remove(NodeContext context, CacheKey<T> key) {
\r
2691 public <T> Set<UIElementReference> removeTreeReference(
\r
2692 NodeContext context, CacheKey<T> key) {
\r
2697 public boolean isShown(NodeContext context) {
\r
2702 public void incRef(NodeContext context) {
\r
2707 public void decRef(NodeContext context) {
\r
2712 public void dispose() {
\r
2717 private class NatTableHeaderMenuConfiguration extends AbstractHeaderMenuConfiguration {
\r
2720 public NatTableHeaderMenuConfiguration(NatTable natTable) {
\r
2725 protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) {
\r
2726 return super.createColumnHeaderMenu(natTable)
\r
2727 .withHideColumnMenuItem()
\r
2728 .withShowAllColumnsMenuItem()
\r
2729 .withAutoResizeSelectedColumnsMenuItem();
\r
2733 protected PopupMenuBuilder createCornerMenu(NatTable natTable) {
\r
2734 return super.createCornerMenu(natTable)
\r
2735 .withShowAllColumnsMenuItem();
\r
2738 protected PopupMenuBuilder createRowHeaderMenu(NatTable natTable) {
\r
2739 return super.createRowHeaderMenu(natTable);
\r
2743 private static class RelativeAlternatingRowConfigLabelAccumulator extends AlternatingRowConfigLabelAccumulator {
\r
2746 public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) {
\r
2747 configLabels.addLabel((rowPosition % 2 == 0 ? EVEN_ROW_CONFIG_TYPE : ODD_ROW_CONFIG_TYPE));
\r
2752 public Object getClicked(Object event) {
\r
2753 MouseEvent e = (MouseEvent)event;
\r
2754 final NatTable tree = (NatTable) e.getSource();
\r
2755 Point point = new Point(e.x, e.y);
\r
2756 int y = natTable.getRowPositionByY(point.y);
\r
2757 int x = natTable.getColumnPositionByX(point.x);
\r
2758 if (x < 0 | y < 0)
\r
2760 return list.get(y);
\r