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