1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.browsing.ui.swt;
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;
62 * An abstract base Eclipse workbench ViewPart for use in situations where a
63 * tree-based GraphExplorer graph model browser is needed.
66 * Override to customize behavior:
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>
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.
83 * <li>{@link #setSessionContextTracker(IHintTracker)}</li>
84 * <li>{@link #setInputSource(SessionContextInputSource)}</li>
88 * @author Tuukka Lehtonen
89 * @deprecated in favor of org.simantics.views.swt.ModelledView
91 public abstract class GraphExplorerViewBase extends ViewPart {
94 * The default hint tracker that will be active if
95 * {@link GraphExplorerViewBase#setSessionContextTracker(IHintTracker) is
98 public class SessionContextProjectTracker extends HintTracker {
99 public SessionContextProjectTracker() {
100 IHintListener activeProjectListener = new HintListenerAdapter() {
102 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
103 applySessionContext(getSessionContext());
106 addKeyHintListener(ProjectKeys.KEY_PROJECT, activeProjectListener);
111 * The default implementation of a {@link SessionContextInputSource} that
113 * {@link GraphExplorerViewBase#setInputSource(SessionContextInputSource)}
116 public class SessionContextProjectSource implements SessionContextInputSource {
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.
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.
126 public Object get(ISessionContext ctx) {
128 return GraphExplorer.EMPTY_INPUT;
130 String inputId = getViewArguments().get(IViewArguments.INPUT);
131 if (inputId != null) {
133 return ctx.getSession().syncRequest(Queries.resource(inputId));
134 } catch (DatabaseException e) {
135 // Ok, the view argument was invalid. Just continue to the next
140 Object input = GraphExplorer.EMPTY_INPUT;
141 IProject project2 = ctx.getHint(ProjectKeys.KEY_PROJECT);
142 if (project2 != null)
143 input = project2.get();
148 public IWorkbenchPart getProvider() {
154 protected LocalResourceManager resourceManager;
156 protected ISelectionListener workbenchSelectionListener;
158 protected Composite parent;
160 protected GraphExplorer explorer;
162 protected IMenuManager menuManager;
164 private Map<String, String> args;
166 private ISessionContextProvider contextProvider;
168 private ISessionContext sessionContext;
170 private Object dragSource;
172 protected IMemento memento;
174 private IHintTracker sessionContextTracker = new SessionContextProjectTracker();
176 private SessionContextInputSource inputSource = new SessionContextProjectSource();
178 private DisposeState disposeState = DisposeState.Alive;
180 protected ISessionContextChangedListener contextChangeListener = new ISessionContextChangedListener() {
182 public void sessionContextChanged(SessionContextChangedEvent event) {
183 sessionContext = event.getNewValue();
184 sessionContextTracker.track(sessionContext);
188 protected void setSessionContextTracker(IHintTracker tracker) {
189 this.sessionContextTracker = tracker;
192 protected void setInputSource(SessionContextInputSource source) {
193 this.inputSource = source;
196 protected SessionContextInputSource getInputSource() {
200 protected Map<String, String> getViewArguments() {
204 protected DisposeState getDisposeState() {
208 public ISessionContext getSessionContext() {
209 return sessionContext;
212 public ISessionContextProvider getSessionContextProvider() {
213 return contextProvider;
217 public void createPartControl(Composite parent) {
218 this.parent = parent;
219 this.resourceManager = new LocalResourceManager(JFaceResources.getResources(parent.getDisplay()));
221 contextProvider = SimanticsUI.getSessionContextProvider(getViewSite().getWorkbenchWindow());
222 createControls(parent);
227 * Invoked when this viewpart is disposed. Unhooks the view from its
228 * ISessionContextProvider. Overriding is allowed but super.dispose() must
231 * @see org.eclipse.ui.part.WorkbenchPart#dispose()
234 public void dispose() {
235 disposeState = DisposeState.Disposing;
237 //System.out.println(this + ".GraphExplorerViewBase.dispose()");
238 if (contextProvider != null) {
239 contextProvider.removeContextChangedListener(contextChangeListener);
240 contextProvider = null;
242 sessionContextTracker.untrack();
243 resourceManager.dispose();
244 resourceManager = null;
247 sessionContext = null;
252 disposeState = DisposeState.Disposed;
257 public void setFocus() {
258 if (explorer != null && !explorer.isDisposed())
263 public void init(IViewSite site) throws PartInitException {
265 this.args = ViewArgumentUtils.parseViewArguments(this);
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;
276 public void saveState(IMemento memento) {
277 if (this.memento != null) {
278 memento.putMemento(this.memento);
280 // if (explorer != null)
281 // explorer.saveState(memento);
284 protected void setWorkbenchListeners() {
285 if (workbenchSelectionListener == null) {
287 ISelectionProvider selectionProvider = (ISelectionProvider)explorer.getAdapter(ISelectionProvider.class);
289 getSite().setSelectionProvider(selectionProvider);
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);
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;
306 getSite().setSelectionProvider(null);
310 protected final void attachToSession() {
311 // Track active ISessionContext changes
312 //contextProvider = SimanticsUI.getSessionContextProvider(getViewSite().getWorkbenchWindow());
313 contextProvider.addContextChangedListener(contextChangeListener);
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);
325 // /////////////////////////////////////////////////////////////////////////
326 // Override / implement these:
329 // * Returns an ID that is used for persisting a GraphExplorer instance.
331 // * Used for </code>restoreState(IMemento)</code> and
332 // * <code>restoreState(IMemento)</code> in OntologyExplorer. Must be unique
333 // * within a workbench part.
335 // * @return a unique name for this particular graph explorer view used for
336 // * saving and restoring the state of this view part
338 // public String getExplorerName() {
339 // return "GraphExplorerViewBase";
342 protected Column[] getColumns() {
347 * Override this method to add controls to the view part. This is invoked
348 * before attaching the view part to a database session.
352 protected void createControls(Composite parent) {
354 parent.setLayout(LayoutUtils.createNoBorderGridLayout(1, false));
356 // Initialize explorer control.
357 explorer = createExplorerControl(parent);
359 ISelectionProvider selectionProvider = (ISelectionProvider)explorer.getAdapter(ISelectionProvider.class);
360 Control control = explorer.getControl();
362 Column[] columns = getColumns();
364 explorer.setColumns(columns);
366 GridDataFactory.fillDefaults().grab(true, true).applyTo(control);
368 // Initialize context menu if an initializer is provided.
369 IContextMenuInitializer cmi = getContextMenuInitializer();
371 menuManager = cmi.createContextMenu(control, selectionProvider, getSite());
375 dragSource = createDragSource(explorer);
377 // Listeners are only added once per listener, not every time the
378 // session context changes.
379 addListeners(explorer, menuManager);
383 * Override this method and provide a proper context menu initializer if you
384 * want to have this base class initialize one for you.
386 * @return the initializer to be used by {@link #createControls(Composite)}
388 protected IContextMenuInitializer getContextMenuInitializer() {
389 String contextMenuId = getContextMenuId();
390 if(contextMenuId != null) {
391 return new ContextMenuInitializer(contextMenuId);
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
402 protected String getContextMenuId() {
406 protected int getStyle() {
414 protected GraphExplorer createExplorerControl(Composite parent) {
415 return GraphExplorerFactory.getInstance()
416 .selectionDataResolver(new DefaultSelectionDataResolver())
417 .create(parent, getStyle());
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.
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.
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);
437 protected void setupDragSource(Session session) {
438 if (dragSource instanceof SessionContainer) {
439 ((SessionContainer) dragSource).setSession(session);
444 * Override to customize the addition of listeners a newly created
449 protected void addListeners(GraphExplorer explorer, IMenuManager menuManager) {
450 addSelectionInputListeners(explorer, menuManager);
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[]>() {
457 public String[] execute(Object... obj) {
458 return new String[] { getEditingColumn((NodeContext) obj[0]) };
461 // Default double click handling
462 explorer.addListener(new DefaultMouseListener(explorer));
465 protected String getEditingColumn(NodeContext context) {
466 return ColumnKeys.SINGLE;
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.
475 * @param context may be <code>null</code> if there is no session
477 protected void initializeExplorer(final GraphExplorer explorer, ISessionContext context) {
478 setupDragSource((context != null) ? context.getSession() : null);
481 // Needed for preventing unnecessary re-initialization of the explorer with the same input.
482 private Object currentInput;
484 protected boolean isImportantInput(Object previousInput, Object input) {
485 return !ObjectUtils.objectEquals(previousInput, input);
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.
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)
502 this.sessionContext = context;
503 Object input = inputSource.get(context);
504 if (!isImportantInput(currentInput, input))
507 // System.out.println(this + ": initializeExplorer(" + explorer + ", " + context + ")");
508 initializeExplorer(explorer, context);
509 // System.out.println(this + ": setRoot(" + input + ")");
510 explorer.setRoot(input);
512 currentInput = input;
514 // Start tracking the session context.
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
521 sessionContextTracker.track(sessionContext);
526 @SuppressWarnings("unchecked")
528 public <T> T getAdapter(Class<T> adapter) {
530 if (GraphExplorer.class == adapter)
532 else if(ISessionContextProvider.class == adapter)
533 return (T) getSessionContextProvider();
534 else if(IPropertyPage.class == adapter)
535 return (T) getPropertyPage();
537 return super.getAdapter(adapter);
541 protected IPropertyPage getPropertyPage() {