-/*******************************************************************************\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;
+ }
+
+}