]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.selectionview/src/org/simantics/selectionview/TabbedPropertyTable.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.selectionview / src / org / simantics / selectionview / TabbedPropertyTable.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.selectionview;\r
13 \r
14 import java.util.ArrayList;\r
15 import java.util.Collection;\r
16 import java.util.Collections;\r
17 import java.util.List;\r
18 import java.util.TreeSet;\r
19 import java.util.concurrent.CopyOnWriteArrayList;\r
20 import java.util.concurrent.atomic.AtomicBoolean;\r
21 import java.util.concurrent.atomic.AtomicInteger;\r
22 import java.util.function.Consumer;\r
23 \r
24 import org.eclipse.jface.layout.GridDataFactory;\r
25 import org.eclipse.jface.layout.GridLayoutFactory;\r
26 import org.eclipse.jface.resource.JFaceResources;\r
27 import org.eclipse.jface.resource.LocalResourceManager;\r
28 import org.eclipse.jface.viewers.ISelection;\r
29 import org.eclipse.jface.viewers.ISelectionChangedListener;\r
30 import org.eclipse.jface.viewers.ISelectionProvider;\r
31 import org.eclipse.jface.viewers.SelectionChangedEvent;\r
32 import org.eclipse.jface.viewers.StructuredSelection;\r
33 import org.eclipse.swt.SWT;\r
34 import org.eclipse.swt.layout.FillLayout;\r
35 import org.eclipse.swt.widgets.Composite;\r
36 import org.eclipse.swt.widgets.Control;\r
37 import org.eclipse.swt.widgets.Event;\r
38 import org.eclipse.swt.widgets.Listener;\r
39 import org.eclipse.ui.IWorkbenchPartSite;\r
40 import org.eclipse.ui.part.IPageSite;\r
41 import org.simantics.browsing.ui.common.ErrorLogger;\r
42 import org.simantics.browsing.ui.swt.TabbedPropertyPage;\r
43 import org.simantics.db.ReadGraph;\r
44 import org.simantics.db.common.request.ReadRequest;\r
45 import org.simantics.db.common.request.UniqueRead;\r
46 import org.simantics.db.exception.DatabaseException;\r
47 import org.simantics.db.management.ISessionContext;\r
48 import org.simantics.ui.SimanticsUI;\r
49 import org.simantics.utils.ObjectUtils;\r
50 import org.simantics.utils.datastructures.MapList;\r
51 import org.simantics.utils.ui.ISelectionUtils;\r
52 import org.simantics.utils.ui.jface.BasePostSelectionProvider;\r
53 \r
54 /**\r
55  * <p>\r
56  * Subclasses may extend or reimplement the following methods as required:\r
57  * <ul>\r
58  * <li><code>createBaseComposite</code> - reimplement to customize how the\r
59  * composite is constructed that is the basis of the whole tab container.</\r
60  * <li>\r
61  * </ul>\r
62  * </p>\r
63  * \r
64  * @author Tuukka Lehtonen\r
65  */\r
66 public class TabbedPropertyTable extends Composite implements IPropertyTab {\r
67 \r
68     public static final SelectionProcessor<Object, Object> DEFAULT_SELECTION_PROCESSOR = new SelectionProcessor<Object, Object>() {\r
69         @Override\r
70         public Collection<?> process(Object selection, Object object) {\r
71             return Collections.emptyList();\r
72         }\r
73     };\r
74 \r
75     @SuppressWarnings("rawtypes")\r
76     private SelectionProcessor                 selectionProcessor          = DEFAULT_SELECTION_PROCESSOR;\r
77 \r
78     private Composite                          baseComposite;\r
79 \r
80     private final List<IPropertyTab>           tabs                        = new CopyOnWriteArrayList<IPropertyTab>();\r
81 \r
82     private final AtomicInteger                activeTab                   = new AtomicInteger(-1);\r
83 \r
84     protected IWorkbenchPartSite               sourceSite;\r
85 \r
86     protected IPageSite                        pageSite;\r
87 \r
88     protected ISelection                       currentSelection;\r
89 \r
90     /**\r
91      * The selection provider set for the page site.\r
92      */\r
93     protected BasePostSelectionProvider        pageSelectionProvider = new BasePostSelectionProvider() {\r
94 \r
95         /**\r
96          * For preventing infinite recursion, not that it should happen. \r
97          */\r
98         private AtomicBoolean settingSelection = new AtomicBoolean();\r
99 \r
100         /**\r
101          * Overridden like this because pageSelectionProvider is published to\r
102          * the workbench as the page site selection provider and therefore it\r
103          * possible that its {@link ISelectionProvider#setSelection(ISelection)}\r
104          * method is invoked externally and we need to propagate the selection\r
105          * to the underlying active tab and its selection provider instead of\r
106          * setting pageSelectionProvider's selection to anything.\r
107          */\r
108         @Override\r
109         public void setSelection(ISelection selection) {\r
110             if (settingSelection.compareAndSet(false, true)) {\r
111                 IPropertyTab table = getActiveTab();\r
112                 if (table != null && table.getSelectionProvider() != null)\r
113                     table.getSelectionProvider().setSelection(selection);\r
114                 settingSelection.set(false);\r
115             } else {\r
116                 ErrorLogger.defaultLogWarning("Possible BUG: prevented recursive attempt to set selection for "\r
117                         + TabbedPropertyTable.this.toString(), new Exception("trace"));\r
118             }\r
119         }\r
120     };\r
121 \r
122 //    protected ISelectionChangedListener        debugPageSelectionListener = new ISelectionChangedListener() {\r
123 //        {\r
124 //            pageSelectionProvider.addSelectionChangedListener(this);\r
125 //        }\r
126 //        @Override\r
127 //        public void selectionChanged(SelectionChangedEvent event) {\r
128 //            System.out.println("page selection change: " + event);\r
129 //            System.out.println("    provider: " + event.getSelectionProvider());\r
130 //            System.out.println("    selection: " + event.getSelection());\r
131 //        }\r
132 //    };\r
133 \r
134     protected ISelectionChangedListener        activeTabSelectionListener = new ISelectionChangedListener() {\r
135         @Override\r
136         public void selectionChanged(SelectionChangedEvent event) {\r
137 //            System.out.println("active tab selection change: " + event);\r
138 //            System.out.println("    provider: " + event.getSelectionProvider());\r
139 //            System.out.println("    selection: " + event.getSelection());\r
140             ISelection s = event.getSelection();\r
141             // This is a workaround to avert calling pageSelectionProvider.setSelection here.\r
142             pageSelectionProvider.setSelectionWithoutFiring(s);\r
143             pageSelectionProvider.fireSelection(s);\r
144             pageSelectionProvider.firePostSelection(s);\r
145         }\r
146     };\r
147 \r
148     protected LocalResourceManager             resourceManager;\r
149 \r
150     public TabbedPropertyTable(IWorkbenchPartSite site, IPageSite pageSite, Composite parent, int style) {\r
151         super(parent, style);\r
152         if (site == null)\r
153             throw new IllegalArgumentException("null source site");\r
154         if (pageSite == null)\r
155             throw new IllegalArgumentException("null page site");\r
156         this.sourceSite = site;\r
157         this.pageSite = pageSite;\r
158         GridLayoutFactory.fillDefaults().applyTo(this);\r
159 \r
160         resourceManager = new LocalResourceManager(JFaceResources.getResources(parent.getDisplay()));\r
161 \r
162         addListener(SWT.Dispose, new Listener() {\r
163             @Override\r
164             public void handleEvent(Event event) {\r
165                 //System.out.println("DISPOSING " + this + " " + System.identityHashCode(TabbedPropertyTable.this));\r
166                 activeTab.set(-1);\r
167                 tabs.clear();\r
168 \r
169                 currentSelection = null;\r
170                 if (currentListener != null)\r
171                     currentListener.dispose();\r
172 \r
173                 TabbedPropertyTable.this.pageSite = null;\r
174                 TabbedPropertyTable.this.sourceSite = null;\r
175                 resourceManager.dispose();\r
176                 \r
177             }\r
178         });\r
179     }\r
180 \r
181     @SuppressWarnings("rawtypes")\r
182     protected void setSelectionProcessor(SelectionProcessor selectionProcessor) {\r
183         this.selectionProcessor = selectionProcessor;\r
184     }\r
185 \r
186     @Override\r
187     public void createControl(Composite parent, ISessionContext context) {\r
188         createBaseComposite(parent, null);\r
189     }\r
190 \r
191     class InputListener implements org.simantics.db.procedure.Listener<Collection<?>> {\r
192 \r
193         final private Consumer<Collection<?>> inputCallback;\r
194         private boolean disposed = false;\r
195 \r
196         public InputListener(Consumer<Collection<?>> inputCallback) {\r
197             this.inputCallback = inputCallback;\r
198         }\r
199 \r
200         @Override\r
201         public void exception(Throwable t) {\r
202             ErrorLogger.defaultLogError(t);\r
203         }\r
204 \r
205         @Override\r
206         public void execute(Collection<?> result) {\r
207             inputCallback.accept(result);\r
208         }\r
209 \r
210         @Override\r
211         public boolean isDisposed() {\r
212             return disposed || TabbedPropertyTable.this.isDisposed();\r
213         }\r
214 \r
215         public void dispose() {\r
216             disposed = true;\r
217         }\r
218 \r
219     }\r
220 \r
221     InputListener currentListener = null;\r
222 \r
223     /**\r
224      * Must be invoked from the SWT UI thread.\r
225      * \r
226      * @param selection the new selection\r
227      * @param force <code>true</code> to force the resetting of the new input\r
228      *        even if it is the same as the previous one.\r
229      */\r
230     @Override\r
231     @SuppressWarnings("unchecked")\r
232     public void setInput(ISessionContext context, ISelection selection, boolean force) {\r
233         //System.out.println(hashCode() + "# TabbedPropertyTable.setInput(" + selection + ", " + force + ")");\r
234         if (isDisposed())\r
235             return;\r
236         if (context == null)\r
237             return;\r
238 \r
239         // Check if this is a duplicate of the previous selection to reduce unnecessary flicker.\r
240         if (!force && ObjectUtils.objectEquals(currentSelection, selection))\r
241             return;\r
242 \r
243 //        System.out.println("[3] setInput " + selection + ", force=" + force);\r
244         currentSelection = selection;\r
245 \r
246         if (selectionProcessor != null) {\r
247                 \r
248             if (currentListener != null) currentListener.dispose();\r
249                 \r
250             final Collection<Object> contents = ISelectionUtils.convertSelection(selection);\r
251             if (contents.isEmpty())\r
252                 return;\r
253 \r
254             currentListener = new InputListener(inputCallback(contents, context));\r
255 \r
256             // NOTE: must be an anonymous read to guarantee that each request\r
257             // will always be performed and not taken from DB caches.\r
258             context.getSession().asyncRequest(new ReadRequest() {\r
259                 \r
260                 @Override\r
261                 public void run(ReadGraph graph) throws DatabaseException {\r
262                         \r
263                         graph.syncRequest(new UniqueRead<Collection<?>>() {\r
264                         @Override\r
265                         public Collection<?> perform(ReadGraph graph) throws DatabaseException {\r
266                                 //System.out.println("TabbedPropertyTable.setInput.perform(" + contents + ")");\r
267                                 return selectionProcessor.process(contents, graph);\r
268                         }\r
269                         }, currentListener);\r
270                         \r
271                 }\r
272             });\r
273             \r
274         }\r
275     }\r
276 \r
277     protected Consumer<Collection<?>> inputCallback(final Collection<Object> selectionContents, final ISessionContext sessionContext) {\r
278         return new Consumer<Collection<?>>() {\r
279             @Override\r
280             public void accept(final Collection<?> contribs) {\r
281                 \r
282                 if (isDisposed())\r
283                     return;\r
284 //                if (contribs.isEmpty())\r
285 //                    return;\r
286 \r
287                 SimanticsUI.asyncExecSWT(TabbedPropertyTable.this, new Runnable() {\r
288                     \r
289                     public void run() {\r
290                         \r
291                         //System.out.println(Thread.currentThread() + " inputCallback: " + input);\r
292                         //System.out.println("is TabbedPropertyTable " + this + " visible: " + isVisible());\r
293                         if (!isVisible()) {\r
294                             // Set current selection to null to force update when the\r
295                             // page becomes visible\r
296                             currentSelection = null;\r
297                             return;\r
298                         }\r
299 \r
300                         createBaseComposite(TabbedPropertyTable.this, new TabbedPropertyPage(sourceSite.getPart()) {\r
301 \r
302                             /**\r
303                              * The selection providers for the current set of property tabs.\r
304                              */\r
305                             ISelectionProvider[] tabSelectionProviders = { null };\r
306 \r
307                             @Override\r
308                             protected void initializePageSwitching() {\r
309                                 // Overridden to prevent TabbedPropertyPage\r
310                                 // from initializing PageSwitcher \r
311                             }\r
312 \r
313                             @Override\r
314                             protected int getContainerStyle() {\r
315                                 return TabbedPropertyTable.this.getTabFolderStyle();\r
316                             }\r
317 \r
318                             @Override\r
319                             protected void pageChange(int newPageIndex) {\r
320                                 int oldActiveTab = activeTab.getAndSet(newPageIndex);\r
321                                 //System.out.println(Thread.currentThread() + " page changed: from " + oldActiveTab + " to " + newPageIndex);\r
322                                 super.pageChange(newPageIndex);\r
323 \r
324                                 // Remove possible old selection listeners from the hidden tab.\r
325                                 ISelection oldSelection = null;\r
326                                 IPropertyTab oldTab = null;\r
327                                 if (oldActiveTab > -1) {\r
328                                     oldTab = tabs.get(oldActiveTab);\r
329                                     ISelectionProvider pv = oldTab.getSelectionProvider();\r
330                                     if (pv != null) {\r
331                                         oldSelection = pv.getSelection();\r
332                                         pv.removeSelectionChangedListener(activeTabSelectionListener);\r
333                                     }\r
334                                 }\r
335 \r
336                                 // Attach selection listeners to the activated tab if possible.\r
337                                 ISelection activeSelection = null;\r
338                                 IPropertyTab activeTab = getActiveTab();\r
339                                 if (activeTab != null) {\r
340                                     ISelectionProvider pv = activeTab.getSelectionProvider();\r
341                                     if (pv != null) {\r
342                                         activeSelection = pv.getSelection();\r
343                                         pv.addSelectionChangedListener(activeTabSelectionListener);\r
344                                     }\r
345                                 }\r
346 \r
347                                 String oldLabel = null;\r
348                                 String newLabel = null;\r
349                                 if (oldActiveTab > -1)\r
350                                     oldLabel = getPageText(oldActiveTab);\r
351                                 if (newPageIndex > -1)\r
352                                     newLabel = getPageText(newPageIndex);\r
353                                 activeTabChanged(new TabChangeEvent(oldTab, oldLabel, activeTab, newLabel));\r
354 \r
355                                 // This is a workaround to avert calling pageSelectionProvider.setSelection here.\r
356                                 pageSelectionProvider.setSelectionWithoutFiring(activeSelection);\r
357                                 if (!ObjectUtils.objectEquals(oldSelection, activeSelection)) {\r
358                                     pageSelectionProvider.fireSelection(activeSelection);\r
359                                     pageSelectionProvider.firePostSelection(activeSelection);\r
360                                 }\r
361                             }\r
362 \r
363                             @Override\r
364                             protected void createPages() {\r
365                                 \r
366                                 // 1. loop through a list of possible contributors\r
367                                 // 2. list the ones that can contribute, in priority order told by the contributions themselves\r
368                                 // 3. add pages in priority order\r
369                                 // 4. initialize pages\r
370                                 \r
371                                 // Categorize contributions by id\r
372                                 MapList<String,ComparableTabContributor> ml = new MapList<String,ComparableTabContributor>();\r
373                                 for (Object o : contribs) {\r
374                                     if (o instanceof ComparableTabContributor) {\r
375                                         ComparableTabContributor c = (ComparableTabContributor) o; \r
376                                         ml.add(c.getId(), c);\r
377                                     } else {\r
378                                         System.out.println("WARNING: SelectionProcessor produced an unusable contribution to TabbedPropertyTable: " + o);\r
379                                     }\r
380                                 }\r
381 \r
382                                 // For each id take the greatest (id) contribution\r
383                                 Collection<ComparableTabContributor> contributions = new TreeSet<ComparableTabContributor>();\r
384                                 for(String key : ml.getKeys()) {\r
385                                         TreeSet<ComparableTabContributor> ts = new TreeSet<ComparableTabContributor>(ml.getValuesUnsafe(key));\r
386                                         ComparableTabContributor contrib = ts.first();\r
387                                         contributions.add(contrib);\r
388                                 }\r
389 \r
390                                 // Sort contributions by id\r
391                                 List<ComparableTabContributor> contributionList = new ArrayList<ComparableTabContributor>(contributions);\r
392 \r
393                                 if (contributions.isEmpty()) {\r
394                                     Composite empty = createEmptyControl(getContainer(), SWT.NONE);\r
395                                     addPage(empty, "No properties to show", null);\r
396                                 } else {\r
397                                     // Set single selection provider into pageSite\r
398                                     // that is dispatched every time the selection\r
399                                     // in the property view changes, be it because\r
400                                     // the selection changed in the current tab or\r
401                                     // because the active tab, i.e. the selection\r
402                                     // provider is changed.\r
403                                     pageSite.setSelectionProvider(pageSelectionProvider);\r
404 \r
405                                     // Allocate space for each tab to specify\r
406                                     // its own selection provider.\r
407                                     tabSelectionProviders = new ISelectionProvider[contributions.size()];\r
408                                     int index = 0;\r
409 \r
410                                     for (ComparableTabContributor cc : contributionList) {\r
411                                         Composite middleware = new Composite(getContainer(), SWT.NONE);\r
412                                         GridLayoutFactory.fillDefaults().applyTo(middleware);\r
413 \r
414                                         Control control = null;\r
415 \r
416                                         // Create a wrapper for pageSite to make it possible\r
417                                         // for each tab contribution to specify its own\r
418                                         // selection provider.\r
419                                         final int tabIndex = index++;\r
420                                         IPageSite siteWrapper = new PageSiteProxy(pageSite) {\r
421                                             @Override\r
422                                             public void setSelectionProvider(ISelectionProvider provider) {\r
423                                                 tabSelectionProviders[tabIndex] = provider;\r
424                                                 if(pageSelectionProvider != null && provider != null)\r
425                                                         pageSelectionProvider.setAndFireNonEqualSelection(provider.getSelection());\r
426                                             }\r
427                                             @Override\r
428                                             public ISelectionProvider getSelectionProvider() {\r
429                                                 return tabSelectionProviders[tabIndex];\r
430                                             }\r
431                                         };\r
432 \r
433                                         IPropertyTab tab = cc.create(middleware, siteWrapper, sessionContext, cc.getInput());\r
434                                         if (tab != null) {\r
435                                             tabs.add(tab);\r
436                                             control = tab.getControl();\r
437                                             if(control != null && !control.isDisposed()) GridDataFactory.fillDefaults().grab(true, true).applyTo(control);\r
438                                         } else {\r
439                                             control = createEmptyControl(middleware, SWT.NONE);\r
440 //                                            Button b = new Button(middleware, SWT.BORDER);\r
441 //                                            GridDataFactory.fillDefaults().grab(true, true).applyTo(b);\r
442 //                                            b.setText("FOO");\r
443                                         }\r
444 \r
445                                         addPage(middleware, cc.getLabel(), cc.getImage());\r
446 \r
447                                         if (tab != null) {\r
448                                             Object input = cc.getInput();\r
449                                             tab.setInput(sessionContext, new StructuredSelection(input), false);\r
450                                         }\r
451                                     }\r
452 \r
453                                     ComparableTabContributor cc = TabbedPropertyTable.this.selectInitialTab(selectionContents, contributionList);\r
454                                     if (cc != null) {\r
455                                         int activePage = contributionList.indexOf(cc);\r
456                                         if (activePage != -1)\r
457                                             setActivePage(activePage);\r
458                                     }\r
459                                 }\r
460                             }\r
461 \r
462                             Composite createEmptyControl(Composite parent, int flags) {\r
463                                 Composite empty = new Composite(parent, flags);\r
464                                 GridLayoutFactory.fillDefaults().applyTo(empty);\r
465                                 return empty;\r
466                             }\r
467                         });\r
468                         layout();\r
469                     }\r
470                 });\r
471             }\r
472         };\r
473     }\r
474 \r
475     @Override\r
476     public Control getControl() {\r
477         return this;\r
478     }\r
479 \r
480     @Override\r
481     public void requestFocus() {\r
482         IPropertyTab table = getActiveTab();\r
483         if (table != null)\r
484             table.requestFocus();\r
485     }\r
486 \r
487     public IPropertyTab getActiveTab() {\r
488         int index = activeTab.get();\r
489         if (index < 0 || index >= tabs.size())\r
490             return null;\r
491         IPropertyTab tab = tabs.get(index);\r
492         return tab;\r
493     }\r
494 \r
495     protected Composite createBaseComposite(final Composite parent, TabbedPropertyPage page) {\r
496         // 1. dispose the previous UI container\r
497         disposeAll();\r
498 \r
499         // 2. construct new base for the tabbed property UI\r
500         baseComposite = new Composite(parent, SWT.NONE);\r
501 \r
502         // <DEBUG>\r
503 //        parent.setBackground(getDisplay().getSystemColor(SWT.COLOR_RED));\r
504 //        baseComposite.setBackground(getDisplay().getSystemColor(SWT.COLOR_TITLE_INACTIVE_BACKGROUND));\r
505         // </DEBUG>\r
506 \r
507         if (page != null)\r
508             page.createPartControl(baseComposite);\r
509 \r
510         GridLayoutFactory.fillDefaults().applyTo(parent);\r
511         GridDataFactory.fillDefaults().grab(true, true).applyTo(baseComposite);\r
512         baseComposite.setLayout(new FillLayout());\r
513 \r
514         return baseComposite;\r
515     }\r
516 \r
517     private void disposeAll() {\r
518         if (baseComposite != null && !baseComposite.isDisposed()) {\r
519             baseComposite.dispose();\r
520             baseComposite = null;\r
521         }\r
522         activeTab.set(-1);\r
523         for (IPropertyTab tab : tabs)\r
524             tab.dispose();\r
525         tabs.clear();\r
526     }\r
527 \r
528     @Override\r
529     public ISelectionProvider getSelectionProvider() {\r
530         return pageSelectionProvider;\r
531     }\r
532 \r
533     /**\r
534      * Override to perform actions when the active tab changes.\r
535      *\r
536      * @param previousTab\r
537      * @param activeTab\r
538      */\r
539     protected void activeTabChanged(TabChangeEvent event) {\r
540     }\r
541 \r
542     /**\r
543      * @param selectionContents \r
544      * @param contributions\r
545      * @return\r
546      */\r
547     protected ComparableTabContributor selectInitialTab(Collection<Object> selectionContents, Collection<ComparableTabContributor> contributions) {\r
548         return null;\r
549     }\r
550 \r
551     /**\r
552      * @return\r
553      */\r
554     protected int getTabFolderStyle() {\r
555         return SWT.NONE;\r
556     }\r
557 \r
558 }\r