1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.selectionview;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.Collections;
17 import java.util.List;
18 import java.util.TreeSet;
19 import java.util.concurrent.CopyOnWriteArrayList;
20 import java.util.concurrent.atomic.AtomicBoolean;
21 import java.util.concurrent.atomic.AtomicInteger;
22 import java.util.function.Consumer;
24 import org.eclipse.jface.layout.GridDataFactory;
25 import org.eclipse.jface.layout.GridLayoutFactory;
26 import org.eclipse.jface.resource.JFaceResources;
27 import org.eclipse.jface.resource.LocalResourceManager;
28 import org.eclipse.jface.viewers.ISelection;
29 import org.eclipse.jface.viewers.ISelectionChangedListener;
30 import org.eclipse.jface.viewers.ISelectionProvider;
31 import org.eclipse.jface.viewers.SelectionChangedEvent;
32 import org.eclipse.jface.viewers.StructuredSelection;
33 import org.eclipse.swt.SWT;
34 import org.eclipse.swt.layout.FillLayout;
35 import org.eclipse.swt.widgets.Composite;
36 import org.eclipse.swt.widgets.Control;
37 import org.eclipse.swt.widgets.Event;
38 import org.eclipse.swt.widgets.Listener;
39 import org.eclipse.ui.IWorkbenchPartSite;
40 import org.eclipse.ui.part.IPageSite;
41 import org.simantics.browsing.ui.common.ErrorLogger;
42 import org.simantics.browsing.ui.swt.TabbedPropertyPage;
43 import org.simantics.db.ReadGraph;
44 import org.simantics.db.common.request.ReadRequest;
45 import org.simantics.db.common.request.UniqueRead;
46 import org.simantics.db.exception.DatabaseException;
47 import org.simantics.db.management.ISessionContext;
48 import org.simantics.ui.SimanticsUI;
49 import org.simantics.utils.ObjectUtils;
50 import org.simantics.utils.datastructures.MapList;
51 import org.simantics.utils.ui.ISelectionUtils;
52 import org.simantics.utils.ui.jface.BasePostSelectionProvider;
56 * Subclasses may extend or reimplement the following methods as required:
58 * <li><code>createBaseComposite</code> - reimplement to customize how the
59 * composite is constructed that is the basis of the whole tab container.</
64 * @author Tuukka Lehtonen
66 public class TabbedPropertyTable extends Composite implements IPropertyTab {
68 public static final SelectionProcessor<Object, Object> DEFAULT_SELECTION_PROCESSOR = new SelectionProcessor<Object, Object>() {
70 public Collection<?> process(Object selection, Object object) {
71 return Collections.emptyList();
75 @SuppressWarnings("rawtypes")
76 private SelectionProcessor selectionProcessor = DEFAULT_SELECTION_PROCESSOR;
78 private Composite baseComposite;
80 private final List<IPropertyTab> tabs = new CopyOnWriteArrayList<IPropertyTab>();
82 private final AtomicInteger activeTab = new AtomicInteger(-1);
84 protected IWorkbenchPartSite sourceSite;
86 protected IPageSite pageSite;
88 protected ISelection currentSelection;
91 * The selection provider set for the page site.
93 protected BasePostSelectionProvider pageSelectionProvider = new BasePostSelectionProvider() {
96 * For preventing infinite recursion, not that it should happen.
98 private AtomicBoolean settingSelection = new AtomicBoolean();
101 * Overridden like this because pageSelectionProvider is published to
102 * the workbench as the page site selection provider and therefore it
103 * possible that its {@link ISelectionProvider#setSelection(ISelection)}
104 * method is invoked externally and we need to propagate the selection
105 * to the underlying active tab and its selection provider instead of
106 * setting pageSelectionProvider's selection to anything.
109 public void setSelection(ISelection selection) {
110 if (settingSelection.compareAndSet(false, true)) {
111 IPropertyTab table = getActiveTab();
112 if (table != null && table.getSelectionProvider() != null)
113 table.getSelectionProvider().setSelection(selection);
114 settingSelection.set(false);
116 ErrorLogger.defaultLogWarning("Possible BUG: prevented recursive attempt to set selection for "
117 + TabbedPropertyTable.this.toString(), new Exception("trace"));
122 // protected ISelectionChangedListener debugPageSelectionListener = new ISelectionChangedListener() {
124 // pageSelectionProvider.addSelectionChangedListener(this);
127 // public void selectionChanged(SelectionChangedEvent event) {
128 // System.out.println("page selection change: " + event);
129 // System.out.println(" provider: " + event.getSelectionProvider());
130 // System.out.println(" selection: " + event.getSelection());
134 protected ISelectionChangedListener activeTabSelectionListener = new ISelectionChangedListener() {
136 public void selectionChanged(SelectionChangedEvent event) {
137 // System.out.println("active tab selection change: " + event);
138 // System.out.println(" provider: " + event.getSelectionProvider());
139 // System.out.println(" selection: " + event.getSelection());
140 ISelection s = event.getSelection();
141 // This is a workaround to avert calling pageSelectionProvider.setSelection here.
142 pageSelectionProvider.setSelectionWithoutFiring(s);
143 pageSelectionProvider.fireSelection(s);
144 pageSelectionProvider.firePostSelection(s);
148 protected LocalResourceManager resourceManager;
150 public TabbedPropertyTable(IWorkbenchPartSite site, IPageSite pageSite, Composite parent, int style) {
151 super(parent, style);
153 throw new IllegalArgumentException("null source site");
154 if (pageSite == null)
155 throw new IllegalArgumentException("null page site");
156 this.sourceSite = site;
157 this.pageSite = pageSite;
158 GridLayoutFactory.fillDefaults().applyTo(this);
160 resourceManager = new LocalResourceManager(JFaceResources.getResources(parent.getDisplay()));
162 addListener(SWT.Dispose, new Listener() {
164 public void handleEvent(Event event) {
165 //System.out.println("DISPOSING " + this + " " + System.identityHashCode(TabbedPropertyTable.this));
169 currentSelection = null;
170 if (currentListener != null)
171 currentListener.dispose();
173 TabbedPropertyTable.this.pageSite = null;
174 TabbedPropertyTable.this.sourceSite = null;
175 resourceManager.dispose();
181 @SuppressWarnings("rawtypes")
182 protected void setSelectionProcessor(SelectionProcessor selectionProcessor) {
183 this.selectionProcessor = selectionProcessor;
187 public void createControl(Composite parent, ISessionContext context) {
188 createBaseComposite(parent, null);
191 class InputListener implements org.simantics.db.procedure.Listener<Collection<?>> {
193 final private Consumer<Collection<?>> inputCallback;
194 private boolean disposed = false;
196 public InputListener(Consumer<Collection<?>> inputCallback) {
197 this.inputCallback = inputCallback;
201 public void exception(Throwable t) {
202 ErrorLogger.defaultLogError(t);
206 public void execute(Collection<?> result) {
207 inputCallback.accept(result);
211 public boolean isDisposed() {
212 return disposed || TabbedPropertyTable.this.isDisposed();
215 public void dispose() {
221 InputListener currentListener = null;
224 * Must be invoked from the SWT UI thread.
226 * @param selection the new selection
227 * @param force <code>true</code> to force the resetting of the new input
228 * even if it is the same as the previous one.
231 @SuppressWarnings("unchecked")
232 public void setInput(ISessionContext context, ISelection selection, boolean force) {
233 //System.out.println(hashCode() + "# TabbedPropertyTable.setInput(" + selection + ", " + force + ")");
239 // Check if this is a duplicate of the previous selection to reduce unnecessary flicker.
240 if (!force && ObjectUtils.objectEquals(currentSelection, selection))
243 // System.out.println("[3] setInput " + selection + ", force=" + force);
244 currentSelection = selection;
246 if (selectionProcessor != null) {
248 if (currentListener != null) currentListener.dispose();
250 final Collection<Object> contents = ISelectionUtils.convertSelection(selection);
251 if (contents.isEmpty())
254 currentListener = new InputListener(inputCallback(contents, context));
256 // NOTE: must be an anonymous read to guarantee that each request
257 // will always be performed and not taken from DB caches.
258 context.getSession().asyncRequest(new ReadRequest() {
261 public void run(ReadGraph graph) throws DatabaseException {
263 graph.syncRequest(new UniqueRead<Collection<?>>() {
265 public Collection<?> perform(ReadGraph graph) throws DatabaseException {
266 //System.out.println("TabbedPropertyTable.setInput.perform(" + contents + ")");
267 return selectionProcessor.process(contents, graph);
277 protected Consumer<Collection<?>> inputCallback(final Collection<Object> selectionContents, final ISessionContext sessionContext) {
278 return new Consumer<Collection<?>>() {
280 public void accept(final Collection<?> contribs) {
284 // if (contribs.isEmpty())
287 SimanticsUI.asyncExecSWT(TabbedPropertyTable.this, new Runnable() {
291 //System.out.println(Thread.currentThread() + " inputCallback: " + input);
292 //System.out.println("is TabbedPropertyTable " + this + " visible: " + isVisible());
294 // Set current selection to null to force update when the
295 // page becomes visible
296 currentSelection = null;
300 createBaseComposite(TabbedPropertyTable.this, new TabbedPropertyPage(sourceSite.getPart()) {
303 * The selection providers for the current set of property tabs.
305 ISelectionProvider[] tabSelectionProviders = { null };
308 protected void initializePageSwitching() {
309 // Overridden to prevent TabbedPropertyPage
310 // from initializing PageSwitcher
314 protected int getContainerStyle() {
315 return TabbedPropertyTable.this.getTabFolderStyle();
319 protected void pageChange(int newPageIndex) {
320 int oldActiveTab = activeTab.getAndSet(newPageIndex);
321 //System.out.println(Thread.currentThread() + " page changed: from " + oldActiveTab + " to " + newPageIndex);
322 super.pageChange(newPageIndex);
324 // Remove possible old selection listeners from the hidden tab.
325 ISelection oldSelection = null;
326 IPropertyTab oldTab = null;
327 if (oldActiveTab > -1) {
328 oldTab = tabs.get(oldActiveTab);
329 ISelectionProvider pv = oldTab.getSelectionProvider();
331 oldSelection = pv.getSelection();
332 pv.removeSelectionChangedListener(activeTabSelectionListener);
336 // Attach selection listeners to the activated tab if possible.
337 ISelection activeSelection = null;
338 IPropertyTab activeTab = getActiveTab();
339 if (activeTab != null) {
340 ISelectionProvider pv = activeTab.getSelectionProvider();
342 activeSelection = pv.getSelection();
343 pv.addSelectionChangedListener(activeTabSelectionListener);
347 String oldLabel = null;
348 String newLabel = null;
349 if (oldActiveTab > -1)
350 oldLabel = getPageText(oldActiveTab);
351 if (newPageIndex > -1)
352 newLabel = getPageText(newPageIndex);
353 activeTabChanged(new TabChangeEvent(oldTab, oldLabel, activeTab, newLabel));
355 // This is a workaround to avert calling pageSelectionProvider.setSelection here.
356 pageSelectionProvider.setSelectionWithoutFiring(activeSelection);
357 if (!ObjectUtils.objectEquals(oldSelection, activeSelection)) {
358 pageSelectionProvider.fireSelection(activeSelection);
359 pageSelectionProvider.firePostSelection(activeSelection);
364 protected void createPages() {
366 // 1. loop through a list of possible contributors
367 // 2. list the ones that can contribute, in priority order told by the contributions themselves
368 // 3. add pages in priority order
369 // 4. initialize pages
371 // Categorize contributions by id
372 MapList<String,ComparableTabContributor> ml = new MapList<String,ComparableTabContributor>();
373 for (Object o : contribs) {
374 if (o instanceof ComparableTabContributor) {
375 ComparableTabContributor c = (ComparableTabContributor) o;
376 ml.add(c.getId(), c);
378 System.out.println("WARNING: SelectionProcessor produced an unusable contribution to TabbedPropertyTable: " + o);
382 // For each id take the greatest (id) contribution
383 Collection<ComparableTabContributor> contributions = new TreeSet<ComparableTabContributor>();
384 for(String key : ml.getKeys()) {
385 TreeSet<ComparableTabContributor> ts = new TreeSet<ComparableTabContributor>(ml.getValuesUnsafe(key));
386 ComparableTabContributor contrib = ts.first();
387 contributions.add(contrib);
390 // Sort contributions by id
391 List<ComparableTabContributor> contributionList = new ArrayList<ComparableTabContributor>(contributions);
393 if (contributions.isEmpty()) {
394 Composite empty = createEmptyControl(getContainer(), SWT.NONE);
395 addPage(empty, "No properties to show", null);
397 // Set single selection provider into pageSite
398 // that is dispatched every time the selection
399 // in the property view changes, be it because
400 // the selection changed in the current tab or
401 // because the active tab, i.e. the selection
402 // provider is changed.
403 pageSite.setSelectionProvider(pageSelectionProvider);
405 // Allocate space for each tab to specify
406 // its own selection provider.
407 tabSelectionProviders = new ISelectionProvider[contributions.size()];
410 for (ComparableTabContributor cc : contributionList) {
411 Composite middleware = new Composite(getContainer(), SWT.NONE);
412 GridLayoutFactory.fillDefaults().applyTo(middleware);
414 Control control = null;
416 // Create a wrapper for pageSite to make it possible
417 // for each tab contribution to specify its own
418 // selection provider.
419 final int tabIndex = index++;
420 IPageSite siteWrapper = new PageSiteProxy(pageSite) {
422 public void setSelectionProvider(ISelectionProvider provider) {
423 tabSelectionProviders[tabIndex] = provider;
424 if(pageSelectionProvider != null && provider != null)
425 pageSelectionProvider.setAndFireNonEqualSelection(provider.getSelection());
428 public ISelectionProvider getSelectionProvider() {
429 return tabSelectionProviders[tabIndex];
433 IPropertyTab tab = cc.create(middleware, siteWrapper, sessionContext, cc.getInput());
436 control = tab.getControl();
437 if(control != null && !control.isDisposed()) GridDataFactory.fillDefaults().grab(true, true).applyTo(control);
439 control = createEmptyControl(middleware, SWT.NONE);
440 // Button b = new Button(middleware, SWT.BORDER);
441 // GridDataFactory.fillDefaults().grab(true, true).applyTo(b);
445 addPage(middleware, cc.getLabel(), cc.getImage());
448 Object input = cc.getInput();
449 tab.setInput(sessionContext, new StructuredSelection(input), false);
453 ComparableTabContributor cc = TabbedPropertyTable.this.selectInitialTab(selectionContents, contributionList);
455 int activePage = contributionList.indexOf(cc);
456 if (activePage != -1)
457 setActivePage(activePage);
462 Composite createEmptyControl(Composite parent, int flags) {
463 Composite empty = new Composite(parent, flags);
464 GridLayoutFactory.fillDefaults().applyTo(empty);
476 public Control getControl() {
481 public void requestFocus() {
482 IPropertyTab table = getActiveTab();
484 table.requestFocus();
487 public IPropertyTab getActiveTab() {
488 int index = activeTab.get();
489 if (index < 0 || index >= tabs.size())
491 IPropertyTab tab = tabs.get(index);
495 protected Composite createBaseComposite(final Composite parent, TabbedPropertyPage page) {
496 // 1. dispose the previous UI container
499 // 2. construct new base for the tabbed property UI
500 baseComposite = new Composite(parent, SWT.NONE);
503 // parent.setBackground(getDisplay().getSystemColor(SWT.COLOR_RED));
504 // baseComposite.setBackground(getDisplay().getSystemColor(SWT.COLOR_TITLE_INACTIVE_BACKGROUND));
508 page.createPartControl(baseComposite);
510 GridLayoutFactory.fillDefaults().applyTo(parent);
511 GridDataFactory.fillDefaults().grab(true, true).applyTo(baseComposite);
512 baseComposite.setLayout(new FillLayout());
514 return baseComposite;
517 private void disposeAll() {
518 if (baseComposite != null && !baseComposite.isDisposed()) {
519 baseComposite.dispose();
520 baseComposite = null;
523 for (IPropertyTab tab : tabs)
529 public ISelectionProvider getSelectionProvider() {
530 return pageSelectionProvider;
534 * Override to perform actions when the active tab changes.
539 protected void activeTabChanged(TabChangeEvent event) {
543 * @param selectionContents
544 * @param contributions
547 protected ComparableTabContributor selectInitialTab(Collection<Object> selectionContents, Collection<ComparableTabContributor> contributions) {
554 protected int getTabFolderStyle() {