]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.browsing.ui.platform/src/org/simantics/browsing/ui/platform/PropertyPageView.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.browsing.ui.platform / src / org / simantics / browsing / ui / platform / PropertyPageView.java
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 (file)
index 0000000..4b98bd3
--- /dev/null
@@ -0,0 +1,614 @@
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ *     VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.browsing.ui.platform;\r
+\r
+import java.util.HashSet;\r
+import java.util.Map;\r
+import java.util.Set;\r
+import java.util.WeakHashMap;\r
+import java.util.function.Consumer;\r
+\r
+import org.eclipse.core.runtime.IConfigurationElement;\r
+import org.eclipse.core.runtime.IExtension;\r
+import org.eclipse.core.runtime.IExtensionPoint;\r
+import org.eclipse.core.runtime.IExtensionRegistry;\r
+import org.eclipse.core.runtime.RegistryFactory;\r
+import org.eclipse.jface.resource.ImageDescriptor;\r
+import org.eclipse.jface.resource.JFaceResources;\r
+import org.eclipse.jface.resource.LocalResourceManager;\r
+import org.eclipse.jface.resource.ResourceManager;\r
+import org.eclipse.jface.viewers.ISelection;\r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.ui.IEditorPart;\r
+import org.eclipse.ui.IMemento;\r
+import org.eclipse.ui.IPropertyListener;\r
+import org.eclipse.ui.ISelectionListener;\r
+import org.eclipse.ui.IViewPart;\r
+import org.eclipse.ui.IViewSite;\r
+import org.eclipse.ui.IWorkbenchPage;\r
+import org.eclipse.ui.IWorkbenchPart;\r
+import org.eclipse.ui.IWorkbenchPart3;\r
+import org.eclipse.ui.PartInitException;\r
+import org.eclipse.ui.PlatformUI;\r
+import org.eclipse.ui.contexts.IContextService;\r
+import org.eclipse.ui.part.IContributedContentsView;\r
+import org.eclipse.ui.part.IPage;\r
+import org.eclipse.ui.part.IPageBookViewPage;\r
+import org.eclipse.ui.part.PageBook;\r
+import org.simantics.db.management.ISessionContextProvider;\r
+import org.simantics.selectionview.PropertyPage;\r
+import org.simantics.ui.SimanticsUI;\r
+import org.simantics.ui.workbench.IPropertyPage;\r
+import org.simantics.ui.workbench.ResourceInput;\r
+import org.simantics.utils.threads.SWTThread;\r
+import org.simantics.utils.threads.Throttler;\r
+import org.simantics.utils.ui.BundleUtils;\r
+import org.simantics.utils.ui.SWTUtils;\r
+\r
+/**\r
+ * This is a version of the standard eclipse <code>PropertySheet</code> view a\r
+ * graph database access twist. It presents a property view based the active\r
+ * workbench part and the active part's current selection.\r
+ * \r
+ * <p>\r
+ * To get a property page for your own view or editor part you can do one of the\r
+ * following:\r
+ * \r
+ * <ol>\r
+ * <li>Implement getAdapter for your view or editor part as follows:\r
+ * \r
+ * <pre>\r
+ * Object getAdapter(Class c) {\r
+ *     if (c == IPropertyPage.class) {\r
+ *         // Get the browse contexts to use from somewhere\r
+ *         Set&lt;String&gt; browseContexts = Collections.singleton("...");\r
+ *         return new StandardPropertyPage(getSite(), browseContexts);\r
+ *     }\r
+ *     return super.getAdapter(c);\r
+ * }\r
+ * </pre>\r
+ * \r
+ * This method also allows customization of the actual property page control\r
+ * that gets created. <code>PropertyPage</code> serves as a good starting point\r
+ * for your own version.</li>\r
+ * <li>Make the workbench part implement the marker interface\r
+ * <code>IStandardPropertyPage</code> which will make this view do the above\r
+ * automatically without implementing <code>getAdapter</code>.</li>\r
+ * </ol>\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ * \r
+ * @see IStandardPropertyPage\r
+ * @see IPropertyPage\r
+ * @see PropertyPage\r
+ */\r
+public class PropertyPageView extends PageBookView implements ISelectionListener, IContributedContentsView {\r
+\r
+    /**\r
+     * Extension point used to modify behavior of the view\r
+     */\r
+    private static final String                   EXT_POINT                  = "org.eclipse.ui.propertiesView";              //$NON-NLS-1$\r
+\r
+    private static final String                   PROPERTY_VIEW_CONTEXT      = "org.simantics.modeling.ui.properties";\r
+\r
+    private static final String                   PROP_PINNED                = "pinned";\r
+\r
+    protected static final long                   SELECTION_CHANGE_THRESHOLD = 500;\r
+\r
+    private ISessionContextProvider               contextProvider;\r
+\r
+    /**\r
+     * The initial selection when the property sheet opens\r
+     */\r
+    private ISelection                            bootstrapSelection;\r
+\r
+    /**\r
+     * A flag for indicating whether or not this view will only use the\r
+     * bootstrap selection and and IPropertyPage source instead of listening to\r
+     * the input constantly.\r
+     */\r
+    private final boolean                         bootstrapOnly              = false;\r
+\r
+    private IMemento                              memento;\r
+\r
+    private boolean                               pinSelection               = false;\r
+\r
+    private IWorkbenchPart                        lastPart;\r
+    private ISelection                            lastSelection;\r
+    private final Map<IWorkbenchPart, ISelection> lastSelections             = new WeakHashMap<IWorkbenchPart, ISelection>();\r
+\r
+    private ResourceManager                       resourceManager;\r
+\r
+    private ImageDescriptor                       notPinned;\r
+    private ImageDescriptor                       pinned;\r
+\r
+    /**\r
+     * Set of workbench parts, which should not be used as a source for PropertySheet\r
+     */\r
+    private Set<String>                           ignoredViews;\r
+\r
+    @Override\r
+    public void createPartControl(Composite parent) {\r
+        super.createPartControl(parent);\r
+\r
+        this.resourceManager = new LocalResourceManager(JFaceResources.getResources());\r
+        notPinned = BundleUtils.getImageDescriptorFromPlugin("org.simantics.browsing.ui.common", "icons/table_multiple.png");\r
+        pinned = BundleUtils.getImageDescriptorFromPlugin("org.simantics.browsing.ui.common", "icons/table_multiple_pinned.png");\r
+\r
+        IContextService cs = (IContextService) getSite().getService(IContextService.class);\r
+        cs.activateContext(PROPERTY_VIEW_CONTEXT);\r
+    }\r
+\r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see org.eclipse.ui.part.PageBookView#getAdapter(java.lang.Class)\r
+     */\r
+    @SuppressWarnings("rawtypes")\r
+    @Override\r
+    public Object getAdapter(Class adapter) {\r
+        if (adapter == IContributedContentsView.class) {\r
+            // This makes it possible to duplicate a PropertyPageView with another\r
+            // secondary ID and make it show the same property page that was showing\r
+            // in the original property page view.\r
+            return new IContributedContentsView() {\r
+                @Override\r
+                public IWorkbenchPart getContributingPart() {\r
+                    return getContributingEditor();\r
+                }\r
+            };\r
+        }\r
+        return super.getAdapter(adapter);\r
+    }\r
+\r
+    /**\r
+     * Returns the editor which contributed the current\r
+     * page to this view.\r
+     *\r
+     * @return the editor which contributed the current page\r
+     * or <code>null</code> if no editor contributed the current page\r
+     */\r
+    private IWorkbenchPart getContributingEditor() {\r
+        return getCurrentContributingPart();\r
+    }\r
+\r
+    /* (non-Javadoc)\r
+     * @see org.eclipse.ui.part.ViewPart#init(org.eclipse.ui.IViewSite, org.eclipse.ui.IMemento)\r
+     */\r
+    @Override\r
+    public void init(IViewSite site, IMemento memento) throws PartInitException {\r
+        this.memento = memento;\r
+        init(site);\r
+    }\r
+\r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see org.eclipse.ui.part.PageBookView#init(org.eclipse.ui.IViewSite)\r
+     */\r
+    @Override\r
+    public void init(IViewSite site) throws PartInitException {\r
+        String secondaryId = site.getSecondaryId();\r
+        if (secondaryId != null) {\r
+            ResourceInput input = ResourceInput.unmarshall(secondaryId);\r
+            if (input != null) {\r
+                //bootstrapOnly = true;\r
+            }\r
+        }\r
+\r
+        //System.out.println("PPV init: " + this);\r
+        super.init(site);\r
+\r
+        // This prevents the Properties view from providing a selection to other\r
+        // workbench parts, thus making them lose their selections which is not\r
+        // desirable.\r
+//        site.setSelectionProvider(null);\r
+\r
+        contextProvider = SimanticsUI.getSessionContextProvider();\r
+\r
+        if (!bootstrapOnly) {\r
+            site.getPage().addSelectionListener(immediateSelectionListener);\r
+            site.getPage().addPostSelectionListener(this);\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public void saveState(IMemento memento) {\r
+        if (this.memento != null) {\r
+            memento.putMemento(this.memento);\r
+        }\r
+    }\r
+\r
+    /* (non-Javadoc)\r
+     * Method declared on IWorkbenchPart.\r
+     */\r
+    @Override\r
+    public void dispose() {\r
+        //System.out.println("PPV dispose: " + this);\r
+        // Dispose of this before nullifying contextProvider because this\r
+        // dispose may just need the context provider - at least PropertyPage\r
+        // disposal will.\r
+        super.dispose();\r
+\r
+        if (lastPart != null)\r
+            lastPart.removePropertyListener(partPropertyListener);\r
+\r
+        contextProvider = null;\r
+\r
+        // Remove ourselves as a workbench selection listener.\r
+        if (!bootstrapOnly) {\r
+            getSite().getPage().removePostSelectionListener(this);\r
+            getSite().getPage().removeSelectionListener(immediateSelectionListener);\r
+        }\r
+\r
+        if (resourceManager != null) {\r
+            resourceManager.dispose();\r
+            resourceManager = null;\r
+        }\r
+    }\r
+\r
+    @Override\r
+    protected IPage createDefaultPage(PageBook book) {\r
+        /*\r
+        MessagePage page = new MessagePage();\r
+        initPage(page);\r
+        page.createControl(book);\r
+        page.setMessage(Messages.PropertyPageView_noPropertiesAvailable);\r
+        return page;\r
+         */\r
+\r
+        PropertyPage page = new PropertyPage(getSite());\r
+        initPage(page);\r
+        page.createControl(book);\r
+        //System.out.println("PPV create default page: " + page);\r
+        return page;\r
+\r
+    }\r
+\r
+    @Override\r
+    protected PageRec doCreatePage(IWorkbenchPart part) {\r
+\r
+        // NOTE: If the default page should be shown, this method must return null.\r
+        if (part == null)\r
+            return null;\r
+\r
+        //System.out.println("PPV try to create page for part: " + (part != null ? part.getTitle() : null));\r
+\r
+        // Try to get a property page.\r
+        IPropertyPage page = (IPropertyPage) part.getAdapter(IPropertyPage.class);\r
+        if (page != null) {\r
+            //System.out.println("PPV created page: " + page);\r
+            if (page instanceof IPageBookViewPage) {\r
+                initPage((IPageBookViewPage) page);\r
+            }\r
+            page.createControl(getPageBook());\r
+            //System.out.println("PPV created page control: " + page.getControl());\r
+            return new PageRec(part, page);\r
+        }\r
+        return null;\r
+    }\r
+\r
+    @Override\r
+    protected void doDestroyPage(IWorkbenchPart part, PageRec pageRecord) {\r
+        //System.out.println("PPV destroy page for part: " + part.getTitle());\r
+\r
+        IPropertyPage page = (IPropertyPage) pageRecord.page;\r
+        page.dispose();\r
+        pageRecord.dispose();\r
+    }\r
+\r
+    @Override\r
+    protected IWorkbenchPart getBootstrapPart() {\r
+        IWorkbenchPage page = getSite().getPage();\r
+        if (page != null) {\r
+            bootstrapSelection = page.getSelection();\r
+            return page.getActivePart();\r
+        }\r
+        return null;\r
+    }\r
+\r
+    private boolean isPropertyView(IWorkbenchPart part) {\r
+        boolean ignore = false;\r
+\r
+        if (part instanceof IWorkbenchPart3) {\r
+            IWorkbenchPart3 part3 = (IWorkbenchPart3) part;\r
+            ignore = Boolean.parseBoolean(part3.getPartProperty(PROP_PINNED));\r
+        }\r
+\r
+        // See org.simantics.modeling.ui.actions.DuplicatePinnedViewHandler\r
+//        ignore |= part.getSite().getId().endsWith("Pinned");\r
+        String thisId = getSite().getId();\r
+        String otherId = part.getSite().getId();\r
+        //System.out.println(thisId + " - " + otherId);\r
+        ignore |= otherId.startsWith(thisId);\r
+\r
+        return this == part || ignore;\r
+    }\r
+\r
+    private Set<String> getIgnoredViews() {\r
+        if (ignoredViews == null) {\r
+            ignoredViews = new HashSet<String>();\r
+            IExtensionRegistry registry = RegistryFactory.getRegistry();\r
+            IExtensionPoint ep = registry.getExtensionPoint(EXT_POINT);\r
+            if (ep != null) {\r
+                IExtension[] extensions = ep.getExtensions();\r
+                for (int i = 0; i < extensions.length; i++) {\r
+                    IConfigurationElement[] elements = extensions[i].getConfigurationElements();\r
+                    for (int j = 0; j < elements.length; j++) {\r
+                        if ("excludeSources".equalsIgnoreCase(elements[j].getName())) { //$NON-NLS-1$\r
+                            String id = elements[j].getAttribute("id"); //$NON-NLS-1$\r
+                            if (id != null)\r
+                                ignoredViews.add(id);\r
+                        }\r
+                    }\r
+                }\r
+            }\r
+        }\r
+        return ignoredViews;\r
+    }\r
+\r
+    private boolean isViewIgnored(String partID) {\r
+        return getIgnoredViews().contains(partID);\r
+    }\r
+\r
+    @Override\r
+    protected boolean isImportant(IWorkbenchPart part) {\r
+        String partID = part.getSite().getId();\r
+        //System.out.println("isImportant(" + partID + ")");\r
+        return !isWorkbenchSelectionPinned() && !isPropertyView(part) && !isViewIgnored(partID);\r
+    }\r
+\r
+    /**\r
+     * The <code>PropertySheet</code> implementation of this\r
+     * <code>IPartListener</code> method first sees if the active part is an\r
+     * <code>IContributedContentsView</code> adapter and if so, asks it for\r
+     * its contributing part.\r
+     */\r
+    @Override\r
+    public void partActivated(IWorkbenchPart part) {\r
+//        if (bootstrapSelection == null && bootstrapOnly)\r
+//            return;\r
+\r
+        // Look for a declaratively-contributed adapter - including not yet\r
+        // loaded adapter factories.\r
+        // See bug 86362 [PropertiesView] Can not access AdapterFactory, when\r
+        // plugin is not loaded.\r
+        IWorkbenchPart source = getSourcePart(part);\r
+        //System.out.println("PPV part activated: " + part  + ",src " + source + ",view " + this + " bss: " + bootstrapSelection + " pin " + pinSelection);\r
+        super.partActivated(source);\r
+\r
+        // When the view is first opened, pass the selection to the page\r
+        if (bootstrapSelection != null) {\r
+            IPage page = getCurrentPage();\r
+            if (page instanceof IPropertyPage) {\r
+                IPropertyPage ppage = (IPropertyPage) page;\r
+                // FIXME: should this pass source or part ??\r
+                ppage.selectionChanged(part, bootstrapSelection);\r
+                updatePartName(ppage, bootstrapSelection);\r
+            }\r
+            bootstrapSelection = null;\r
+        }\r
+    }\r
+\r
+\r
+    @Override\r
+    public void partClosed(IWorkbenchPart part) {\r
+        // Make sure that pinned view is not reset even if its originating\r
+        // editor is closed.\r
+        if (!pinSelection)\r
+            super.partClosed(part);\r
+    }\r
+\r
+    @Override\r
+    protected void partHidden(IWorkbenchPart part) {\r
+        // Fast views are quite unusable if this code is enabled.\r
+\r
+        // Make sure that pinned view is not hidden when the editor is hidden\r
+//        if(!pinSelection)\r
+//            super.partHidden(part);\r
+    }\r
+\r
+    ISelectionListener immediateSelectionListener = new ISelectionListener() {\r
+       \r
+       private Throttler throttler = new Throttler(SWTThread.getThreadAccess(PlatformUI.getWorkbench().getDisplay()), 500, 3);\r
+       \r
+        @Override\r
+        public void selectionChanged(final IWorkbenchPart part, final ISelection selection) {\r
+               \r
+               // Do not process selections from self\r
+               if(PropertyPageView.this == part) return;\r
+               \r
+            throttler.schedule(new Runnable() {\r
+\r
+                               @Override\r
+                               public void run() {\r
+                                       PropertyPageView.this.doSelectionChanged(part, selection);                              \r
+                               }\r
+               \r
+            });\r
+            \r
+        }\r
+    };\r
+\r
+    public ISelection getLastSelection() {\r
+        return lastSelection;\r
+    }\r
+\r
+    /*\r
+     * (non-Javadoc)\r
+     * \r
+     * @see org.eclipse.ui.ISelectionListener#selectionChanged(org.eclipse.ui.IWorkbenchPart,\r
+     *      org.eclipse.jface.viewers.ISelection)\r
+     */\r
+    @Override\r
+    public void selectionChanged(IWorkbenchPart part, ISelection sel) {\r
+        doSelectionChanged(part, sel);\r
+    }\r
+\r
+    /**\r
+     * @param part\r
+     * @param sel\r
+     * @return <code>true</code> if the changed selection affected the view,\r
+     *         <code>false</code> otherwise\r
+     */\r
+    boolean doSelectionChanged(IWorkbenchPart part, ISelection sel) {\r
+       \r
+        // we ignore our own selection or null selection\r
+        if (isPropertyView(part) || sel == null) {\r
+            return false;\r
+        }\r
+        // ignore workbench selections when pinned also\r
+        if (pinSelection)\r
+            return false;\r
+\r
+        // pass the selection change to the page\r
+        part = getSourcePart(part);\r
+        IPage page = getCurrentPage();\r
+        //System.out.println("PPV selection changed to (" + part + ", " + sel + "): " + page);\r
+        if (page instanceof IPropertyPage) {\r
+            IPropertyPage ppage = (IPropertyPage) page;\r
+\r
+            // Prevent parts that do not contribute a property page from messing\r
+            // up the contents/title of the currently active property page.\r
+            PageRec pageRec = getPageRec(part);\r
+            if (pageRec == null || pageRec.page != page)\r
+                return false;\r
+\r
+            // Make sure that the part name is not updated unnecessarily because\r
+            // of immediate and post selection listeners.\r
+            ISelection lastPartSelection = lastSelections.get(part);\r
+            //System.out.println("  LAST PART SELECTION(" + part + "): " + lastPartSelection);\r
+            boolean sameSelection = lastPartSelection != null && sel.equals(lastPartSelection);\r
+\r
+            if (lastPart != null) {\r
+                lastPart.removePropertyListener(partPropertyListener);\r
+            }\r
+            lastPart = part;\r
+            lastSelection = sel;\r
+            lastSelections.put(part, sel);\r
+            if (lastPart != null) {\r
+                lastPart.addPropertyListener(partPropertyListener);\r
+            }\r
+\r
+            updatePartName(ppage, sel);\r
+            if (!sameSelection) {\r
+                ppage.selectionChanged(part, sel);\r
+                return true;\r
+            }\r
+        }\r
+        return false;\r
+    }\r
+\r
+    void updatePartName(IPropertyPage ppage, ISelection sel) {\r
+        ppage.updatePartName(sel, partNameUpdateCallback);\r
+    }\r
+\r
+    Consumer<String> partNameUpdateCallback = parameter -> {\r
+        // This check is not safe - there might be a burst of changes incoming\r
+        //if (getPartName().equals(parameter)) return;\r
+        //System.out.println("partNameUpdateCallback : " + parameter);\r
+        SWTUtils.asyncExec(getPageBook(), new Runnable() {\r
+            @Override\r
+            public void run() {\r
+                if (!getPageBook().isDisposed()) {\r
+                    if (getPartName().equals(parameter)) return;\r
+                    //System.out.println("doSetParameterName : " + parameter);\r
+                    doSetPartName(parameter);\r
+                }\r
+            }\r
+        });\r
+    };\r
+\r
+    void doSetPartName(String partName) {\r
+        // Is the page view disposed ??\r
+        if (contextProvider == null)\r
+            return;\r
+        if (partName == null) {\r
+            // Return to default\r
+            partName = "Selection";\r
+        }\r
+        setPartName(partName);\r
+    }\r
+\r
+    public boolean isWorkbenchSelectionPinned() {\r
+        return pinSelection;\r
+    }\r
+\r
+    public void pinWorkbenchSelection(boolean pin) {\r
+        if (pin == pinSelection)\r
+            return;\r
+\r
+        pinSelection = pin;\r
+        setPartProperty(PROP_PINNED, Boolean.toString(pin));\r
+\r
+        if (pin) {\r
+            setTitleImage(resourceManager.createImage(pinned));\r
+        } else {\r
+            setTitleImage(resourceManager.createImage(notPinned));\r
+        }\r
+        updateContentDescription(pin, lastPart);\r
+        // Since lastPart is another PropertyView, we do not want to listen it's changes (At least current implementation is done so)\r
+        if (lastPart != null) {\r
+            lastPart.removePropertyListener(partPropertyListener);\r
+        }\r
+        lastPart = null;\r
+\r
+    }\r
+\r
+    IWorkbenchPart getSourcePart(IWorkbenchPart part) {\r
+        IContributedContentsView view = (IContributedContentsView) part.getAdapter(IContributedContentsView.class);\r
+        if (view != null) {\r
+            IWorkbenchPart source = view.getContributingPart();\r
+            if (source != null)\r
+                return source;\r
+        }\r
+        return part;\r
+    }\r
+\r
+    private void updateContentDescription(boolean selectionPinned, IWorkbenchPart sourcePart) {\r
+        if (selectionPinned) {\r
+            if (sourcePart == null) {\r
+                setContentDescription("No selection");\r
+            } else {\r
+                sourcePart = getSourcePart(sourcePart);\r
+\r
+                StringBuilder desc = new StringBuilder("Selection from ");\r
+                if (sourcePart instanceof IEditorPart)\r
+                    desc.append("editor ");\r
+                if (sourcePart instanceof IViewPart)\r
+                    desc.append("view ");\r
+                desc.append('\'');\r
+                desc.append(sourcePart.getTitle());\r
+                desc.append('\'');\r
+\r
+                setContentDescription(desc.toString());\r
+            }\r
+        } else {\r
+            setContentDescription("");\r
+        }\r
+    }\r
+\r
+    IPropertyListener partPropertyListener = new IPropertyListener() {\r
+        @Override\r
+        public void propertyChanged(Object source, int propId) {\r
+            if (propId == IWorkbenchPart.PROP_TITLE) {\r
+                updateContentDescription(pinSelection, lastPart);\r
+            }\r
+        }\r
+    };\r
+\r
+    @Override\r
+    public IWorkbenchPart getContributingPart() {\r
+        return lastPart;\r
+    }\r
+\r
+}\r