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