X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.browsing.ui.platform%2Fsrc%2Forg%2Fsimantics%2Fbrowsing%2Fui%2Fplatform%2FPropertyPageView.java;fp=bundles%2Forg.simantics.browsing.ui.platform%2Fsrc%2Forg%2Fsimantics%2Fbrowsing%2Fui%2Fplatform%2FPropertyPageView.java;h=4b98bd3e9d2c7741570de09c6af230890e6901e8;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.browsing.ui.platform/src/org/simantics/browsing/ui/platform/PropertyPageView.java b/bundles/org.simantics.browsing.ui.platform/src/org/simantics/browsing/ui/platform/PropertyPageView.java new file mode 100644 index 000000000..4b98bd3e9 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.platform/src/org/simantics/browsing/ui/platform/PropertyPageView.java @@ -0,0 +1,614 @@ +/******************************************************************************* + * 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.platform; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.function.Consumer; + +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExtension; +import org.eclipse.core.runtime.IExtensionPoint; +import org.eclipse.core.runtime.IExtensionRegistry; +import org.eclipse.core.runtime.RegistryFactory; +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.PlatformUI; +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.db.management.ISessionContextProvider; +import org.simantics.selectionview.PropertyPage; +import org.simantics.ui.SimanticsUI; +import org.simantics.ui.workbench.IPropertyPage; +import org.simantics.ui.workbench.ResourceInput; +import org.simantics.utils.threads.SWTThread; +import org.simantics.utils.threads.Throttler; +import org.simantics.utils.ui.BundleUtils; +import org.simantics.utils.ui.SWTUtils; + +/** + * 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 ISelectionListener, IContributedContentsView { + + /** + * Extension point used to modify behavior of the view + */ + private static final String EXT_POINT = "org.eclipse.ui.propertiesView"; //$NON-NLS-1$ + + private static final String PROPERTY_VIEW_CONTEXT = "org.simantics.modeling.ui.properties"; + + private static final String PROP_PINNED = "pinned"; + + protected static final long SELECTION_CHANGE_THRESHOLD = 500; + + 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; + + /** + * Set of workbench parts, which should not be used as a source for PropertySheet + */ + private Set ignoredViews; + + @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); + + // This prevents the Properties view from providing a selection to other + // workbench parts, thus making them lose their selections which is not + // desirable. +// site.setSelectionProvider(null); + + contextProvider = SimanticsUI.getSessionContextProvider(); + + if (!bootstrapOnly) { + site.getPage().addSelectionListener(immediateSelectionListener); + site.getPage().addPostSelectionListener(this); + } + } + + @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(this); + getSite().getPage().removeSelectionListener(immediateSelectionListener); + } + + 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; + } + + private Set getIgnoredViews() { + if (ignoredViews == null) { + ignoredViews = new HashSet(); + IExtensionRegistry registry = RegistryFactory.getRegistry(); + IExtensionPoint ep = registry.getExtensionPoint(EXT_POINT); + if (ep != null) { + IExtension[] extensions = ep.getExtensions(); + for (int i = 0; i < extensions.length; i++) { + IConfigurationElement[] elements = extensions[i].getConfigurationElements(); + for (int j = 0; j < elements.length; j++) { + if ("excludeSources".equalsIgnoreCase(elements[j].getName())) { //$NON-NLS-1$ + String id = elements[j].getAttribute("id"); //$NON-NLS-1$ + if (id != null) + ignoredViews.add(id); + } + } + } + } + } + return ignoredViews; + } + + private boolean isViewIgnored(String partID) { + return getIgnoredViews().contains(partID); + } + + @Override + protected boolean isImportant(IWorkbenchPart part) { + String partID = part.getSite().getId(); + //System.out.println("isImportant(" + partID + ")"); + return !isWorkbenchSelectionPinned() && !isPropertyView(part) && !isViewIgnored(partID); + } + + /** + * 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); + } + + ISelectionListener immediateSelectionListener = new ISelectionListener() { + + private Throttler throttler = new Throttler(SWTThread.getThreadAccess(PlatformUI.getWorkbench().getDisplay()), 500, 3); + + @Override + public void selectionChanged(final IWorkbenchPart part, final ISelection selection) { + + // Do not process selections from self + if(PropertyPageView.this == part) return; + + throttler.schedule(new Runnable() { + + @Override + public void run() { + PropertyPageView.this.doSelectionChanged(part, selection); + } + + }); + + } + }; + + public ISelection getLastSelection() { + return lastSelection; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.ISelectionListener#selectionChanged(org.eclipse.ui.IWorkbenchPart, + * org.eclipse.jface.viewers.ISelection) + */ + @Override + 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) { + + // 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); + } + + updatePartName(ppage, sel); + if (!sameSelection) { + 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(), new Runnable() { + @Override + public void run() { + 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; + } + +}