X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.selectionview%2Fsrc%2Forg%2Fsimantics%2Fselectionview%2FTabbedPropertyTable.java;fp=bundles%2Forg.simantics.selectionview%2Fsrc%2Forg%2Fsimantics%2Fselectionview%2FTabbedPropertyTable.java;h=4f7ae3edcf976e8520b272d466480de3e992b869;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git 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 index 000000000..4f7ae3edc --- /dev/null +++ b/bundles/org.simantics.selectionview/src/org/simantics/selectionview/TabbedPropertyTable.java @@ -0,0 +1,558 @@ +/******************************************************************************* + * 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; + +/** + *

+ * Subclasses may extend or reimplement the following methods as required: + *

+ *

+ * + * @author Tuukka Lehtonen + */ +public class TabbedPropertyTable extends Composite implements IPropertyTab { + + public static final SelectionProcessor DEFAULT_SELECTION_PROCESSOR = new SelectionProcessor() { + @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 tabs = new CopyOnWriteArrayList(); + + 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> { + + final private Consumer> inputCallback; + private boolean disposed = false; + + public InputListener(Consumer> 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 true 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 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>() { + @Override + public Collection perform(ReadGraph graph) throws DatabaseException { + //System.out.println("TabbedPropertyTable.setInput.perform(" + contents + ")"); + return selectionProcessor.process(contents, graph); + } + }, currentListener); + + } + }); + + } + } + + protected Consumer> inputCallback(final Collection selectionContents, final ISessionContext sessionContext) { + return new Consumer>() { + @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 ml = new MapList(); + 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 contributions = new TreeSet(); + for(String key : ml.getKeys()) { + TreeSet ts = new TreeSet(ml.getValuesUnsafe(key)); + ComparableTabContributor contrib = ts.first(); + contributions.add(contrib); + } + + // Sort contributions by id + List contributionList = new ArrayList(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); + + // +// parent.setBackground(getDisplay().getSystemColor(SWT.COLOR_RED)); +// baseComposite.setBackground(getDisplay().getSystemColor(SWT.COLOR_TITLE_INACTIVE_BACKGROUND)); + // + + 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 selectionContents, Collection contributions) { + return null; + } + + /** + * @return + */ + protected int getTabFolderStyle() { + return SWT.NONE; + } + +}