]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.browsing.ui.platform/src/org/simantics/browsing/ui/platform/PropertyPageView.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.browsing.ui.platform / src / org / simantics / browsing / ui / platform / PropertyPageView.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.browsing.ui.platform;\r
13 \r
14 import java.util.HashSet;\r
15 import java.util.Map;\r
16 import java.util.Set;\r
17 import java.util.WeakHashMap;\r
18 import java.util.function.Consumer;\r
19 \r
20 import org.eclipse.core.runtime.IConfigurationElement;\r
21 import org.eclipse.core.runtime.IExtension;\r
22 import org.eclipse.core.runtime.IExtensionPoint;\r
23 import org.eclipse.core.runtime.IExtensionRegistry;\r
24 import org.eclipse.core.runtime.RegistryFactory;\r
25 import org.eclipse.jface.resource.ImageDescriptor;\r
26 import org.eclipse.jface.resource.JFaceResources;\r
27 import org.eclipse.jface.resource.LocalResourceManager;\r
28 import org.eclipse.jface.resource.ResourceManager;\r
29 import org.eclipse.jface.viewers.ISelection;\r
30 import org.eclipse.swt.widgets.Composite;\r
31 import org.eclipse.ui.IEditorPart;\r
32 import org.eclipse.ui.IMemento;\r
33 import org.eclipse.ui.IPropertyListener;\r
34 import org.eclipse.ui.ISelectionListener;\r
35 import org.eclipse.ui.IViewPart;\r
36 import org.eclipse.ui.IViewSite;\r
37 import org.eclipse.ui.IWorkbenchPage;\r
38 import org.eclipse.ui.IWorkbenchPart;\r
39 import org.eclipse.ui.IWorkbenchPart3;\r
40 import org.eclipse.ui.PartInitException;\r
41 import org.eclipse.ui.PlatformUI;\r
42 import org.eclipse.ui.contexts.IContextService;\r
43 import org.eclipse.ui.part.IContributedContentsView;\r
44 import org.eclipse.ui.part.IPage;\r
45 import org.eclipse.ui.part.IPageBookViewPage;\r
46 import org.eclipse.ui.part.PageBook;\r
47 import org.simantics.db.management.ISessionContextProvider;\r
48 import org.simantics.selectionview.PropertyPage;\r
49 import org.simantics.ui.SimanticsUI;\r
50 import org.simantics.ui.workbench.IPropertyPage;\r
51 import org.simantics.ui.workbench.ResourceInput;\r
52 import org.simantics.utils.threads.SWTThread;\r
53 import org.simantics.utils.threads.Throttler;\r
54 import org.simantics.utils.ui.BundleUtils;\r
55 import org.simantics.utils.ui.SWTUtils;\r
56 \r
57 /**\r
58  * This is a version of the standard eclipse <code>PropertySheet</code> view a\r
59  * graph database access twist. It presents a property view based the active\r
60  * workbench part and the active part's current selection.\r
61  * \r
62  * <p>\r
63  * To get a property page for your own view or editor part you can do one of the\r
64  * following:\r
65  * \r
66  * <ol>\r
67  * <li>Implement getAdapter for your view or editor part as follows:\r
68  * \r
69  * <pre>\r
70  * Object getAdapter(Class c) {\r
71  *     if (c == IPropertyPage.class) {\r
72  *         // Get the browse contexts to use from somewhere\r
73  *         Set&lt;String&gt; browseContexts = Collections.singleton("...");\r
74  *         return new StandardPropertyPage(getSite(), browseContexts);\r
75  *     }\r
76  *     return super.getAdapter(c);\r
77  * }\r
78  * </pre>\r
79  * \r
80  * This method also allows customization of the actual property page control\r
81  * that gets created. <code>PropertyPage</code> serves as a good starting point\r
82  * for your own version.</li>\r
83  * <li>Make the workbench part implement the marker interface\r
84  * <code>IStandardPropertyPage</code> which will make this view do the above\r
85  * automatically without implementing <code>getAdapter</code>.</li>\r
86  * </ol>\r
87  * \r
88  * @author Tuukka Lehtonen\r
89  * \r
90  * @see IStandardPropertyPage\r
91  * @see IPropertyPage\r
92  * @see PropertyPage\r
93  */\r
94 public class PropertyPageView extends PageBookView implements ISelectionListener, IContributedContentsView {\r
95 \r
96     /**\r
97      * Extension point used to modify behavior of the view\r
98      */\r
99     private static final String                   EXT_POINT                  = "org.eclipse.ui.propertiesView";              //$NON-NLS-1$\r
100 \r
101     private static final String                   PROPERTY_VIEW_CONTEXT      = "org.simantics.modeling.ui.properties";\r
102 \r
103     private static final String                   PROP_PINNED                = "pinned";\r
104 \r
105     protected static final long                   SELECTION_CHANGE_THRESHOLD = 500;\r
106 \r
107     private ISessionContextProvider               contextProvider;\r
108 \r
109     /**\r
110      * The initial selection when the property sheet opens\r
111      */\r
112     private ISelection                            bootstrapSelection;\r
113 \r
114     /**\r
115      * A flag for indicating whether or not this view will only use the\r
116      * bootstrap selection and and IPropertyPage source instead of listening to\r
117      * the input constantly.\r
118      */\r
119     private final boolean                         bootstrapOnly              = false;\r
120 \r
121     private IMemento                              memento;\r
122 \r
123     private boolean                               pinSelection               = false;\r
124 \r
125     private IWorkbenchPart                        lastPart;\r
126     private ISelection                            lastSelection;\r
127     private final Map<IWorkbenchPart, ISelection> lastSelections             = new WeakHashMap<IWorkbenchPart, ISelection>();\r
128 \r
129     private ResourceManager                       resourceManager;\r
130 \r
131     private ImageDescriptor                       notPinned;\r
132     private ImageDescriptor                       pinned;\r
133 \r
134     /**\r
135      * Set of workbench parts, which should not be used as a source for PropertySheet\r
136      */\r
137     private Set<String>                           ignoredViews;\r
138 \r
139     @Override\r
140     public void createPartControl(Composite parent) {\r
141         super.createPartControl(parent);\r
142 \r
143         this.resourceManager = new LocalResourceManager(JFaceResources.getResources());\r
144         notPinned = BundleUtils.getImageDescriptorFromPlugin("org.simantics.browsing.ui.common", "icons/table_multiple.png");\r
145         pinned = BundleUtils.getImageDescriptorFromPlugin("org.simantics.browsing.ui.common", "icons/table_multiple_pinned.png");\r
146 \r
147         IContextService cs = (IContextService) getSite().getService(IContextService.class);\r
148         cs.activateContext(PROPERTY_VIEW_CONTEXT);\r
149     }\r
150 \r
151     /*\r
152      * (non-Javadoc)\r
153      * \r
154      * @see org.eclipse.ui.part.PageBookView#getAdapter(java.lang.Class)\r
155      */\r
156     @SuppressWarnings("rawtypes")\r
157     @Override\r
158     public Object getAdapter(Class adapter) {\r
159         if (adapter == IContributedContentsView.class) {\r
160             // This makes it possible to duplicate a PropertyPageView with another\r
161             // secondary ID and make it show the same property page that was showing\r
162             // in the original property page view.\r
163             return new IContributedContentsView() {\r
164                 @Override\r
165                 public IWorkbenchPart getContributingPart() {\r
166                     return getContributingEditor();\r
167                 }\r
168             };\r
169         }\r
170         return super.getAdapter(adapter);\r
171     }\r
172 \r
173     /**\r
174      * Returns the editor which contributed the current\r
175      * page to this view.\r
176      *\r
177      * @return the editor which contributed the current page\r
178      * or <code>null</code> if no editor contributed the current page\r
179      */\r
180     private IWorkbenchPart getContributingEditor() {\r
181         return getCurrentContributingPart();\r
182     }\r
183 \r
184     /* (non-Javadoc)\r
185      * @see org.eclipse.ui.part.ViewPart#init(org.eclipse.ui.IViewSite, org.eclipse.ui.IMemento)\r
186      */\r
187     @Override\r
188     public void init(IViewSite site, IMemento memento) throws PartInitException {\r
189         this.memento = memento;\r
190         init(site);\r
191     }\r
192 \r
193     /*\r
194      * (non-Javadoc)\r
195      * \r
196      * @see org.eclipse.ui.part.PageBookView#init(org.eclipse.ui.IViewSite)\r
197      */\r
198     @Override\r
199     public void init(IViewSite site) throws PartInitException {\r
200         String secondaryId = site.getSecondaryId();\r
201         if (secondaryId != null) {\r
202             ResourceInput input = ResourceInput.unmarshall(secondaryId);\r
203             if (input != null) {\r
204                 //bootstrapOnly = true;\r
205             }\r
206         }\r
207 \r
208         //System.out.println("PPV init: " + this);\r
209         super.init(site);\r
210 \r
211         // This prevents the Properties view from providing a selection to other\r
212         // workbench parts, thus making them lose their selections which is not\r
213         // desirable.\r
214 //        site.setSelectionProvider(null);\r
215 \r
216         contextProvider = SimanticsUI.getSessionContextProvider();\r
217 \r
218         if (!bootstrapOnly) {\r
219             site.getPage().addSelectionListener(immediateSelectionListener);\r
220             site.getPage().addPostSelectionListener(this);\r
221         }\r
222     }\r
223 \r
224     @Override\r
225     public void saveState(IMemento memento) {\r
226         if (this.memento != null) {\r
227             memento.putMemento(this.memento);\r
228         }\r
229     }\r
230 \r
231     /* (non-Javadoc)\r
232      * Method declared on IWorkbenchPart.\r
233      */\r
234     @Override\r
235     public void dispose() {\r
236         //System.out.println("PPV dispose: " + this);\r
237         // Dispose of this before nullifying contextProvider because this\r
238         // dispose may just need the context provider - at least PropertyPage\r
239         // disposal will.\r
240         super.dispose();\r
241 \r
242         if (lastPart != null)\r
243             lastPart.removePropertyListener(partPropertyListener);\r
244 \r
245         contextProvider = null;\r
246 \r
247         // Remove ourselves as a workbench selection listener.\r
248         if (!bootstrapOnly) {\r
249             getSite().getPage().removePostSelectionListener(this);\r
250             getSite().getPage().removeSelectionListener(immediateSelectionListener);\r
251         }\r
252 \r
253         if (resourceManager != null) {\r
254             resourceManager.dispose();\r
255             resourceManager = null;\r
256         }\r
257     }\r
258 \r
259     @Override\r
260     protected IPage createDefaultPage(PageBook book) {\r
261         /*\r
262         MessagePage page = new MessagePage();\r
263         initPage(page);\r
264         page.createControl(book);\r
265         page.setMessage(Messages.PropertyPageView_noPropertiesAvailable);\r
266         return page;\r
267          */\r
268 \r
269         PropertyPage page = new PropertyPage(getSite());\r
270         initPage(page);\r
271         page.createControl(book);\r
272         //System.out.println("PPV create default page: " + page);\r
273         return page;\r
274 \r
275     }\r
276 \r
277     @Override\r
278     protected PageRec doCreatePage(IWorkbenchPart part) {\r
279 \r
280         // NOTE: If the default page should be shown, this method must return null.\r
281         if (part == null)\r
282             return null;\r
283 \r
284         //System.out.println("PPV try to create page for part: " + (part != null ? part.getTitle() : null));\r
285 \r
286         // Try to get a property page.\r
287         IPropertyPage page = (IPropertyPage) part.getAdapter(IPropertyPage.class);\r
288         if (page != null) {\r
289             //System.out.println("PPV created page: " + page);\r
290             if (page instanceof IPageBookViewPage) {\r
291                 initPage((IPageBookViewPage) page);\r
292             }\r
293             page.createControl(getPageBook());\r
294             //System.out.println("PPV created page control: " + page.getControl());\r
295             return new PageRec(part, page);\r
296         }\r
297         return null;\r
298     }\r
299 \r
300     @Override\r
301     protected void doDestroyPage(IWorkbenchPart part, PageRec pageRecord) {\r
302         //System.out.println("PPV destroy page for part: " + part.getTitle());\r
303 \r
304         IPropertyPage page = (IPropertyPage) pageRecord.page;\r
305         page.dispose();\r
306         pageRecord.dispose();\r
307     }\r
308 \r
309     @Override\r
310     protected IWorkbenchPart getBootstrapPart() {\r
311         IWorkbenchPage page = getSite().getPage();\r
312         if (page != null) {\r
313             bootstrapSelection = page.getSelection();\r
314             return page.getActivePart();\r
315         }\r
316         return null;\r
317     }\r
318 \r
319     private boolean isPropertyView(IWorkbenchPart part) {\r
320         boolean ignore = false;\r
321 \r
322         if (part instanceof IWorkbenchPart3) {\r
323             IWorkbenchPart3 part3 = (IWorkbenchPart3) part;\r
324             ignore = Boolean.parseBoolean(part3.getPartProperty(PROP_PINNED));\r
325         }\r
326 \r
327         // See org.simantics.modeling.ui.actions.DuplicatePinnedViewHandler\r
328 //        ignore |= part.getSite().getId().endsWith("Pinned");\r
329         String thisId = getSite().getId();\r
330         String otherId = part.getSite().getId();\r
331         //System.out.println(thisId + " - " + otherId);\r
332         ignore |= otherId.startsWith(thisId);\r
333 \r
334         return this == part || ignore;\r
335     }\r
336 \r
337     private Set<String> getIgnoredViews() {\r
338         if (ignoredViews == null) {\r
339             ignoredViews = new HashSet<String>();\r
340             IExtensionRegistry registry = RegistryFactory.getRegistry();\r
341             IExtensionPoint ep = registry.getExtensionPoint(EXT_POINT);\r
342             if (ep != null) {\r
343                 IExtension[] extensions = ep.getExtensions();\r
344                 for (int i = 0; i < extensions.length; i++) {\r
345                     IConfigurationElement[] elements = extensions[i].getConfigurationElements();\r
346                     for (int j = 0; j < elements.length; j++) {\r
347                         if ("excludeSources".equalsIgnoreCase(elements[j].getName())) { //$NON-NLS-1$\r
348                             String id = elements[j].getAttribute("id"); //$NON-NLS-1$\r
349                             if (id != null)\r
350                                 ignoredViews.add(id);\r
351                         }\r
352                     }\r
353                 }\r
354             }\r
355         }\r
356         return ignoredViews;\r
357     }\r
358 \r
359     private boolean isViewIgnored(String partID) {\r
360         return getIgnoredViews().contains(partID);\r
361     }\r
362 \r
363     @Override\r
364     protected boolean isImportant(IWorkbenchPart part) {\r
365         String partID = part.getSite().getId();\r
366         //System.out.println("isImportant(" + partID + ")");\r
367         return !isWorkbenchSelectionPinned() && !isPropertyView(part) && !isViewIgnored(partID);\r
368     }\r
369 \r
370     /**\r
371      * The <code>PropertySheet</code> implementation of this\r
372      * <code>IPartListener</code> method first sees if the active part is an\r
373      * <code>IContributedContentsView</code> adapter and if so, asks it for\r
374      * its contributing part.\r
375      */\r
376     @Override\r
377     public void partActivated(IWorkbenchPart part) {\r
378 //        if (bootstrapSelection == null && bootstrapOnly)\r
379 //            return;\r
380 \r
381         // Look for a declaratively-contributed adapter - including not yet\r
382         // loaded adapter factories.\r
383         // See bug 86362 [PropertiesView] Can not access AdapterFactory, when\r
384         // plugin is not loaded.\r
385         IWorkbenchPart source = getSourcePart(part);\r
386         //System.out.println("PPV part activated: " + part  + ",src " + source + ",view " + this + " bss: " + bootstrapSelection + " pin " + pinSelection);\r
387         super.partActivated(source);\r
388 \r
389         // When the view is first opened, pass the selection to the page\r
390         if (bootstrapSelection != null) {\r
391             IPage page = getCurrentPage();\r
392             if (page instanceof IPropertyPage) {\r
393                 IPropertyPage ppage = (IPropertyPage) page;\r
394                 // FIXME: should this pass source or part ??\r
395                 ppage.selectionChanged(part, bootstrapSelection);\r
396                 updatePartName(ppage, bootstrapSelection);\r
397             }\r
398             bootstrapSelection = null;\r
399         }\r
400     }\r
401 \r
402 \r
403     @Override\r
404     public void partClosed(IWorkbenchPart part) {\r
405         // Make sure that pinned view is not reset even if its originating\r
406         // editor is closed.\r
407         if (!pinSelection)\r
408             super.partClosed(part);\r
409     }\r
410 \r
411     @Override\r
412     protected void partHidden(IWorkbenchPart part) {\r
413         // Fast views are quite unusable if this code is enabled.\r
414 \r
415         // Make sure that pinned view is not hidden when the editor is hidden\r
416 //        if(!pinSelection)\r
417 //            super.partHidden(part);\r
418     }\r
419 \r
420     ISelectionListener immediateSelectionListener = new ISelectionListener() {\r
421         \r
422         private Throttler throttler = new Throttler(SWTThread.getThreadAccess(PlatformUI.getWorkbench().getDisplay()), 500, 3);\r
423         \r
424         @Override\r
425         public void selectionChanged(final IWorkbenchPart part, final ISelection selection) {\r
426                 \r
427                 // Do not process selections from self\r
428                 if(PropertyPageView.this == part) return;\r
429                 \r
430             throttler.schedule(new Runnable() {\r
431 \r
432                                 @Override\r
433                                 public void run() {\r
434                                         PropertyPageView.this.doSelectionChanged(part, selection);                              \r
435                                 }\r
436                 \r
437             });\r
438             \r
439         }\r
440     };\r
441 \r
442     public ISelection getLastSelection() {\r
443         return lastSelection;\r
444     }\r
445 \r
446     /*\r
447      * (non-Javadoc)\r
448      * \r
449      * @see org.eclipse.ui.ISelectionListener#selectionChanged(org.eclipse.ui.IWorkbenchPart,\r
450      *      org.eclipse.jface.viewers.ISelection)\r
451      */\r
452     @Override\r
453     public void selectionChanged(IWorkbenchPart part, ISelection sel) {\r
454         doSelectionChanged(part, sel);\r
455     }\r
456 \r
457     /**\r
458      * @param part\r
459      * @param sel\r
460      * @return <code>true</code> if the changed selection affected the view,\r
461      *         <code>false</code> otherwise\r
462      */\r
463     boolean doSelectionChanged(IWorkbenchPart part, ISelection sel) {\r
464         \r
465         // we ignore our own selection or null selection\r
466         if (isPropertyView(part) || sel == null) {\r
467             return false;\r
468         }\r
469         // ignore workbench selections when pinned also\r
470         if (pinSelection)\r
471             return false;\r
472 \r
473         // pass the selection change to the page\r
474         part = getSourcePart(part);\r
475         IPage page = getCurrentPage();\r
476         //System.out.println("PPV selection changed to (" + part + ", " + sel + "): " + page);\r
477         if (page instanceof IPropertyPage) {\r
478             IPropertyPage ppage = (IPropertyPage) page;\r
479 \r
480             // Prevent parts that do not contribute a property page from messing\r
481             // up the contents/title of the currently active property page.\r
482             PageRec pageRec = getPageRec(part);\r
483             if (pageRec == null || pageRec.page != page)\r
484                 return false;\r
485 \r
486             // Make sure that the part name is not updated unnecessarily because\r
487             // of immediate and post selection listeners.\r
488             ISelection lastPartSelection = lastSelections.get(part);\r
489             //System.out.println("  LAST PART SELECTION(" + part + "): " + lastPartSelection);\r
490             boolean sameSelection = lastPartSelection != null && sel.equals(lastPartSelection);\r
491 \r
492             if (lastPart != null) {\r
493                 lastPart.removePropertyListener(partPropertyListener);\r
494             }\r
495             lastPart = part;\r
496             lastSelection = sel;\r
497             lastSelections.put(part, sel);\r
498             if (lastPart != null) {\r
499                 lastPart.addPropertyListener(partPropertyListener);\r
500             }\r
501 \r
502             updatePartName(ppage, sel);\r
503             if (!sameSelection) {\r
504                 ppage.selectionChanged(part, sel);\r
505                 return true;\r
506             }\r
507         }\r
508         return false;\r
509     }\r
510 \r
511     void updatePartName(IPropertyPage ppage, ISelection sel) {\r
512         ppage.updatePartName(sel, partNameUpdateCallback);\r
513     }\r
514 \r
515     Consumer<String> partNameUpdateCallback = parameter -> {\r
516         // This check is not safe - there might be a burst of changes incoming\r
517         //if (getPartName().equals(parameter)) return;\r
518         //System.out.println("partNameUpdateCallback : " + parameter);\r
519         SWTUtils.asyncExec(getPageBook(), new Runnable() {\r
520             @Override\r
521             public void run() {\r
522                 if (!getPageBook().isDisposed()) {\r
523                     if (getPartName().equals(parameter)) return;\r
524                     //System.out.println("doSetParameterName : " + parameter);\r
525                     doSetPartName(parameter);\r
526                 }\r
527             }\r
528         });\r
529     };\r
530 \r
531     void doSetPartName(String partName) {\r
532         // Is the page view disposed ??\r
533         if (contextProvider == null)\r
534             return;\r
535         if (partName == null) {\r
536             // Return to default\r
537             partName = "Selection";\r
538         }\r
539         setPartName(partName);\r
540     }\r
541 \r
542     public boolean isWorkbenchSelectionPinned() {\r
543         return pinSelection;\r
544     }\r
545 \r
546     public void pinWorkbenchSelection(boolean pin) {\r
547         if (pin == pinSelection)\r
548             return;\r
549 \r
550         pinSelection = pin;\r
551         setPartProperty(PROP_PINNED, Boolean.toString(pin));\r
552 \r
553         if (pin) {\r
554             setTitleImage(resourceManager.createImage(pinned));\r
555         } else {\r
556             setTitleImage(resourceManager.createImage(notPinned));\r
557         }\r
558         updateContentDescription(pin, lastPart);\r
559         // Since lastPart is another PropertyView, we do not want to listen it's changes (At least current implementation is done so)\r
560         if (lastPart != null) {\r
561             lastPart.removePropertyListener(partPropertyListener);\r
562         }\r
563         lastPart = null;\r
564 \r
565     }\r
566 \r
567     IWorkbenchPart getSourcePart(IWorkbenchPart part) {\r
568         IContributedContentsView view = (IContributedContentsView) part.getAdapter(IContributedContentsView.class);\r
569         if (view != null) {\r
570             IWorkbenchPart source = view.getContributingPart();\r
571             if (source != null)\r
572                 return source;\r
573         }\r
574         return part;\r
575     }\r
576 \r
577     private void updateContentDescription(boolean selectionPinned, IWorkbenchPart sourcePart) {\r
578         if (selectionPinned) {\r
579             if (sourcePart == null) {\r
580                 setContentDescription("No selection");\r
581             } else {\r
582                 sourcePart = getSourcePart(sourcePart);\r
583 \r
584                 StringBuilder desc = new StringBuilder("Selection from ");\r
585                 if (sourcePart instanceof IEditorPart)\r
586                     desc.append("editor ");\r
587                 if (sourcePart instanceof IViewPart)\r
588                     desc.append("view ");\r
589                 desc.append('\'');\r
590                 desc.append(sourcePart.getTitle());\r
591                 desc.append('\'');\r
592 \r
593                 setContentDescription(desc.toString());\r
594             }\r
595         } else {\r
596             setContentDescription("");\r
597         }\r
598     }\r
599 \r
600     IPropertyListener partPropertyListener = new IPropertyListener() {\r
601         @Override\r
602         public void propertyChanged(Object source, int propId) {\r
603             if (propId == IWorkbenchPart.PROP_TITLE) {\r
604                 updateContentDescription(pinSelection, lastPart);\r
605             }\r
606         }\r
607     };\r
608 \r
609     @Override\r
610     public IWorkbenchPart getContributingPart() {\r
611         return lastPart;\r
612     }\r
613 \r
614 }\r