/******************************************************************************* * Copyright (c) 2007, 2018 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.platform; import java.util.Map; import java.util.WeakHashMap; import java.util.function.Consumer; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.LocalResourceManager; import org.eclipse.jface.resource.ResourceManager; import org.eclipse.jface.viewers.ISelection; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IMemento; import org.eclipse.ui.IPropertyListener; import org.eclipse.ui.ISelectionListener; import org.eclipse.ui.IViewPart; import org.eclipse.ui.IViewSite; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchPart3; import org.eclipse.ui.PartInitException; import org.eclipse.ui.contexts.IContextService; import org.eclipse.ui.part.IContributedContentsView; import org.eclipse.ui.part.IPage; import org.eclipse.ui.part.IPageBookViewPage; import org.eclipse.ui.part.PageBook; import org.simantics.Simantics; import org.simantics.db.management.ISessionContextProvider; import org.simantics.selectionview.PropertyPage; import org.simantics.ui.workbench.IPropertyPage; import org.simantics.ui.workbench.ResourceInput; import org.simantics.utils.ui.BundleUtils; import org.simantics.utils.ui.SWTUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This is a version of the standard eclipse PropertySheet view a * graph database access twist. It presents a property view based the active * workbench part and the active part's current selection. * *

* To get a property page for your own view or editor part you can do one of the * following: * *

    *
  1. Implement getAdapter for your view or editor part as follows: * *
     * Object getAdapter(Class c) {
     *     if (c == IPropertyPage.class) {
     *         // Get the browse contexts to use from somewhere
     *         Set<String> browseContexts = Collections.singleton("...");
     *         return new StandardPropertyPage(getSite(), browseContexts);
     *     }
     *     return super.getAdapter(c);
     * }
     * 
    * * This method also allows customization of the actual property page control * that gets created. PropertyPage serves as a good starting point * for your own version.
  2. *
  3. Make the workbench part implement the marker interface * IStandardPropertyPage which will make this view do the above * automatically without implementing getAdapter.
  4. *
* * @author Tuukka Lehtonen * * @see IStandardPropertyPage * @see IPropertyPage * @see PropertyPage */ public class PropertyPageView extends PageBookView implements IContributedContentsView { private static final Logger LOGGER = LoggerFactory.getLogger(PropertyPageView.class); private static final String PROPERTY_VIEW_CONTEXT = "org.simantics.modeling.ui.properties"; private static final String PROP_PINNED = "pinned"; private ISessionContextProvider contextProvider; /** * The initial selection when the property sheet opens */ private ISelection bootstrapSelection; /** * A flag for indicating whether or not this view will only use the * bootstrap selection and and IPropertyPage source instead of listening to * the input constantly. */ private final boolean bootstrapOnly = false; private IMemento memento; private boolean pinSelection = false; private IWorkbenchPart lastPart; private ISelection lastSelection; private final Map lastSelections = new WeakHashMap<>(); private ResourceManager resourceManager; private ImageDescriptor notPinned; private ImageDescriptor pinned; /** * The workbench post-selection listener for this view that changes the * input of the pagebook page for the part which changed its selection. */ private ISelectionListener selectionListener = this::doSelectionChanged; @Override public void createPartControl(Composite parent) { super.createPartControl(parent); this.resourceManager = new LocalResourceManager(JFaceResources.getResources()); notPinned = BundleUtils.getImageDescriptorFromPlugin("org.simantics.browsing.ui.common", "icons/table_multiple.png"); pinned = BundleUtils.getImageDescriptorFromPlugin("org.simantics.browsing.ui.common", "icons/table_multiple_pinned.png"); IContextService cs = (IContextService) getSite().getService(IContextService.class); cs.activateContext(PROPERTY_VIEW_CONTEXT); } /* * (non-Javadoc) * * @see org.eclipse.ui.part.PageBookView#getAdapter(java.lang.Class) */ @SuppressWarnings("rawtypes") @Override public Object getAdapter(Class adapter) { if (adapter == IContributedContentsView.class) { // This makes it possible to duplicate a PropertyPageView with another // secondary ID and make it show the same property page that was showing // in the original property page view. return new IContributedContentsView() { @Override public IWorkbenchPart getContributingPart() { return getContributingEditor(); } }; } return super.getAdapter(adapter); } /** * Returns the editor which contributed the current * page to this view. * * @return the editor which contributed the current page * or null if no editor contributed the current page */ private IWorkbenchPart getContributingEditor() { return getCurrentContributingPart(); } /* (non-Javadoc) * @see org.eclipse.ui.part.ViewPart#init(org.eclipse.ui.IViewSite, org.eclipse.ui.IMemento) */ @Override public void init(IViewSite site, IMemento memento) throws PartInitException { this.memento = memento; init(site); } /* * (non-Javadoc) * * @see org.eclipse.ui.part.PageBookView#init(org.eclipse.ui.IViewSite) */ @Override public void init(IViewSite site) throws PartInitException { String secondaryId = site.getSecondaryId(); if (secondaryId != null) { ResourceInput input = ResourceInput.unmarshall(secondaryId); if (input != null) { //bootstrapOnly = true; } } //System.out.println("PPV init: " + this); super.init(site); contextProvider = Simantics.getSessionContextProvider(); if (!bootstrapOnly) { site.getPage().addPostSelectionListener(selectionListener); } } @Override public void saveState(IMemento memento) { if (this.memento != null) { memento.putMemento(this.memento); } } /* (non-Javadoc) * Method declared on IWorkbenchPart. */ @Override public void dispose() { //System.out.println("PPV dispose: " + this); // Dispose of this before nullifying contextProvider because this // dispose may just need the context provider - at least PropertyPage // disposal will. super.dispose(); if (lastPart != null) lastPart.removePropertyListener(partPropertyListener); contextProvider = null; // Remove ourselves as a workbench selection listener. if (!bootstrapOnly) { getSite().getPage().removePostSelectionListener(selectionListener); } if (resourceManager != null) { resourceManager.dispose(); resourceManager = null; } } @Override protected IPage createDefaultPage(PageBook book) { /* MessagePage page = new MessagePage(); initPage(page); page.createControl(book); page.setMessage(Messages.PropertyPageView_noPropertiesAvailable); return page; */ PropertyPage page = new PropertyPage(getSite()); initPage(page); page.createControl(book); //System.out.println("PPV create default page: " + page); return page; } @Override protected PageRec doCreatePage(IWorkbenchPart part) { // NOTE: If the default page should be shown, this method must return null. if (part == null) return null; //System.out.println("PPV try to create page for part: " + (part != null ? part.getTitle() : null)); // Try to get a property page. IPropertyPage page = (IPropertyPage) part.getAdapter(IPropertyPage.class); if (page != null) { //System.out.println("PPV created page: " + page); if (page instanceof IPageBookViewPage) { initPage((IPageBookViewPage) page); } page.createControl(getPageBook()); //System.out.println("PPV created page control: " + page.getControl()); return new PageRec(part, page); } return null; } @Override protected void doDestroyPage(IWorkbenchPart part, PageRec pageRecord) { //System.out.println("PPV destroy page for part: " + part.getTitle()); IPropertyPage page = (IPropertyPage) pageRecord.page; page.dispose(); pageRecord.dispose(); } @Override protected IWorkbenchPart getBootstrapPart() { IWorkbenchPage page = getSite().getPage(); if (page != null) { bootstrapSelection = page.getSelection(); return page.getActivePart(); } return null; } private boolean isPropertyView(IWorkbenchPart part) { boolean ignore = false; if (part instanceof IWorkbenchPart3) { IWorkbenchPart3 part3 = (IWorkbenchPart3) part; ignore = Boolean.parseBoolean(part3.getPartProperty(PROP_PINNED)); } // See org.simantics.modeling.ui.actions.DuplicatePinnedViewHandler // ignore |= part.getSite().getId().endsWith("Pinned"); String thisId = getSite().getId(); String otherId = part.getSite().getId(); //System.out.println(thisId + " - " + otherId); ignore |= otherId.startsWith(thisId); return this == part || ignore; } @Override protected boolean isImportant(IWorkbenchPart part) { //System.out.println("isImportant(" + part.getSite().getId() + ")"); return !isWorkbenchSelectionPinned() && !isPropertyView(part); } /** * The PropertySheet implementation of this * IPartListener method first sees if the active part is an * IContributedContentsView adapter and if so, asks it for * its contributing part. */ @Override public void partActivated(IWorkbenchPart part) { // if (bootstrapSelection == null && bootstrapOnly) // return; // Look for a declaratively-contributed adapter - including not yet // loaded adapter factories. // See bug 86362 [PropertiesView] Can not access AdapterFactory, when // plugin is not loaded. IWorkbenchPart source = getSourcePart(part); //System.out.println("PPV part activated: " + part + ",src " + source + ",view " + this + " bss: " + bootstrapSelection + " pin " + pinSelection); super.partActivated(source); // When the view is first opened, pass the selection to the page if (bootstrapSelection != null) { IPage page = getCurrentPage(); if (page instanceof IPropertyPage) { IPropertyPage ppage = (IPropertyPage) page; // FIXME: should this pass source or part ?? ppage.selectionChanged(part, bootstrapSelection); updatePartName(ppage, bootstrapSelection); } bootstrapSelection = null; } } @Override public void partClosed(IWorkbenchPart part) { // Make sure that pinned view is not reset even if its originating // editor is closed. if (!pinSelection) super.partClosed(part); } @Override protected void partHidden(IWorkbenchPart part) { // Fast views are quite unusable if this code is enabled. // Make sure that pinned view is not hidden when the editor is hidden // if(!pinSelection) // super.partHidden(part); } public ISelection getLastSelection() { return lastSelection; } public void selectionChanged(IWorkbenchPart part, ISelection sel) { doSelectionChanged(part, sel); } /** * @param part * @param sel * @return true if the changed selection affected the view, * false otherwise */ boolean doSelectionChanged(IWorkbenchPart part, ISelection sel) { LOGGER.trace("doSelectionChanged({}): incoming selection {}", part, sel); // we ignore our own selection or null selection if (isPropertyView(part) || sel == null) { return false; } // ignore workbench selections when pinned also if (pinSelection) return false; // pass the selection change to the page part = getSourcePart(part); IPage page = getCurrentPage(); //System.out.println("PPV selection changed to (" + part + ", " + sel + "): " + page); if (page instanceof IPropertyPage) { IPropertyPage ppage = (IPropertyPage) page; // Prevent parts that do not contribute a property page from messing // up the contents/title of the currently active property page. PageRec pageRec = getPageRec(part); if (pageRec == null || pageRec.page != page) return false; // Make sure that the part name is not updated unnecessarily because // of immediate and post selection listeners. ISelection lastPartSelection = lastSelections.get(part); //System.out.println(" LAST PART SELECTION(" + part + "): " + lastPartSelection); boolean sameSelection = lastPartSelection != null && sel.equals(lastPartSelection); if (lastPart != null) { lastPart.removePropertyListener(partPropertyListener); } lastPart = part; lastSelection = sel; lastSelections.put(part, sel); if (lastPart != null) { lastPart.addPropertyListener(partPropertyListener); } if (!sameSelection) { LOGGER.trace("doSelectionChanged({}): updating page input selection to {}", part, sel); updatePartName(ppage, sel); ppage.selectionChanged(part, sel); return true; } } return false; } void updatePartName(IPropertyPage ppage, ISelection sel) { ppage.updatePartName(sel, partNameUpdateCallback); } Consumer partNameUpdateCallback = parameter -> { // This check is not safe - there might be a burst of changes incoming //if (getPartName().equals(parameter)) return; //System.out.println("partNameUpdateCallback : " + parameter); SWTUtils.asyncExec(getPageBook(), () -> { if (!getPageBook().isDisposed()) { if (getPartName().equals(parameter)) return; //System.out.println("doSetParameterName : " + parameter); doSetPartName(parameter); } }); }; void doSetPartName(String partName) { // Is the page view disposed ?? if (contextProvider == null) return; if (partName == null) { // Return to default partName = "Selection"; } setPartName(partName); } public boolean isWorkbenchSelectionPinned() { return pinSelection; } public void pinWorkbenchSelection(boolean pin) { if (pin == pinSelection) return; pinSelection = pin; setPartProperty(PROP_PINNED, Boolean.toString(pin)); if (pin) { setTitleImage(resourceManager.createImage(pinned)); } else { setTitleImage(resourceManager.createImage(notPinned)); } updateContentDescription(pin, lastPart); // Since lastPart is another PropertyView, we do not want to listen it's changes (At least current implementation is done so) if (lastPart != null) { lastPart.removePropertyListener(partPropertyListener); } lastPart = null; } IWorkbenchPart getSourcePart(IWorkbenchPart part) { IContributedContentsView view = (IContributedContentsView) part.getAdapter(IContributedContentsView.class); if (view != null) { IWorkbenchPart source = view.getContributingPart(); if (source != null) return source; } return part; } private void updateContentDescription(boolean selectionPinned, IWorkbenchPart sourcePart) { if (selectionPinned) { if (sourcePart == null) { setContentDescription("No selection"); } else { sourcePart = getSourcePart(sourcePart); StringBuilder desc = new StringBuilder("Selection from "); if (sourcePart instanceof IEditorPart) desc.append("editor "); if (sourcePart instanceof IViewPart) desc.append("view "); desc.append('\''); desc.append(sourcePart.getTitle()); desc.append('\''); setContentDescription(desc.toString()); } } else { setContentDescription(""); } } IPropertyListener partPropertyListener = new IPropertyListener() { @Override public void propertyChanged(Object source, int propId) { if (propId == IWorkbenchPart.PROP_TITLE) { updateContentDescription(pinSelection, lastPart); } } }; @Override public IWorkbenchPart getContributingPart() { return lastPart; } }