]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerViewBase.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.browsing.ui.swt / src / org / simantics / browsing / ui / swt / GraphExplorerViewBase.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
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
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.browsing.ui.swt;
13
14 import java.util.Map;
15
16 import org.eclipse.jface.action.IMenuManager;
17 import org.eclipse.jface.layout.GridDataFactory;
18 import org.eclipse.jface.resource.JFaceResources;
19 import org.eclipse.jface.resource.LocalResourceManager;
20 import org.eclipse.jface.viewers.ISelectionProvider;
21 import org.eclipse.swt.SWT;
22 import org.eclipse.swt.widgets.Composite;
23 import org.eclipse.swt.widgets.Control;
24 import org.eclipse.ui.IMemento;
25 import org.eclipse.ui.ISelectionListener;
26 import org.eclipse.ui.IViewSite;
27 import org.eclipse.ui.IWorkbenchPart;
28 import org.eclipse.ui.PartInitException;
29 import org.eclipse.ui.part.ViewPart;
30 import org.eclipse.ui.services.IDisposable;
31 import org.simantics.browsing.ui.Column;
32 import org.simantics.browsing.ui.GraphExplorer;
33 import org.simantics.browsing.ui.NodeContext;
34 import org.simantics.browsing.ui.common.ColumnKeys;
35 import org.simantics.browsing.ui.common.views.IViewArguments;
36 import org.simantics.browsing.ui.graph.impl.SessionContextInputSource;
37 import org.simantics.db.Session;
38 import org.simantics.db.common.request.Queries;
39 import org.simantics.db.exception.DatabaseException;
40 import org.simantics.db.management.ISessionContext;
41 import org.simantics.db.management.ISessionContextChangedListener;
42 import org.simantics.db.management.ISessionContextProvider;
43 import org.simantics.db.management.SessionContextChangedEvent;
44 import org.simantics.project.IProject;
45 import org.simantics.project.ProjectKeys;
46 import org.simantics.ui.SimanticsUI;
47 import org.simantics.ui.dnd.BasicDragSource;
48 import org.simantics.ui.dnd.SessionContainer;
49 import org.simantics.ui.workbench.IPropertyPage;
50 import org.simantics.utils.ObjectUtils;
51 import org.simantics.utils.datastructures.Function;
52 import org.simantics.utils.datastructures.disposable.DisposeState;
53 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
54 import org.simantics.utils.datastructures.hints.HintTracker;
55 import org.simantics.utils.datastructures.hints.IHintContext.Key;
56 import org.simantics.utils.datastructures.hints.IHintListener;
57 import org.simantics.utils.datastructures.hints.IHintObservable;
58 import org.simantics.utils.datastructures.hints.IHintTracker;
59 import org.simantics.utils.ui.LayoutUtils;
60
61 /**
62  * An abstract base Eclipse workbench ViewPart for use in situations where a
63  * tree-based GraphExplorer graph model browser is needed.
64  * 
65  * <p>
66  * Override to customize behavior:
67  * <ul>
68  * <li>{@link #createControls(Composite)}</li>
69  * <li>{@link #createExplorerControl(Composite)}</li>
70  * <li>{@link #createDragSource(GraphExplorer)}</li>
71  * <li>{@link #getContextMenuId()}</li>
72  * <li>{@link #getContextMenuInitializer()}</li>
73  * <li>{@link #addListeners(GraphExplorer, IMenuManager)}</li>
74  * </ul>
75  * </p>
76  * 
77  * <p>
78  * You can invoke the following methods from within
79  * {@link #createControls(Composite)} to customize how the view keeps track of
80  * the active ISessionContext and how the view resolves the input object of the
81  * GraphExplorer control.
82  * <ul>
83  * <li>{@link #setSessionContextTracker(IHintTracker)}</li>
84  * <li>{@link #setInputSource(SessionContextInputSource)}</li>
85  * </ul>
86  * </p>
87  * 
88  * @author Tuukka Lehtonen
89  * @deprecated in favor of org.simantics.views.swt.ModelledView 
90  */
91 public abstract class GraphExplorerViewBase extends ViewPart {
92
93     /**
94      * The default hint tracker that will be active if
95      * {@link GraphExplorerViewBase#setSessionContextTracker(IHintTracker) is
96      * not called.
97      */
98     public class SessionContextProjectTracker extends HintTracker {
99         public SessionContextProjectTracker() {
100             IHintListener activeProjectListener = new HintListenerAdapter() {
101                 @Override
102                 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
103                     applySessionContext(getSessionContext());
104                 }
105             };
106             addKeyHintListener(ProjectKeys.KEY_PROJECT, activeProjectListener);
107         }
108     }
109
110     /**
111      * The default implementation of a {@link SessionContextInputSource} that
112      * will be active if
113      * {@link GraphExplorerViewBase#setInputSource(SessionContextInputSource)}
114      * is not called.
115      */
116     public class SessionContextProjectSource implements SessionContextInputSource {
117         /**
118          * Returns the input object used by this GraphExplorer view. This object
119          * will be the starting point for all content shown in this explorer tree.
120          * 
121          * @param sessionContext a valid database session context
122          * @return the root object of the graph explorer tree or <code>null</code>
123          *         to indicate that there is no input and nothing should be shown.
124          */
125         @Override
126         public Object get(ISessionContext ctx) {
127             if (ctx == null)
128                 return GraphExplorer.EMPTY_INPUT;
129
130             String inputId = getViewArguments().get(IViewArguments.INPUT);
131             if (inputId != null) {
132                 try {
133                     return ctx.getSession().syncRequest(Queries.resource(inputId));
134                 } catch (DatabaseException e) {
135                     // Ok, the view argument was invalid. Just continue to the next
136                     // method.
137                 }
138             }
139
140             Object input = GraphExplorer.EMPTY_INPUT;
141             IProject project2 = ctx.getHint(ProjectKeys.KEY_PROJECT);
142             if (project2 != null)
143                 input = project2.get();
144             return input;
145         }
146         
147         @Override
148         public IWorkbenchPart getProvider() {
149                 return null;
150         }
151         
152     }
153
154     protected LocalResourceManager           resourceManager;
155
156     protected ISelectionListener             workbenchSelectionListener;
157
158     protected Composite                      parent;
159
160     protected GraphExplorer                 explorer;
161
162     protected IMenuManager                   menuManager;
163
164     private Map<String, String>              args;
165
166     private ISessionContextProvider          contextProvider;
167
168     private ISessionContext                  sessionContext;
169
170     private Object                           dragSource;
171
172     protected IMemento                       memento;
173
174     private IHintTracker                     sessionContextTracker = new SessionContextProjectTracker();
175
176     private SessionContextInputSource        inputSource           = new SessionContextProjectSource();
177
178     private DisposeState                     disposeState          = DisposeState.Alive;
179
180     protected ISessionContextChangedListener contextChangeListener = new ISessionContextChangedListener() {
181         @Override
182         public void sessionContextChanged(SessionContextChangedEvent event) {
183             sessionContext = event.getNewValue();
184             sessionContextTracker.track(sessionContext);
185         }
186     };
187
188     protected void setSessionContextTracker(IHintTracker tracker) {
189         this.sessionContextTracker = tracker;
190     }
191
192     protected void setInputSource(SessionContextInputSource source) {
193         this.inputSource = source;
194     }
195
196     protected SessionContextInputSource getInputSource() {
197         return inputSource;
198     }
199
200     protected Map<String, String> getViewArguments() {
201         return args;
202     }
203
204     protected DisposeState getDisposeState() {
205         return disposeState;
206     }
207
208     public ISessionContext getSessionContext() {
209         return sessionContext;
210     }
211
212     public ISessionContextProvider getSessionContextProvider() {
213         return contextProvider;
214     }
215
216     @Override
217     public void createPartControl(Composite parent) {
218         this.parent = parent;
219         this.resourceManager = new LocalResourceManager(JFaceResources.getResources(parent.getDisplay()));
220
221         contextProvider = SimanticsUI.getSessionContextProvider(getViewSite().getWorkbenchWindow());
222         createControls(parent);
223         attachToSession();
224     }
225
226     /**
227      * Invoked when this viewpart is disposed. Unhooks the view from its
228      * ISessionContextProvider. Overriding is allowed but super.dispose() must
229      * be called.
230      * 
231      * @see org.eclipse.ui.part.WorkbenchPart#dispose()
232      */
233     @Override
234     public void dispose() {
235         disposeState = DisposeState.Disposing;
236         try {
237             //System.out.println(this + ".GraphExplorerViewBase.dispose()");
238             if (contextProvider != null) {
239                 contextProvider.removeContextChangedListener(contextChangeListener);
240                 contextProvider = null;
241             }
242             sessionContextTracker.untrack();
243             resourceManager.dispose();
244             resourceManager = null;
245             args = null;
246             explorer = null;
247             sessionContext = null;
248             dragSource = null;
249             parent = null;
250             super.dispose();
251         } finally {
252             disposeState = DisposeState.Disposed;
253         }
254     }
255
256     @Override
257     public void setFocus() {
258         if (explorer != null && !explorer.isDisposed())
259             explorer.setFocus();
260     }
261
262     @Override
263     public void init(IViewSite site) throws PartInitException {
264         super.init(site);
265         this.args = ViewArgumentUtils.parseViewArguments(this);
266     }
267
268     @Override
269     public void init(IViewSite site, IMemento memento) throws PartInitException {
270         super.init(site, memento);
271         this.args = ViewArgumentUtils.parseViewArguments(this);
272         this.memento = memento;
273     }
274
275     @Override
276     public void saveState(IMemento memento) {
277         if (this.memento != null) {
278             memento.putMemento(this.memento);
279         }
280 //        if (explorer != null)
281 //            explorer.saveState(memento);
282     }
283
284     protected void setWorkbenchListeners() {
285         if (workbenchSelectionListener == null) {
286
287             ISelectionProvider selectionProvider = (ISelectionProvider)explorer.getAdapter(ISelectionProvider.class);
288
289             getSite().setSelectionProvider(selectionProvider);
290
291             // Listen to the workbench selection also to propagate it to
292             // the explorer also.
293             workbenchSelectionListener = new DefaultExplorerSelectionListener(this, explorer);
294             getSite().getWorkbenchWindow().getSelectionService().addPostSelectionListener(workbenchSelectionListener);
295         }
296     }
297
298     protected void removeWorkbenchListeners() {
299         // Remember to remove the installed workbench selection listener
300         if (workbenchSelectionListener != null) {
301             getSite().getWorkbenchWindow().getSelectionService().removePostSelectionListener(workbenchSelectionListener);
302             if (workbenchSelectionListener instanceof IDisposable)
303                 ((IDisposable) workbenchSelectionListener).dispose();
304             workbenchSelectionListener = null;
305
306             getSite().setSelectionProvider(null);
307         }
308     }
309
310     protected final void attachToSession() {
311         // Track active ISessionContext changes
312         //contextProvider = SimanticsUI.getSessionContextProvider(getViewSite().getWorkbenchWindow());
313         contextProvider.addContextChangedListener(contextChangeListener);
314
315         // Start tracking the current session context for input changes.
316         // This will/must cause applySessionContext to get called.
317         // Doing the applySessionContext initialization this way
318         // instead of directly calling it will also make sure that
319         // applySessionContext is only called once when first initialized,
320         // and not twice like with the direct invocation.
321         this.sessionContext = contextProvider.getSessionContext();
322         sessionContextTracker.track(sessionContext);
323     }
324
325     // /////////////////////////////////////////////////////////////////////////
326     // Override / implement these:
327
328 //    /**
329 //     * Returns an ID that is used for persisting a GraphExplorer instance.
330 //     *
331 //     * Used for </code>restoreState(IMemento)</code> and
332 //     * <code>restoreState(IMemento)</code> in OntologyExplorer. Must be unique
333 //     * within a workbench part.
334 //     *
335 //     * @return a unique name for this particular graph explorer view used for
336 //     *         saving and restoring the state of this view part
337 //     */
338 //    public String getExplorerName() {
339 //        return "GraphExplorerViewBase";
340 //    }
341
342     protected Column[] getColumns() {
343         return null;
344     }
345
346     /**
347      * Override this method to add controls to the view part. This is invoked
348      * before attaching the view part to a database session.
349      * 
350      * @param parent
351      */
352     protected void createControls(Composite parent) {
353
354         parent.setLayout(LayoutUtils.createNoBorderGridLayout(1, false));
355
356         // Initialize explorer control.
357         explorer = createExplorerControl(parent);
358
359         ISelectionProvider selectionProvider = (ISelectionProvider)explorer.getAdapter(ISelectionProvider.class);
360         Control control = explorer.getControl();
361
362         Column[] columns = getColumns();
363         if(columns != null)
364             explorer.setColumns(columns);
365
366         GridDataFactory.fillDefaults().grab(true, true).applyTo(control);
367
368         // Initialize context menu if an initializer is provided.
369         IContextMenuInitializer cmi = getContextMenuInitializer();
370         if (cmi != null) {
371             menuManager = cmi.createContextMenu(control, selectionProvider, getSite());
372         }
373
374         // Initialize DND.
375         dragSource = createDragSource(explorer);
376
377         // Listeners are only added once per listener, not every time the
378         // session context changes.
379         addListeners(explorer, menuManager);
380     }
381
382     /**
383      * Override this method and provide a proper context menu initializer if you
384      * want to have this base class initialize one for you.
385      * 
386      * @return the initializer to be used by {@link #createControls(Composite)}
387      */
388     protected IContextMenuInitializer getContextMenuInitializer() {
389         String contextMenuId = getContextMenuId();
390         if(contextMenuId != null) {
391             return new ContextMenuInitializer(contextMenuId);
392         } else {
393             return null;
394         }
395     }
396
397     /**
398      * @return the ID of the context menu to initialize for this this graph
399      *         explorer view or <code>null</code> to not initialize a context
400      *         menu
401      */
402     protected String getContextMenuId() {
403         return null;
404     }
405
406     protected int getStyle() {
407         return SWT.MULTI;
408     }
409
410     /**
411      * @param parent
412      * @return
413      */
414     protected GraphExplorer createExplorerControl(Composite parent) {
415         return GraphExplorerFactory.getInstance()
416         .selectionDataResolver(new DefaultSelectionDataResolver())
417         .create(parent, getStyle());
418     }
419
420     /**
421      * Override to customize drag source initialization. This default
422      * implementation creates a {@link BasicDragSource}. The drag source is
423      * initialized when the active database session is set.
424      * 
425      * @param explorer
426      * @return the object representing the drag source. If the object implements
427      *         {@link SessionContainer}, its
428      *         {@link SessionContainer#setSession(Session)} will be invoked
429      *         every time the active database session changes.
430      */
431     protected Object createDragSource(GraphExplorer explorer) {
432         ISelectionProvider selectionProvider = (ISelectionProvider)explorer.getAdapter(ISelectionProvider.class);
433         Control control = explorer.getControl();
434         return new BasicDragSource(selectionProvider, control, null);
435     }
436
437     protected void setupDragSource(Session session) {
438         if (dragSource instanceof SessionContainer) {
439             ((SessionContainer) dragSource).setSession(session);
440         }
441     }
442
443     /**
444      * Override to customize the addition of listeners a newly created
445      * GraphExplorer.
446      * 
447      * @param explorer
448      */
449     protected void addListeners(GraphExplorer explorer, IMenuManager menuManager) {
450         addSelectionInputListeners(explorer, menuManager);
451     }
452
453     protected void addSelectionInputListeners(GraphExplorer explorer, IMenuManager menuManager) {
454         // Consider ENTER presses to simulate mouse left button double clicks
455         explorer.addListener(new DefaultKeyListener(contextProvider, explorer, new Function<String[]>() {
456             @Override
457             public String[] execute(Object... obj) {
458                 return new String[] { getEditingColumn((NodeContext) obj[0]) };
459             }
460         }));
461         // Default double click handling
462         explorer.addListener(new DefaultMouseListener(explorer));
463     }
464
465     protected String getEditingColumn(NodeContext context) {
466         return ColumnKeys.SINGLE;
467     }
468
469     /**
470      * Override to customize the initialization of the content provision and
471      * presentation of a GraphExplorer. This is called every time the input
472      * database session changes.
473      * 
474      * @param explorer
475      * @param context may be <code>null</code> if there is no session
476      */
477     protected void initializeExplorer(final GraphExplorer explorer, ISessionContext context) {
478         setupDragSource((context != null) ? context.getSession() : null);
479     }
480
481     // Needed for preventing unnecessary re-initialization of the explorer with the same input.
482     private Object currentInput;
483
484     protected boolean isImportantInput(Object previousInput, Object input) {
485         return !ObjectUtils.objectEquals(previousInput, input);
486     }
487
488     /**
489      * Invoke this to reinitialize the explorer and reset its input. The input
490      * will be resolved from the specified ISessionContext based on the
491      * {@link SessionContextInputSource} that is currently in use. If the input
492      * is identical to the previous input, nothing will be done.
493      * 
494      * @param context
495      */
496     protected final boolean applySessionContext(ISessionContext context) {
497         // If control is not alive anymore, do nothing.
498 //        System.out.println(this + ": applySessionContext(" + context + "), explorer="  + explorer);
499         if (disposeState != DisposeState.Alive)
500             return false;
501
502         this.sessionContext = context;
503         Object input = inputSource.get(context);
504         if (!isImportantInput(currentInput, input))
505             return false;
506
507 //        System.out.println(this + ": initializeExplorer(" + explorer + ", " + context + ")");
508         initializeExplorer(explorer, context);
509 //        System.out.println(this + ": setRoot(" + input + ")");
510         explorer.setRoot(input);
511
512         currentInput = input;
513
514         // Start tracking the session context.
515         //
516         // If this is not the same session that is currently tracked, it will
517         // cause IHintListeners of the sessionContextTracker to fire.
518         // For this we need the above input equality (identity) checking.
519         // This is here just to make sure that we are tracking the correct
520         // session context.
521         sessionContextTracker.track(sessionContext);
522
523         return true;
524     }
525
526     @SuppressWarnings("unchecked")
527     @Override
528     public <T> T getAdapter(Class<T> adapter) {
529
530         if (GraphExplorer.class == adapter)
531             return (T) explorer;
532         else if(ISessionContextProvider.class == adapter)
533             return (T) getSessionContextProvider();
534         else if(IPropertyPage.class == adapter)
535             return (T) getPropertyPage();
536
537         return super.getAdapter(adapter);
538
539     }
540
541     protected IPropertyPage getPropertyPage() {
542         return null;
543     }
544
545
546
547 }