1 /*******************************************************************************
2 * Copyright (c) 2007, 2018 Association for Decentralized Information Management
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.browsing.ui.platform;
15 import java.util.WeakHashMap;
16 import java.util.function.Consumer;
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;
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.
55 * To get a property page for your own view or editor part you can do one of the
59 * <li>Implement getAdapter for your view or editor part as follows:
62 * Object getAdapter(Class c) {
63 * if (c == IPropertyPage.class) {
64 * // Get the browse contexts to use from somewhere
65 * Set<String> browseContexts = Collections.singleton("...");
66 * return new StandardPropertyPage(getSite(), browseContexts);
68 * return super.getAdapter(c);
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>
80 * @author Tuukka Lehtonen
82 * @see IStandardPropertyPage
86 public class PropertyPageView extends PageBookView implements IContributedContentsView {
88 private static final Logger LOGGER = LoggerFactory.getLogger(PropertyPageView.class);
90 private static final String PROPERTY_VIEW_CONTEXT = "org.simantics.modeling.ui.properties";
92 private static final String PROP_PINNED = "pinned";
94 private ISessionContextProvider contextProvider;
97 * The initial selection when the property sheet opens
99 private ISelection bootstrapSelection;
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.
106 private final boolean bootstrapOnly = false;
108 private IMemento memento;
110 private boolean pinSelection = false;
112 private IWorkbenchPart lastPart;
113 private ISelection lastSelection;
114 private final Map<IWorkbenchPart, ISelection> lastSelections = new WeakHashMap<>();
116 private ResourceManager resourceManager;
118 private ImageDescriptor notPinned;
119 private ImageDescriptor pinned;
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.
125 private ISelectionListener selectionListener = this::doSelectionChanged;
128 public void createPartControl(Composite parent) {
129 super.createPartControl(parent);
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");
135 IContextService cs = (IContextService) getSite().getService(IContextService.class);
136 cs.activateContext(PROPERTY_VIEW_CONTEXT);
142 * @see org.eclipse.ui.part.PageBookView#getAdapter(java.lang.Class)
144 @SuppressWarnings("rawtypes")
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() {
153 public IWorkbenchPart getContributingPart() {
154 return getContributingEditor();
158 return super.getAdapter(adapter);
162 * Returns the editor which contributed the current
165 * @return the editor which contributed the current page
166 * or <code>null</code> if no editor contributed the current page
168 private IWorkbenchPart getContributingEditor() {
169 return getCurrentContributingPart();
173 * @see org.eclipse.ui.part.ViewPart#init(org.eclipse.ui.IViewSite, org.eclipse.ui.IMemento)
176 public void init(IViewSite site, IMemento memento) throws PartInitException {
177 this.memento = memento;
184 * @see org.eclipse.ui.part.PageBookView#init(org.eclipse.ui.IViewSite)
187 public void init(IViewSite site) throws PartInitException {
188 String secondaryId = site.getSecondaryId();
189 if (secondaryId != null) {
190 ResourceInput input = ResourceInput.unmarshall(secondaryId);
192 //bootstrapOnly = true;
196 //System.out.println("PPV init: " + this);
199 contextProvider = Simantics.getSessionContextProvider();
201 if (!bootstrapOnly) {
202 site.getPage().addPostSelectionListener(selectionListener);
207 public void saveState(IMemento memento) {
208 if (this.memento != null) {
209 memento.putMemento(this.memento);
214 * Method declared on IWorkbenchPart.
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
224 if (lastPart != null)
225 lastPart.removePropertyListener(partPropertyListener);
227 contextProvider = null;
229 // Remove ourselves as a workbench selection listener.
230 if (!bootstrapOnly) {
231 getSite().getPage().removePostSelectionListener(selectionListener);
234 if (resourceManager != null) {
235 resourceManager.dispose();
236 resourceManager = null;
241 protected IPage createDefaultPage(PageBook book) {
243 MessagePage page = new MessagePage();
245 page.createControl(book);
246 page.setMessage(Messages.PropertyPageView_noPropertiesAvailable);
250 PropertyPage page = new PropertyPage(getSite());
252 page.createControl(book);
253 //System.out.println("PPV create default page: " + page);
259 protected PageRec doCreatePage(IWorkbenchPart part) {
260 // NOTE: If the default page should be shown, this method must return null.
264 //System.out.println("PPV try to create page for part: " + (part != null ? part.getTitle() : null));
266 // Try to get a property page.
267 IPropertyPage page = (IPropertyPage) part.getAdapter(IPropertyPage.class);
269 //System.out.println("PPV created page: " + page);
270 if (page instanceof IPageBookViewPage) {
271 initPage((IPageBookViewPage) page);
273 page.createControl(getPageBook());
274 //System.out.println("PPV created page control: " + page.getControl());
275 return new PageRec(part, page);
281 protected void doDestroyPage(IWorkbenchPart part, PageRec pageRecord) {
282 //System.out.println("PPV destroy page for part: " + part.getTitle());
284 IPropertyPage page = (IPropertyPage) pageRecord.page;
286 pageRecord.dispose();
290 protected IWorkbenchPart getBootstrapPart() {
291 IWorkbenchPage page = getSite().getPage();
293 bootstrapSelection = page.getSelection();
294 return page.getActivePart();
299 private boolean isPropertyView(IWorkbenchPart part) {
300 boolean ignore = false;
302 if (part instanceof IWorkbenchPart3) {
303 IWorkbenchPart3 part3 = (IWorkbenchPart3) part;
304 ignore = Boolean.parseBoolean(part3.getPartProperty(PROP_PINNED));
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);
314 return this == part || ignore;
318 protected boolean isImportant(IWorkbenchPart part) {
319 //System.out.println("isImportant(" + part.getSite().getId() + ")");
320 return !isWorkbenchSelectionPinned() && !isPropertyView(part);
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.
330 public void partActivated(IWorkbenchPart part) {
331 // if (bootstrapSelection == null && bootstrapOnly)
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);
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);
351 bootstrapSelection = null;
357 public void partClosed(IWorkbenchPart part) {
358 // Make sure that pinned view is not reset even if its originating
361 super.partClosed(part);
365 protected void partHidden(IWorkbenchPart part) {
366 // Fast views are quite unusable if this code is enabled.
368 // Make sure that pinned view is not hidden when the editor is hidden
370 // super.partHidden(part);
373 public ISelection getLastSelection() {
374 return lastSelection;
377 public void selectionChanged(IWorkbenchPart part, ISelection sel) {
378 doSelectionChanged(part, sel);
384 * @return <code>true</code> if the changed selection affected the view,
385 * <code>false</code> otherwise
387 boolean doSelectionChanged(IWorkbenchPart part, ISelection sel) {
388 LOGGER.trace("doSelectionChanged({}): incoming selection {}", part, sel);
390 // we ignore our own selection or null selection
391 if (isPropertyView(part) || sel == null) {
394 // ignore workbench selections when pinned also
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;
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)
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);
417 if (lastPart != null) {
418 lastPart.removePropertyListener(partPropertyListener);
422 lastSelections.put(part, sel);
423 if (lastPart != null) {
424 lastPart.addPropertyListener(partPropertyListener);
427 if (!sameSelection) {
428 LOGGER.trace("doSelectionChanged({}): updating page input selection to {}", part, sel);
429 updatePartName(ppage, sel);
430 ppage.selectionChanged(part, sel);
437 void updatePartName(IPropertyPage ppage, ISelection sel) {
438 ppage.updatePartName(sel, partNameUpdateCallback);
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);
454 void doSetPartName(String partName) {
455 // Is the page view disposed ??
456 if (contextProvider == null)
458 if (partName == null) {
460 partName = "Selection";
462 setPartName(partName);
465 public boolean isWorkbenchSelectionPinned() {
469 public void pinWorkbenchSelection(boolean pin) {
470 if (pin == pinSelection)
474 setPartProperty(PROP_PINNED, Boolean.toString(pin));
477 setTitleImage(resourceManager.createImage(pinned));
479 setTitleImage(resourceManager.createImage(notPinned));
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);
490 IWorkbenchPart getSourcePart(IWorkbenchPart part) {
491 IContributedContentsView view = (IContributedContentsView) part.getAdapter(IContributedContentsView.class);
493 IWorkbenchPart source = view.getContributingPart();
500 private void updateContentDescription(boolean selectionPinned, IWorkbenchPart sourcePart) {
501 if (selectionPinned) {
502 if (sourcePart == null) {
503 setContentDescription("No selection");
505 sourcePart = getSourcePart(sourcePart);
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 ");
513 desc.append(sourcePart.getTitle());
516 setContentDescription(desc.toString());
519 setContentDescription("");
523 IPropertyListener partPropertyListener = new IPropertyListener() {
525 public void propertyChanged(Object source, int propId) {
526 if (propId == IWorkbenchPart.PROP_TITLE) {
527 updateContentDescription(pinSelection, lastPart);
533 public IWorkbenchPart getContributingPart() {