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<?>>() {
279 Collection<?> currentContribs =null;
281 public void accept(final Collection<?> contribs) {
284 currentContribs = null;
287 if (contribs != null && contribs.equals(currentContribs))
289 currentContribs = contribs;
290 // if (contribs.isEmpty())
292 SimanticsUI.asyncExecSWT(TabbedPropertyTable.this, new Runnable() {
296 //System.out.println(Thread.currentThread() + " inputCallback: " + input);
297 //System.out.println("is TabbedPropertyTable " + this + " visible: " + isVisible());
299 // Set current selection to null to force update when the
300 // page becomes visible
301 currentSelection = null;
305 createBaseComposite(TabbedPropertyTable.this, new TabbedPropertyPage(sourceSite.getPart()) {
308 * The selection providers for the current set of property tabs.
310 ISelectionProvider[] tabSelectionProviders = { null };
313 protected void initializePageSwitching() {
314 // Overridden to prevent TabbedPropertyPage
315 // from initializing PageSwitcher
319 protected int getContainerStyle() {
320 return TabbedPropertyTable.this.getTabFolderStyle();
324 protected void pageChange(int newPageIndex) {
325 int oldActiveTab = activeTab.getAndSet(newPageIndex);
326 //System.out.println(Thread.currentThread() + " page changed: from " + oldActiveTab + " to " + newPageIndex);
327 super.pageChange(newPageIndex);
329 // Remove possible old selection listeners from the hidden tab.
330 ISelection oldSelection = null;
331 IPropertyTab oldTab = null;
332 if (oldActiveTab > -1) {
333 oldTab = tabs.get(oldActiveTab);
334 ISelectionProvider pv = oldTab.getSelectionProvider();
336 oldSelection = pv.getSelection();
337 pv.removeSelectionChangedListener(activeTabSelectionListener);
341 // Attach selection listeners to the activated tab if possible.
342 ISelection activeSelection = null;
343 IPropertyTab activeTab = getActiveTab();
344 if (activeTab != null) {
345 ISelectionProvider pv = activeTab.getSelectionProvider();
347 activeSelection = pv.getSelection();
348 pv.addSelectionChangedListener(activeTabSelectionListener);
352 String oldLabel = null;
353 String newLabel = null;
354 if (oldActiveTab > -1)
355 oldLabel = getPageText(oldActiveTab);
356 if (newPageIndex > -1)
357 newLabel = getPageText(newPageIndex);
358 activeTabChanged(new TabChangeEvent(oldTab, oldLabel, activeTab, newLabel));
360 // This is a workaround to avert calling pageSelectionProvider.setSelection here.
361 pageSelectionProvider.setSelectionWithoutFiring(activeSelection);
362 if (!ObjectUtils.objectEquals(oldSelection, activeSelection)) {
363 pageSelectionProvider.fireSelection(activeSelection);
364 pageSelectionProvider.firePostSelection(activeSelection);
369 protected void createPages() {
371 // 1. loop through a list of possible contributors
372 // 2. list the ones that can contribute, in priority order told by the contributions themselves
373 // 3. add pages in priority order
374 // 4. initialize pages
376 // Categorize contributions by id
377 MapList<String,ComparableTabContributor> ml = new MapList<String,ComparableTabContributor>();
378 for (Object o : contribs) {
379 if (o instanceof ComparableTabContributor) {
380 ComparableTabContributor c = (ComparableTabContributor) o;
381 ml.add(c.getId(), c);
383 System.out.println("WARNING: SelectionProcessor produced an unusable contribution to TabbedPropertyTable: " + o);
387 // For each id take the greatest (id) contribution
388 Collection<ComparableTabContributor> contributions = new TreeSet<ComparableTabContributor>();
389 for(String key : ml.getKeys()) {
390 TreeSet<ComparableTabContributor> ts = new TreeSet<ComparableTabContributor>(ml.getValuesUnsafe(key));
391 ComparableTabContributor contrib = ts.first();
392 contributions.add(contrib);
395 // Sort contributions by id
396 List<ComparableTabContributor> contributionList = new ArrayList<ComparableTabContributor>(contributions);
398 if (contributions.isEmpty()) {
399 Composite empty = createEmptyControl(getContainer(), SWT.NONE);
400 addPage(empty, "No properties to show", null);
402 // Set single selection provider into pageSite
403 // that is dispatched every time the selection
404 // in the property view changes, be it because
405 // the selection changed in the current tab or
406 // because the active tab, i.e. the selection
407 // provider is changed.
408 pageSite.setSelectionProvider(pageSelectionProvider);
410 // Allocate space for each tab to specify
411 // its own selection provider.
412 tabSelectionProviders = new ISelectionProvider[contributions.size()];
415 for (ComparableTabContributor cc : contributionList) {
416 Composite middleware = new Composite(getContainer(), SWT.NONE);
417 GridLayoutFactory.fillDefaults().applyTo(middleware);
419 Control control = null;
421 // Create a wrapper for pageSite to make it possible
422 // for each tab contribution to specify its own
423 // selection provider.
424 final int tabIndex = index++;
425 IPageSite siteWrapper = new PageSiteProxy(pageSite) {
427 public void setSelectionProvider(ISelectionProvider provider) {
428 tabSelectionProviders[tabIndex] = provider;
429 if(pageSelectionProvider != null && provider != null)
430 pageSelectionProvider.setAndFireNonEqualSelection(provider.getSelection());
433 public ISelectionProvider getSelectionProvider() {
434 return tabSelectionProviders[tabIndex];
438 IPropertyTab tab = cc.create(middleware, siteWrapper, sessionContext, cc.getInput());
441 control = tab.getControl();
442 if(control != null && !control.isDisposed()) GridDataFactory.fillDefaults().grab(true, true).applyTo(control);
444 control = createEmptyControl(middleware, SWT.NONE);
445 // Button b = new Button(middleware, SWT.BORDER);
446 // GridDataFactory.fillDefaults().grab(true, true).applyTo(b);
450 addPage(middleware, cc.getLabel(), cc.getImage());
453 Object input = cc.getInput();
454 tab.setInput(sessionContext, new StructuredSelection(input), false);
458 ComparableTabContributor cc = TabbedPropertyTable.this.selectInitialTab(selectionContents, contributionList);
460 int activePage = contributionList.indexOf(cc);
461 if (activePage != -1)
462 setActivePage(activePage);
467 Composite createEmptyControl(Composite parent, int flags) {
468 Composite empty = new Composite(parent, flags);
469 GridLayoutFactory.fillDefaults().applyTo(empty);
481 public Control getControl() {
486 public void requestFocus() {
487 IPropertyTab table = getActiveTab();
489 table.requestFocus();
492 public IPropertyTab getActiveTab() {
493 int index = activeTab.get();
494 if (index < 0 || index >= tabs.size())
496 IPropertyTab tab = tabs.get(index);
500 protected Composite createBaseComposite(final Composite parent, TabbedPropertyPage page) {
501 // 1. dispose the previous UI container
504 // 2. construct new base for the tabbed property UI
505 baseComposite = new Composite(parent, SWT.NONE);
508 // parent.setBackground(getDisplay().getSystemColor(SWT.COLOR_RED));
509 // baseComposite.setBackground(getDisplay().getSystemColor(SWT.COLOR_TITLE_INACTIVE_BACKGROUND));
513 page.createPartControl(baseComposite);
515 GridLayoutFactory.fillDefaults().applyTo(parent);
516 GridDataFactory.fillDefaults().grab(true, true).applyTo(baseComposite);
517 baseComposite.setLayout(new FillLayout());
519 return baseComposite;
522 private void disposeAll() {
523 if (baseComposite != null && !baseComposite.isDisposed()) {
524 baseComposite.dispose();
525 baseComposite = null;
528 for (IPropertyTab tab : tabs)
534 public ISelectionProvider getSelectionProvider() {
535 return pageSelectionProvider;
539 * Override to perform actions when the active tab changes.
544 protected void activeTabChanged(TabChangeEvent event) {
548 * @param selectionContents
549 * @param contributions
552 protected ComparableTabContributor selectInitialTab(Collection<Object> selectionContents, Collection<ComparableTabContributor> contributions) {
559 protected int getTabFolderStyle() {