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