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