/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.browsing.ui.swt; import java.util.Map; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.LocalResourceManager; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.ui.IMemento; import org.eclipse.ui.ISelectionListener; import org.eclipse.ui.IViewSite; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PartInitException; import org.eclipse.ui.part.ViewPart; import org.simantics.browsing.ui.Column; import org.simantics.browsing.ui.GraphExplorer; import org.simantics.browsing.ui.NodeContext; import org.simantics.browsing.ui.common.ColumnKeys; import org.simantics.browsing.ui.common.views.IViewArguments; import org.simantics.browsing.ui.graph.impl.SessionContextInputSource; import org.simantics.db.Session; import org.simantics.db.common.request.Queries; import org.simantics.db.exception.DatabaseException; import org.simantics.db.management.ISessionContext; import org.simantics.db.management.ISessionContextChangedListener; import org.simantics.db.management.ISessionContextProvider; import org.simantics.db.management.SessionContextChangedEvent; import org.simantics.project.IProject; import org.simantics.project.ProjectKeys; import org.simantics.ui.SimanticsUI; import org.simantics.ui.dnd.BasicDragSource; import org.simantics.ui.dnd.SessionContainer; import org.simantics.ui.workbench.IPropertyPage; import org.simantics.utils.ObjectUtils; import org.simantics.utils.datastructures.Function; import org.simantics.utils.datastructures.disposable.DisposeState; import org.simantics.utils.datastructures.hints.HintListenerAdapter; import org.simantics.utils.datastructures.hints.HintTracker; import org.simantics.utils.datastructures.hints.IHintContext.Key; import org.simantics.utils.datastructures.hints.IHintListener; import org.simantics.utils.datastructures.hints.IHintObservable; import org.simantics.utils.datastructures.hints.IHintTracker; import org.simantics.utils.ui.LayoutUtils; /** * An abstract base Eclipse workbench ViewPart for use in situations where a * tree-based GraphExplorer graph model browser is needed. * *

* Override to customize behavior: *

*

* *

* You can invoke the following methods from within * {@link #createControls(Composite)} to customize how the view keeps track of * the active ISessionContext and how the view resolves the input object of the * GraphExplorer control. *

*

* * @author Tuukka Lehtonen * @deprecated in favor of org.simantics.views.swt.ModelledView */ public abstract class GraphExplorerViewBase extends ViewPart { /** * The default hint tracker that will be active if * {@link GraphExplorerViewBase#setSessionContextTracker(IHintTracker) is * not called. */ public class SessionContextProjectTracker extends HintTracker { public SessionContextProjectTracker() { IHintListener activeProjectListener = new HintListenerAdapter() { @Override public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) { applySessionContext(getSessionContext()); } }; addKeyHintListener(ProjectKeys.KEY_PROJECT, activeProjectListener); } } /** * The default implementation of a {@link SessionContextInputSource} that * will be active if * {@link GraphExplorerViewBase#setInputSource(SessionContextInputSource)} * is not called. */ public class SessionContextProjectSource implements SessionContextInputSource { /** * Returns the input object used by this GraphExplorer view. This object * will be the starting point for all content shown in this explorer tree. * * @param sessionContext a valid database session context * @return the root object of the graph explorer tree or null * to indicate that there is no input and nothing should be shown. */ @Override public Object get(ISessionContext ctx) { if (ctx == null) return GraphExplorer.EMPTY_INPUT; String inputId = getViewArguments().get(IViewArguments.INPUT); if (inputId != null) { try { return ctx.getSession().syncRequest(Queries.resource(inputId)); } catch (DatabaseException e) { // Ok, the view argument was invalid. Just continue to the next // method. } } Object input = GraphExplorer.EMPTY_INPUT; IProject project2 = ctx.getHint(ProjectKeys.KEY_PROJECT); if (project2 != null) input = project2.get(); return input; } @Override public IWorkbenchPart getProvider() { return null; } } protected LocalResourceManager resourceManager; protected ISelectionListener workbenchSelectionListener; protected Composite parent; protected GraphExplorer explorer; protected IMenuManager menuManager; private Map args; private ISessionContextProvider contextProvider; private ISessionContext sessionContext; private Object dragSource; protected IMemento memento; private IHintTracker sessionContextTracker = new SessionContextProjectTracker(); private SessionContextInputSource inputSource = new SessionContextProjectSource(); private DisposeState disposeState = DisposeState.Alive; protected ISessionContextChangedListener contextChangeListener = new ISessionContextChangedListener() { @Override public void sessionContextChanged(SessionContextChangedEvent event) { sessionContext = event.getNewValue(); sessionContextTracker.track(sessionContext); } }; protected void setSessionContextTracker(IHintTracker tracker) { this.sessionContextTracker = tracker; } protected void setInputSource(SessionContextInputSource source) { this.inputSource = source; } protected SessionContextInputSource getInputSource() { return inputSource; } protected Map getViewArguments() { return args; } protected DisposeState getDisposeState() { return disposeState; } public ISessionContext getSessionContext() { return sessionContext; } public ISessionContextProvider getSessionContextProvider() { return contextProvider; } @Override public void createPartControl(Composite parent) { this.parent = parent; this.resourceManager = new LocalResourceManager(JFaceResources.getResources(parent.getDisplay())); contextProvider = SimanticsUI.getSessionContextProvider(getViewSite().getWorkbenchWindow()); createControls(parent); attachToSession(); } /** * Invoked when this viewpart is disposed. Unhooks the view from its * ISessionContextProvider. Overriding is allowed but super.dispose() must * be called. * * @see org.eclipse.ui.part.WorkbenchPart#dispose() */ @Override public void dispose() { disposeState = DisposeState.Disposing; try { //System.out.println(this + ".GraphExplorerViewBase.dispose()"); if (contextProvider != null) { contextProvider.removeContextChangedListener(contextChangeListener); contextProvider = null; } sessionContextTracker.untrack(); resourceManager.dispose(); resourceManager = null; args = null; explorer = null; sessionContext = null; dragSource = null; parent = null; super.dispose(); } finally { disposeState = DisposeState.Disposed; } } @Override public void setFocus() { if (explorer != null && !explorer.isDisposed()) explorer.setFocus(); } @Override public void init(IViewSite site) throws PartInitException { super.init(site); this.args = ViewArgumentUtils.parseViewArguments(this); } @Override public void init(IViewSite site, IMemento memento) throws PartInitException { super.init(site, memento); this.args = ViewArgumentUtils.parseViewArguments(this); this.memento = memento; } @Override public void saveState(IMemento memento) { if (this.memento != null) { memento.putMemento(this.memento); } // if (explorer != null) // explorer.saveState(memento); } protected void setWorkbenchListeners() { if (workbenchSelectionListener == null) { ISelectionProvider selectionProvider = (ISelectionProvider)explorer.getAdapter(ISelectionProvider.class); getSite().setSelectionProvider(selectionProvider); // Listen to the workbench selection also to propagate it to // the explorer also. workbenchSelectionListener = new DefaultExplorerSelectionListener(this, explorer); getSite().getWorkbenchWindow().getSelectionService().addPostSelectionListener(workbenchSelectionListener); } } protected void removeWorkbenchListeners() { // Remember to remove the installed workbench selection listener if (workbenchSelectionListener != null) { getSite().getWorkbenchWindow().getSelectionService().removePostSelectionListener(workbenchSelectionListener); workbenchSelectionListener = null; getSite().setSelectionProvider(null); } } protected final void attachToSession() { // Track active ISessionContext changes //contextProvider = SimanticsUI.getSessionContextProvider(getViewSite().getWorkbenchWindow()); contextProvider.addContextChangedListener(contextChangeListener); // Start tracking the current session context for input changes. // This will/must cause applySessionContext to get called. // Doing the applySessionContext initialization this way // instead of directly calling it will also make sure that // applySessionContext is only called once when first initialized, // and not twice like with the direct invocation. this.sessionContext = contextProvider.getSessionContext(); sessionContextTracker.track(sessionContext); } // ///////////////////////////////////////////////////////////////////////// // Override / implement these: // /** // * Returns an ID that is used for persisting a GraphExplorer instance. // * // * Used for restoreState(IMemento) and // * restoreState(IMemento) in OntologyExplorer. Must be unique // * within a workbench part. // * // * @return a unique name for this particular graph explorer view used for // * saving and restoring the state of this view part // */ // public String getExplorerName() { // return "GraphExplorerViewBase"; // } protected Column[] getColumns() { return null; } /** * Override this method to add controls to the view part. This is invoked * before attaching the view part to a database session. * * @param parent */ protected void createControls(Composite parent) { parent.setLayout(LayoutUtils.createNoBorderGridLayout(1, false)); // Initialize explorer control. explorer = createExplorerControl(parent); ISelectionProvider selectionProvider = (ISelectionProvider)explorer.getAdapter(ISelectionProvider.class); Control control = explorer.getControl(); Column[] columns = getColumns(); if(columns != null) explorer.setColumns(columns); GridDataFactory.fillDefaults().grab(true, true).applyTo(control); // Initialize context menu if an initializer is provided. IContextMenuInitializer cmi = getContextMenuInitializer(); if (cmi != null) { menuManager = cmi.createContextMenu(control, selectionProvider, getSite()); } // Initialize DND. dragSource = createDragSource(explorer); // Listeners are only added once per listener, not every time the // session context changes. addListeners(explorer, menuManager); } /** * Override this method and provide a proper context menu initializer if you * want to have this base class initialize one for you. * * @return the initializer to be used by {@link #createControls(Composite)} */ protected IContextMenuInitializer getContextMenuInitializer() { String contextMenuId = getContextMenuId(); if(contextMenuId != null) { return new ContextMenuInitializer(contextMenuId); } else { return null; } } /** * @return the ID of the context menu to initialize for this this graph * explorer view or null to not initialize a context * menu */ protected String getContextMenuId() { return null; } protected int getStyle() { return SWT.MULTI; } /** * @param parent * @return */ protected GraphExplorer createExplorerControl(Composite parent) { return GraphExplorerFactory.getInstance() .selectionDataResolver(new DefaultSelectionDataResolver()) .create(parent, getStyle()); } /** * Override to customize drag source initialization. This default * implementation creates a {@link BasicDragSource}. The drag source is * initialized when the active database session is set. * * @param explorer * @return the object representing the drag source. If the object implements * {@link SessionContainer}, its * {@link SessionContainer#setSession(Session)} will be invoked * every time the active database session changes. */ protected Object createDragSource(GraphExplorer explorer) { ISelectionProvider selectionProvider = (ISelectionProvider)explorer.getAdapter(ISelectionProvider.class); Control control = explorer.getControl(); return new BasicDragSource(selectionProvider, control, null); } protected void setupDragSource(Session session) { if (dragSource instanceof SessionContainer) { ((SessionContainer) dragSource).setSession(session); } } /** * Override to customize the addition of listeners a newly created * GraphExplorer. * * @param explorer */ protected void addListeners(GraphExplorer explorer, IMenuManager menuManager) { addSelectionInputListeners(explorer, menuManager); } protected void addSelectionInputListeners(GraphExplorer explorer, IMenuManager menuManager) { // Consider ENTER presses to simulate mouse left button double clicks explorer.addListener(new DefaultKeyListener(contextProvider, explorer, new Function() { @Override public String[] execute(Object... obj) { return new String[] { getEditingColumn((NodeContext) obj[0]) }; } })); // Default double click handling explorer.addListener(new DefaultMouseListener(explorer)); } protected String getEditingColumn(NodeContext context) { return ColumnKeys.SINGLE; } /** * Override to customize the initialization of the content provision and * presentation of a GraphExplorer. This is called every time the input * database session changes. * * @param explorer * @param context may be null if there is no session */ protected void initializeExplorer(final GraphExplorer explorer, ISessionContext context) { setupDragSource((context != null) ? context.getSession() : null); } // Needed for preventing unnecessary re-initialization of the explorer with the same input. private Object currentInput; protected boolean isImportantInput(Object previousInput, Object input) { return !ObjectUtils.objectEquals(previousInput, input); } /** * Invoke this to reinitialize the explorer and reset its input. The input * will be resolved from the specified ISessionContext based on the * {@link SessionContextInputSource} that is currently in use. If the input * is identical to the previous input, nothing will be done. * * @param context */ protected final boolean applySessionContext(ISessionContext context) { // If control is not alive anymore, do nothing. // System.out.println(this + ": applySessionContext(" + context + "), explorer=" + explorer); if (disposeState != DisposeState.Alive) return false; this.sessionContext = context; Object input = inputSource.get(context); if (!isImportantInput(currentInput, input)) return false; // System.out.println(this + ": initializeExplorer(" + explorer + ", " + context + ")"); initializeExplorer(explorer, context); // System.out.println(this + ": setRoot(" + input + ")"); explorer.setRoot(input); currentInput = input; // Start tracking the session context. // // If this is not the same session that is currently tracked, it will // cause IHintListeners of the sessionContextTracker to fire. // For this we need the above input equality (identity) checking. // This is here just to make sure that we are tracking the correct // session context. sessionContextTracker.track(sessionContext); return true; } @SuppressWarnings("rawtypes") @Override public Object getAdapter(Class adapter) { if (GraphExplorer.class == adapter) return explorer; else if(ISessionContextProvider.class == adapter) return getSessionContextProvider(); else if(IPropertyPage.class == adapter) return getPropertyPage(); return super.getAdapter(adapter); } protected IPropertyPage getPropertyPage() { return null; } }