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