]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.selectionview/src/org/simantics/selectionview/TabbedPropertyTable.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.selectionview / src / org / simantics / selectionview / TabbedPropertyTable.java
diff --git a/bundles/org.simantics.selectionview/src/org/simantics/selectionview/TabbedPropertyTable.java b/bundles/org.simantics.selectionview/src/org/simantics/selectionview/TabbedPropertyTable.java
new file mode 100644 (file)
index 0000000..4f7ae3e
--- /dev/null
@@ -0,0 +1,558 @@
+/*******************************************************************************\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.selectionview;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.List;\r
+import java.util.TreeSet;\r
+import java.util.concurrent.CopyOnWriteArrayList;\r
+import java.util.concurrent.atomic.AtomicBoolean;\r
+import java.util.concurrent.atomic.AtomicInteger;\r
+import java.util.function.Consumer;\r
+\r
+import org.eclipse.jface.layout.GridDataFactory;\r
+import org.eclipse.jface.layout.GridLayoutFactory;\r
+import org.eclipse.jface.resource.JFaceResources;\r
+import org.eclipse.jface.resource.LocalResourceManager;\r
+import org.eclipse.jface.viewers.ISelection;\r
+import org.eclipse.jface.viewers.ISelectionChangedListener;\r
+import org.eclipse.jface.viewers.ISelectionProvider;\r
+import org.eclipse.jface.viewers.SelectionChangedEvent;\r
+import org.eclipse.jface.viewers.StructuredSelection;\r
+import org.eclipse.swt.SWT;\r
+import org.eclipse.swt.layout.FillLayout;\r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.swt.widgets.Control;\r
+import org.eclipse.swt.widgets.Event;\r
+import org.eclipse.swt.widgets.Listener;\r
+import org.eclipse.ui.IWorkbenchPartSite;\r
+import org.eclipse.ui.part.IPageSite;\r
+import org.simantics.browsing.ui.common.ErrorLogger;\r
+import org.simantics.browsing.ui.swt.TabbedPropertyPage;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.common.request.ReadRequest;\r
+import org.simantics.db.common.request.UniqueRead;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.management.ISessionContext;\r
+import org.simantics.ui.SimanticsUI;\r
+import org.simantics.utils.ObjectUtils;\r
+import org.simantics.utils.datastructures.MapList;\r
+import org.simantics.utils.ui.ISelectionUtils;\r
+import org.simantics.utils.ui.jface.BasePostSelectionProvider;\r
+\r
+/**\r
+ * <p>\r
+ * Subclasses may extend or reimplement the following methods as required:\r
+ * <ul>\r
+ * <li><code>createBaseComposite</code> - reimplement to customize how the\r
+ * composite is constructed that is the basis of the whole tab container.</\r
+ * <li>\r
+ * </ul>\r
+ * </p>\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class TabbedPropertyTable extends Composite implements IPropertyTab {\r
+\r
+    public static final SelectionProcessor<Object, Object> DEFAULT_SELECTION_PROCESSOR = new SelectionProcessor<Object, Object>() {\r
+        @Override\r
+        public Collection<?> process(Object selection, Object object) {\r
+            return Collections.emptyList();\r
+        }\r
+    };\r
+\r
+    @SuppressWarnings("rawtypes")\r
+    private SelectionProcessor                 selectionProcessor          = DEFAULT_SELECTION_PROCESSOR;\r
+\r
+    private Composite                          baseComposite;\r
+\r
+    private final List<IPropertyTab>           tabs                        = new CopyOnWriteArrayList<IPropertyTab>();\r
+\r
+    private final AtomicInteger                activeTab                   = new AtomicInteger(-1);\r
+\r
+    protected IWorkbenchPartSite               sourceSite;\r
+\r
+    protected IPageSite                        pageSite;\r
+\r
+    protected ISelection                       currentSelection;\r
+\r
+    /**\r
+     * The selection provider set for the page site.\r
+     */\r
+    protected BasePostSelectionProvider        pageSelectionProvider = new BasePostSelectionProvider() {\r
+\r
+        /**\r
+         * For preventing infinite recursion, not that it should happen. \r
+         */\r
+        private AtomicBoolean settingSelection = new AtomicBoolean();\r
+\r
+        /**\r
+         * Overridden like this because pageSelectionProvider is published to\r
+         * the workbench as the page site selection provider and therefore it\r
+         * possible that its {@link ISelectionProvider#setSelection(ISelection)}\r
+         * method is invoked externally and we need to propagate the selection\r
+         * to the underlying active tab and its selection provider instead of\r
+         * setting pageSelectionProvider's selection to anything.\r
+         */\r
+        @Override\r
+        public void setSelection(ISelection selection) {\r
+            if (settingSelection.compareAndSet(false, true)) {\r
+                IPropertyTab table = getActiveTab();\r
+                if (table != null && table.getSelectionProvider() != null)\r
+                    table.getSelectionProvider().setSelection(selection);\r
+                settingSelection.set(false);\r
+            } else {\r
+                ErrorLogger.defaultLogWarning("Possible BUG: prevented recursive attempt to set selection for "\r
+                        + TabbedPropertyTable.this.toString(), new Exception("trace"));\r
+            }\r
+        }\r
+    };\r
+\r
+//    protected ISelectionChangedListener        debugPageSelectionListener = new ISelectionChangedListener() {\r
+//        {\r
+//            pageSelectionProvider.addSelectionChangedListener(this);\r
+//        }\r
+//        @Override\r
+//        public void selectionChanged(SelectionChangedEvent event) {\r
+//            System.out.println("page selection change: " + event);\r
+//            System.out.println("    provider: " + event.getSelectionProvider());\r
+//            System.out.println("    selection: " + event.getSelection());\r
+//        }\r
+//    };\r
+\r
+    protected ISelectionChangedListener        activeTabSelectionListener = new ISelectionChangedListener() {\r
+        @Override\r
+        public void selectionChanged(SelectionChangedEvent event) {\r
+//            System.out.println("active tab selection change: " + event);\r
+//            System.out.println("    provider: " + event.getSelectionProvider());\r
+//            System.out.println("    selection: " + event.getSelection());\r
+            ISelection s = event.getSelection();\r
+            // This is a workaround to avert calling pageSelectionProvider.setSelection here.\r
+            pageSelectionProvider.setSelectionWithoutFiring(s);\r
+            pageSelectionProvider.fireSelection(s);\r
+            pageSelectionProvider.firePostSelection(s);\r
+        }\r
+    };\r
+\r
+    protected LocalResourceManager             resourceManager;\r
+\r
+    public TabbedPropertyTable(IWorkbenchPartSite site, IPageSite pageSite, Composite parent, int style) {\r
+        super(parent, style);\r
+        if (site == null)\r
+            throw new IllegalArgumentException("null source site");\r
+        if (pageSite == null)\r
+            throw new IllegalArgumentException("null page site");\r
+        this.sourceSite = site;\r
+        this.pageSite = pageSite;\r
+        GridLayoutFactory.fillDefaults().applyTo(this);\r
+\r
+        resourceManager = new LocalResourceManager(JFaceResources.getResources(parent.getDisplay()));\r
+\r
+        addListener(SWT.Dispose, new Listener() {\r
+            @Override\r
+            public void handleEvent(Event event) {\r
+                //System.out.println("DISPOSING " + this + " " + System.identityHashCode(TabbedPropertyTable.this));\r
+                activeTab.set(-1);\r
+                tabs.clear();\r
+\r
+                currentSelection = null;\r
+                if (currentListener != null)\r
+                    currentListener.dispose();\r
+\r
+                TabbedPropertyTable.this.pageSite = null;\r
+                TabbedPropertyTable.this.sourceSite = null;\r
+                resourceManager.dispose();\r
+                \r
+            }\r
+        });\r
+    }\r
+\r
+    @SuppressWarnings("rawtypes")\r
+    protected void setSelectionProcessor(SelectionProcessor selectionProcessor) {\r
+        this.selectionProcessor = selectionProcessor;\r
+    }\r
+\r
+    @Override\r
+    public void createControl(Composite parent, ISessionContext context) {\r
+        createBaseComposite(parent, null);\r
+    }\r
+\r
+    class InputListener implements org.simantics.db.procedure.Listener<Collection<?>> {\r
+\r
+        final private Consumer<Collection<?>> inputCallback;\r
+        private boolean disposed = false;\r
+\r
+        public InputListener(Consumer<Collection<?>> inputCallback) {\r
+            this.inputCallback = inputCallback;\r
+        }\r
+\r
+        @Override\r
+        public void exception(Throwable t) {\r
+            ErrorLogger.defaultLogError(t);\r
+        }\r
+\r
+        @Override\r
+        public void execute(Collection<?> result) {\r
+            inputCallback.accept(result);\r
+        }\r
+\r
+        @Override\r
+        public boolean isDisposed() {\r
+            return disposed || TabbedPropertyTable.this.isDisposed();\r
+        }\r
+\r
+        public void dispose() {\r
+            disposed = true;\r
+        }\r
+\r
+    }\r
+\r
+    InputListener currentListener = null;\r
+\r
+    /**\r
+     * Must be invoked from the SWT UI thread.\r
+     * \r
+     * @param selection the new selection\r
+     * @param force <code>true</code> to force the resetting of the new input\r
+     *        even if it is the same as the previous one.\r
+     */\r
+    @Override\r
+    @SuppressWarnings("unchecked")\r
+    public void setInput(ISessionContext context, ISelection selection, boolean force) {\r
+        //System.out.println(hashCode() + "# TabbedPropertyTable.setInput(" + selection + ", " + force + ")");\r
+        if (isDisposed())\r
+            return;\r
+        if (context == null)\r
+            return;\r
+\r
+        // Check if this is a duplicate of the previous selection to reduce unnecessary flicker.\r
+        if (!force && ObjectUtils.objectEquals(currentSelection, selection))\r
+            return;\r
+\r
+//        System.out.println("[3] setInput " + selection + ", force=" + force);\r
+        currentSelection = selection;\r
+\r
+        if (selectionProcessor != null) {\r
+               \r
+            if (currentListener != null) currentListener.dispose();\r
+               \r
+            final Collection<Object> contents = ISelectionUtils.convertSelection(selection);\r
+            if (contents.isEmpty())\r
+                return;\r
+\r
+            currentListener = new InputListener(inputCallback(contents, context));\r
+\r
+            // NOTE: must be an anonymous read to guarantee that each request\r
+            // will always be performed and not taken from DB caches.\r
+            context.getSession().asyncRequest(new ReadRequest() {\r
+               \r
+                @Override\r
+                public void run(ReadGraph graph) throws DatabaseException {\r
+                       \r
+                       graph.syncRequest(new UniqueRead<Collection<?>>() {\r
+                        @Override\r
+                        public Collection<?> perform(ReadGraph graph) throws DatabaseException {\r
+                               //System.out.println("TabbedPropertyTable.setInput.perform(" + contents + ")");\r
+                               return selectionProcessor.process(contents, graph);\r
+                        }\r
+                       }, currentListener);\r
+                       \r
+                }\r
+            });\r
+            \r
+        }\r
+    }\r
+\r
+    protected Consumer<Collection<?>> inputCallback(final Collection<Object> selectionContents, final ISessionContext sessionContext) {\r
+        return new Consumer<Collection<?>>() {\r
+            @Override\r
+            public void accept(final Collection<?> contribs) {\r
+                \r
+                if (isDisposed())\r
+                    return;\r
+//                if (contribs.isEmpty())\r
+//                    return;\r
+\r
+                SimanticsUI.asyncExecSWT(TabbedPropertyTable.this, new Runnable() {\r
+                    \r
+                    public void run() {\r
+                        \r
+                        //System.out.println(Thread.currentThread() + " inputCallback: " + input);\r
+                        //System.out.println("is TabbedPropertyTable " + this + " visible: " + isVisible());\r
+                        if (!isVisible()) {\r
+                            // Set current selection to null to force update when the\r
+                            // page becomes visible\r
+                            currentSelection = null;\r
+                            return;\r
+                        }\r
+\r
+                        createBaseComposite(TabbedPropertyTable.this, new TabbedPropertyPage(sourceSite.getPart()) {\r
+\r
+                            /**\r
+                             * The selection providers for the current set of property tabs.\r
+                             */\r
+                            ISelectionProvider[] tabSelectionProviders = { null };\r
+\r
+                            @Override\r
+                            protected void initializePageSwitching() {\r
+                                // Overridden to prevent TabbedPropertyPage\r
+                                // from initializing PageSwitcher \r
+                            }\r
+\r
+                            @Override\r
+                            protected int getContainerStyle() {\r
+                                return TabbedPropertyTable.this.getTabFolderStyle();\r
+                            }\r
+\r
+                            @Override\r
+                            protected void pageChange(int newPageIndex) {\r
+                                int oldActiveTab = activeTab.getAndSet(newPageIndex);\r
+                                //System.out.println(Thread.currentThread() + " page changed: from " + oldActiveTab + " to " + newPageIndex);\r
+                                super.pageChange(newPageIndex);\r
+\r
+                                // Remove possible old selection listeners from the hidden tab.\r
+                                ISelection oldSelection = null;\r
+                                IPropertyTab oldTab = null;\r
+                                if (oldActiveTab > -1) {\r
+                                    oldTab = tabs.get(oldActiveTab);\r
+                                    ISelectionProvider pv = oldTab.getSelectionProvider();\r
+                                    if (pv != null) {\r
+                                        oldSelection = pv.getSelection();\r
+                                        pv.removeSelectionChangedListener(activeTabSelectionListener);\r
+                                    }\r
+                                }\r
+\r
+                                // Attach selection listeners to the activated tab if possible.\r
+                                ISelection activeSelection = null;\r
+                                IPropertyTab activeTab = getActiveTab();\r
+                                if (activeTab != null) {\r
+                                    ISelectionProvider pv = activeTab.getSelectionProvider();\r
+                                    if (pv != null) {\r
+                                        activeSelection = pv.getSelection();\r
+                                        pv.addSelectionChangedListener(activeTabSelectionListener);\r
+                                    }\r
+                                }\r
+\r
+                                String oldLabel = null;\r
+                                String newLabel = null;\r
+                                if (oldActiveTab > -1)\r
+                                    oldLabel = getPageText(oldActiveTab);\r
+                                if (newPageIndex > -1)\r
+                                    newLabel = getPageText(newPageIndex);\r
+                                activeTabChanged(new TabChangeEvent(oldTab, oldLabel, activeTab, newLabel));\r
+\r
+                                // This is a workaround to avert calling pageSelectionProvider.setSelection here.\r
+                                pageSelectionProvider.setSelectionWithoutFiring(activeSelection);\r
+                                if (!ObjectUtils.objectEquals(oldSelection, activeSelection)) {\r
+                                    pageSelectionProvider.fireSelection(activeSelection);\r
+                                    pageSelectionProvider.firePostSelection(activeSelection);\r
+                                }\r
+                            }\r
+\r
+                            @Override\r
+                            protected void createPages() {\r
+                               \r
+                                // 1. loop through a list of possible contributors\r
+                                // 2. list the ones that can contribute, in priority order told by the contributions themselves\r
+                                // 3. add pages in priority order\r
+                                // 4. initialize pages\r
+                               \r
+                               // Categorize contributions by id\r
+                                MapList<String,ComparableTabContributor> ml = new MapList<String,ComparableTabContributor>();\r
+                                for (Object o : contribs) {\r
+                                    if (o instanceof ComparableTabContributor) {\r
+                                       ComparableTabContributor c = (ComparableTabContributor) o; \r
+                                       ml.add(c.getId(), c);\r
+                                    } else {\r
+                                        System.out.println("WARNING: SelectionProcessor produced an unusable contribution to TabbedPropertyTable: " + o);\r
+                                    }\r
+                                }\r
+\r
+                               // For each id take the greatest (id) contribution\r
+                                Collection<ComparableTabContributor> contributions = new TreeSet<ComparableTabContributor>();\r
+                                for(String key : ml.getKeys()) {\r
+                                       TreeSet<ComparableTabContributor> ts = new TreeSet<ComparableTabContributor>(ml.getValuesUnsafe(key));\r
+                                       ComparableTabContributor contrib = ts.first();\r
+                                       contributions.add(contrib);\r
+                                }\r
+\r
+                                // Sort contributions by id\r
+                                List<ComparableTabContributor> contributionList = new ArrayList<ComparableTabContributor>(contributions);\r
+\r
+                                if (contributions.isEmpty()) {\r
+                                    Composite empty = createEmptyControl(getContainer(), SWT.NONE);\r
+                                    addPage(empty, "No properties to show", null);\r
+                                } else {\r
+                                    // Set single selection provider into pageSite\r
+                                    // that is dispatched every time the selection\r
+                                    // in the property view changes, be it because\r
+                                    // the selection changed in the current tab or\r
+                                    // because the active tab, i.e. the selection\r
+                                    // provider is changed.\r
+                                    pageSite.setSelectionProvider(pageSelectionProvider);\r
+\r
+                                    // Allocate space for each tab to specify\r
+                                    // its own selection provider.\r
+                                    tabSelectionProviders = new ISelectionProvider[contributions.size()];\r
+                                    int index = 0;\r
+\r
+                                    for (ComparableTabContributor cc : contributionList) {\r
+                                        Composite middleware = new Composite(getContainer(), SWT.NONE);\r
+                                        GridLayoutFactory.fillDefaults().applyTo(middleware);\r
+\r
+                                        Control control = null;\r
+\r
+                                        // Create a wrapper for pageSite to make it possible\r
+                                        // for each tab contribution to specify its own\r
+                                        // selection provider.\r
+                                        final int tabIndex = index++;\r
+                                        IPageSite siteWrapper = new PageSiteProxy(pageSite) {\r
+                                            @Override\r
+                                            public void setSelectionProvider(ISelectionProvider provider) {\r
+                                                tabSelectionProviders[tabIndex] = provider;\r
+                                                if(pageSelectionProvider != null && provider != null)\r
+                                                       pageSelectionProvider.setAndFireNonEqualSelection(provider.getSelection());\r
+                                            }\r
+                                            @Override\r
+                                            public ISelectionProvider getSelectionProvider() {\r
+                                                return tabSelectionProviders[tabIndex];\r
+                                            }\r
+                                        };\r
+\r
+                                        IPropertyTab tab = cc.create(middleware, siteWrapper, sessionContext, cc.getInput());\r
+                                        if (tab != null) {\r
+                                            tabs.add(tab);\r
+                                            control = tab.getControl();\r
+                                            if(control != null && !control.isDisposed()) GridDataFactory.fillDefaults().grab(true, true).applyTo(control);\r
+                                        } else {\r
+                                            control = createEmptyControl(middleware, SWT.NONE);\r
+//                                            Button b = new Button(middleware, SWT.BORDER);\r
+//                                            GridDataFactory.fillDefaults().grab(true, true).applyTo(b);\r
+//                                            b.setText("FOO");\r
+                                        }\r
+\r
+                                        addPage(middleware, cc.getLabel(), cc.getImage());\r
+\r
+                                        if (tab != null) {\r
+                                            Object input = cc.getInput();\r
+                                            tab.setInput(sessionContext, new StructuredSelection(input), false);\r
+                                        }\r
+                                    }\r
+\r
+                                    ComparableTabContributor cc = TabbedPropertyTable.this.selectInitialTab(selectionContents, contributionList);\r
+                                    if (cc != null) {\r
+                                        int activePage = contributionList.indexOf(cc);\r
+                                        if (activePage != -1)\r
+                                            setActivePage(activePage);\r
+                                    }\r
+                                }\r
+                            }\r
+\r
+                            Composite createEmptyControl(Composite parent, int flags) {\r
+                                Composite empty = new Composite(parent, flags);\r
+                                GridLayoutFactory.fillDefaults().applyTo(empty);\r
+                                return empty;\r
+                            }\r
+                        });\r
+                        layout();\r
+                    }\r
+                });\r
+            }\r
+        };\r
+    }\r
+\r
+    @Override\r
+    public Control getControl() {\r
+        return this;\r
+    }\r
+\r
+    @Override\r
+    public void requestFocus() {\r
+        IPropertyTab table = getActiveTab();\r
+        if (table != null)\r
+            table.requestFocus();\r
+    }\r
+\r
+    public IPropertyTab getActiveTab() {\r
+        int index = activeTab.get();\r
+        if (index < 0 || index >= tabs.size())\r
+            return null;\r
+        IPropertyTab tab = tabs.get(index);\r
+        return tab;\r
+    }\r
+\r
+    protected Composite createBaseComposite(final Composite parent, TabbedPropertyPage page) {\r
+        // 1. dispose the previous UI container\r
+        disposeAll();\r
+\r
+        // 2. construct new base for the tabbed property UI\r
+        baseComposite = new Composite(parent, SWT.NONE);\r
+\r
+        // <DEBUG>\r
+//        parent.setBackground(getDisplay().getSystemColor(SWT.COLOR_RED));\r
+//        baseComposite.setBackground(getDisplay().getSystemColor(SWT.COLOR_TITLE_INACTIVE_BACKGROUND));\r
+        // </DEBUG>\r
+\r
+        if (page != null)\r
+            page.createPartControl(baseComposite);\r
+\r
+        GridLayoutFactory.fillDefaults().applyTo(parent);\r
+        GridDataFactory.fillDefaults().grab(true, true).applyTo(baseComposite);\r
+        baseComposite.setLayout(new FillLayout());\r
+\r
+        return baseComposite;\r
+    }\r
+\r
+    private void disposeAll() {\r
+        if (baseComposite != null && !baseComposite.isDisposed()) {\r
+            baseComposite.dispose();\r
+            baseComposite = null;\r
+        }\r
+        activeTab.set(-1);\r
+        for (IPropertyTab tab : tabs)\r
+            tab.dispose();\r
+        tabs.clear();\r
+    }\r
+\r
+    @Override\r
+    public ISelectionProvider getSelectionProvider() {\r
+        return pageSelectionProvider;\r
+    }\r
+\r
+    /**\r
+     * Override to perform actions when the active tab changes.\r
+     *\r
+     * @param previousTab\r
+     * @param activeTab\r
+     */\r
+    protected void activeTabChanged(TabChangeEvent event) {\r
+    }\r
+\r
+    /**\r
+     * @param selectionContents \r
+     * @param contributions\r
+     * @return\r
+     */\r
+    protected ComparableTabContributor selectInitialTab(Collection<Object> selectionContents, Collection<ComparableTabContributor> contributions) {\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * @return\r
+     */\r
+    protected int getTabFolderStyle() {\r
+        return SWT.NONE;\r
+    }\r
+\r
+}\r