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
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.browsing.ui.swt;
\r
14 import java.util.Map;
\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
61 * An abstract base Eclipse workbench ViewPart for use in situations where a
\r
62 * tree-based GraphExplorer graph model browser is needed.
\r
65 * Override to customize behavior:
\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
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
82 * <li>{@link #setSessionContextTracker(IHintTracker)}</li>
\r
83 * <li>{@link #setInputSource(SessionContextInputSource)}</li>
\r
87 * @author Tuukka Lehtonen
\r
88 * @deprecated in favor of org.simantics.views.swt.ModelledView
\r
90 public abstract class GraphExplorerViewBase extends ViewPart {
\r
93 * The default hint tracker that will be active if
\r
94 * {@link GraphExplorerViewBase#setSessionContextTracker(IHintTracker) is
\r
97 public class SessionContextProjectTracker extends HintTracker {
\r
98 public SessionContextProjectTracker() {
\r
99 IHintListener activeProjectListener = new HintListenerAdapter() {
\r
101 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
\r
102 applySessionContext(getSessionContext());
\r
105 addKeyHintListener(ProjectKeys.KEY_PROJECT, activeProjectListener);
\r
110 * The default implementation of a {@link SessionContextInputSource} that
\r
111 * will be active if
\r
112 * {@link GraphExplorerViewBase#setInputSource(SessionContextInputSource)}
\r
115 public class SessionContextProjectSource implements SessionContextInputSource {
\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
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
125 public Object get(ISessionContext ctx) {
\r
127 return GraphExplorer.EMPTY_INPUT;
\r
129 String inputId = getViewArguments().get(IViewArguments.INPUT);
\r
130 if (inputId != null) {
\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
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
147 public IWorkbenchPart getProvider() {
\r
153 protected LocalResourceManager resourceManager;
\r
155 protected ISelectionListener workbenchSelectionListener;
\r
157 protected Composite parent;
\r
159 protected GraphExplorer explorer;
\r
161 protected IMenuManager menuManager;
\r
163 private Map<String, String> args;
\r
165 private ISessionContextProvider contextProvider;
\r
167 private ISessionContext sessionContext;
\r
169 private Object dragSource;
\r
171 protected IMemento memento;
\r
173 private IHintTracker sessionContextTracker = new SessionContextProjectTracker();
\r
175 private SessionContextInputSource inputSource = new SessionContextProjectSource();
\r
177 private DisposeState disposeState = DisposeState.Alive;
\r
179 protected ISessionContextChangedListener contextChangeListener = new ISessionContextChangedListener() {
\r
181 public void sessionContextChanged(SessionContextChangedEvent event) {
\r
182 sessionContext = event.getNewValue();
\r
183 sessionContextTracker.track(sessionContext);
\r
187 protected void setSessionContextTracker(IHintTracker tracker) {
\r
188 this.sessionContextTracker = tracker;
\r
191 protected void setInputSource(SessionContextInputSource source) {
\r
192 this.inputSource = source;
\r
195 protected SessionContextInputSource getInputSource() {
\r
196 return inputSource;
\r
199 protected Map<String, String> getViewArguments() {
\r
203 protected DisposeState getDisposeState() {
\r
204 return disposeState;
\r
207 public ISessionContext getSessionContext() {
\r
208 return sessionContext;
\r
211 public ISessionContextProvider getSessionContextProvider() {
\r
212 return contextProvider;
\r
216 public void createPartControl(Composite parent) {
\r
217 this.parent = parent;
\r
218 this.resourceManager = new LocalResourceManager(JFaceResources.getResources(parent.getDisplay()));
\r
220 contextProvider = SimanticsUI.getSessionContextProvider(getViewSite().getWorkbenchWindow());
\r
221 createControls(parent);
\r
226 * Invoked when this viewpart is disposed. Unhooks the view from its
\r
227 * ISessionContextProvider. Overriding is allowed but super.dispose() must
\r
230 * @see org.eclipse.ui.part.WorkbenchPart#dispose()
\r
233 public void dispose() {
\r
234 disposeState = DisposeState.Disposing;
\r
236 //System.out.println(this + ".GraphExplorerViewBase.dispose()");
\r
237 if (contextProvider != null) {
\r
238 contextProvider.removeContextChangedListener(contextChangeListener);
\r
239 contextProvider = null;
\r
241 sessionContextTracker.untrack();
\r
242 resourceManager.dispose();
\r
243 resourceManager = null;
\r
246 sessionContext = null;
\r
251 disposeState = DisposeState.Disposed;
\r
256 public void setFocus() {
\r
257 if (explorer != null && !explorer.isDisposed())
\r
258 explorer.setFocus();
\r
262 public void init(IViewSite site) throws PartInitException {
\r
264 this.args = ViewArgumentUtils.parseViewArguments(this);
\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
275 public void saveState(IMemento memento) {
\r
276 if (this.memento != null) {
\r
277 memento.putMemento(this.memento);
\r
279 // if (explorer != null)
\r
280 // explorer.saveState(memento);
\r
283 protected void setWorkbenchListeners() {
\r
284 if (workbenchSelectionListener == null) {
\r
286 ISelectionProvider selectionProvider = (ISelectionProvider)explorer.getAdapter(ISelectionProvider.class);
\r
288 getSite().setSelectionProvider(selectionProvider);
\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
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
303 getSite().setSelectionProvider(null);
\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
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
322 // /////////////////////////////////////////////////////////////////////////
\r
323 // Override / implement these:
\r
326 // * Returns an ID that is used for persisting a GraphExplorer instance.
\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
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
335 // public String getExplorerName() {
\r
336 // return "GraphExplorerViewBase";
\r
339 protected Column[] getColumns() {
\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
349 protected void createControls(Composite parent) {
\r
351 parent.setLayout(LayoutUtils.createNoBorderGridLayout(1, false));
\r
353 // Initialize explorer control.
\r
354 explorer = createExplorerControl(parent);
\r
356 ISelectionProvider selectionProvider = (ISelectionProvider)explorer.getAdapter(ISelectionProvider.class);
\r
357 Control control = explorer.getControl();
\r
359 Column[] columns = getColumns();
\r
360 if(columns != null)
\r
361 explorer.setColumns(columns);
\r
363 GridDataFactory.fillDefaults().grab(true, true).applyTo(control);
\r
365 // Initialize context menu if an initializer is provided.
\r
366 IContextMenuInitializer cmi = getContextMenuInitializer();
\r
368 menuManager = cmi.createContextMenu(control, selectionProvider, getSite());
\r
372 dragSource = createDragSource(explorer);
\r
374 // Listeners are only added once per listener, not every time the
\r
375 // session context changes.
\r
376 addListeners(explorer, menuManager);
\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
383 * @return the initializer to be used by {@link #createControls(Composite)}
\r
385 protected IContextMenuInitializer getContextMenuInitializer() {
\r
386 String contextMenuId = getContextMenuId();
\r
387 if(contextMenuId != null) {
\r
388 return new ContextMenuInitializer(contextMenuId);
\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
399 protected String getContextMenuId() {
\r
403 protected int getStyle() {
\r
411 protected GraphExplorer createExplorerControl(Composite parent) {
\r
412 return GraphExplorerFactory.getInstance()
\r
413 .selectionDataResolver(new DefaultSelectionDataResolver())
\r
414 .create(parent, getStyle());
\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
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
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
434 protected void setupDragSource(Session session) {
\r
435 if (dragSource instanceof SessionContainer) {
\r
436 ((SessionContainer) dragSource).setSession(session);
\r
441 * Override to customize the addition of listeners a newly created
\r
446 protected void addListeners(GraphExplorer explorer, IMenuManager menuManager) {
\r
447 addSelectionInputListeners(explorer, menuManager);
\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
454 public String[] execute(Object... obj) {
\r
455 return new String[] { getEditingColumn((NodeContext) obj[0]) };
\r
458 // Default double click handling
\r
459 explorer.addListener(new DefaultMouseListener(explorer));
\r
462 protected String getEditingColumn(NodeContext context) {
\r
463 return ColumnKeys.SINGLE;
\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
472 * @param context may be <code>null</code> if there is no session
\r
474 protected void initializeExplorer(final GraphExplorer explorer, ISessionContext context) {
\r
475 setupDragSource((context != null) ? context.getSession() : null);
\r
478 // Needed for preventing unnecessary re-initialization of the explorer with the same input.
\r
479 private Object currentInput;
\r
481 protected boolean isImportantInput(Object previousInput, Object input) {
\r
482 return !ObjectUtils.objectEquals(previousInput, input);
\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
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
499 this.sessionContext = context;
\r
500 Object input = inputSource.get(context);
\r
501 if (!isImportantInput(currentInput, input))
\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
509 currentInput = input;
\r
511 // Start tracking the session context.
\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
523 @SuppressWarnings("rawtypes")
\r
525 public Object getAdapter(Class adapter) {
\r
527 if (GraphExplorer.class == adapter)
\r
529 else if(ISessionContextProvider.class == adapter)
\r
530 return getSessionContextProvider();
\r
531 else if(IPropertyPage.class == adapter)
\r
532 return getPropertyPage();
\r
534 return super.getAdapter(adapter);
\r
538 protected IPropertyPage getPropertyPage() {
\r