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