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
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.browsing.ui.platform;
\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
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
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
63 * To get a property page for your own view or editor part you can do one of the
\r
67 * <li>Implement getAdapter for your view or editor part as follows:
\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<String> browseContexts = Collections.singleton("...");
\r
74 * return new StandardPropertyPage(getSite(), browseContexts);
\r
76 * return super.getAdapter(c);
\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
88 * @author Tuukka Lehtonen
\r
90 * @see IStandardPropertyPage
\r
91 * @see IPropertyPage
\r
94 public class PropertyPageView extends PageBookView implements ISelectionListener, IContributedContentsView {
\r
97 * Extension point used to modify behavior of the view
\r
99 private static final String EXT_POINT = "org.eclipse.ui.propertiesView"; //$NON-NLS-1$
\r
101 private static final String PROPERTY_VIEW_CONTEXT = "org.simantics.modeling.ui.properties";
\r
103 private static final String PROP_PINNED = "pinned";
\r
105 protected static final long SELECTION_CHANGE_THRESHOLD = 500;
\r
107 private ISessionContextProvider contextProvider;
\r
110 * The initial selection when the property sheet opens
\r
112 private ISelection bootstrapSelection;
\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
119 private final boolean bootstrapOnly = false;
\r
121 private IMemento memento;
\r
123 private boolean pinSelection = false;
\r
125 private IWorkbenchPart lastPart;
\r
126 private ISelection lastSelection;
\r
127 private final Map<IWorkbenchPart, ISelection> lastSelections = new WeakHashMap<IWorkbenchPart, ISelection>();
\r
129 private ResourceManager resourceManager;
\r
131 private ImageDescriptor notPinned;
\r
132 private ImageDescriptor pinned;
\r
135 * Set of workbench parts, which should not be used as a source for PropertySheet
\r
137 private Set<String> ignoredViews;
\r
140 public void createPartControl(Composite parent) {
\r
141 super.createPartControl(parent);
\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
147 IContextService cs = (IContextService) getSite().getService(IContextService.class);
\r
148 cs.activateContext(PROPERTY_VIEW_CONTEXT);
\r
154 * @see org.eclipse.ui.part.PageBookView#getAdapter(java.lang.Class)
\r
156 @SuppressWarnings("rawtypes")
\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
165 public IWorkbenchPart getContributingPart() {
\r
166 return getContributingEditor();
\r
170 return super.getAdapter(adapter);
\r
174 * Returns the editor which contributed the current
\r
175 * page to this view.
\r
177 * @return the editor which contributed the current page
\r
178 * or <code>null</code> if no editor contributed the current page
\r
180 private IWorkbenchPart getContributingEditor() {
\r
181 return getCurrentContributingPart();
\r
185 * @see org.eclipse.ui.part.ViewPart#init(org.eclipse.ui.IViewSite, org.eclipse.ui.IMemento)
\r
188 public void init(IViewSite site, IMemento memento) throws PartInitException {
\r
189 this.memento = memento;
\r
196 * @see org.eclipse.ui.part.PageBookView#init(org.eclipse.ui.IViewSite)
\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
208 //System.out.println("PPV init: " + this);
\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
214 // site.setSelectionProvider(null);
\r
216 contextProvider = SimanticsUI.getSessionContextProvider();
\r
218 if (!bootstrapOnly) {
\r
219 site.getPage().addSelectionListener(immediateSelectionListener);
\r
220 site.getPage().addPostSelectionListener(this);
\r
225 public void saveState(IMemento memento) {
\r
226 if (this.memento != null) {
\r
227 memento.putMemento(this.memento);
\r
232 * Method declared on IWorkbenchPart.
\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
242 if (lastPart != null)
\r
243 lastPart.removePropertyListener(partPropertyListener);
\r
245 contextProvider = null;
\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
253 if (resourceManager != null) {
\r
254 resourceManager.dispose();
\r
255 resourceManager = null;
\r
260 protected IPage createDefaultPage(PageBook book) {
\r
262 MessagePage page = new MessagePage();
\r
264 page.createControl(book);
\r
265 page.setMessage(Messages.PropertyPageView_noPropertiesAvailable);
\r
269 PropertyPage page = new PropertyPage(getSite());
\r
271 page.createControl(book);
\r
272 //System.out.println("PPV create default page: " + page);
\r
278 protected PageRec doCreatePage(IWorkbenchPart part) {
\r
280 // NOTE: If the default page should be shown, this method must return null.
\r
284 //System.out.println("PPV try to create page for part: " + (part != null ? part.getTitle() : null));
\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
293 page.createControl(getPageBook());
\r
294 //System.out.println("PPV created page control: " + page.getControl());
\r
295 return new PageRec(part, page);
\r
301 protected void doDestroyPage(IWorkbenchPart part, PageRec pageRecord) {
\r
302 //System.out.println("PPV destroy page for part: " + part.getTitle());
\r
304 IPropertyPage page = (IPropertyPage) pageRecord.page;
\r
306 pageRecord.dispose();
\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
319 private boolean isPropertyView(IWorkbenchPart part) {
\r
320 boolean ignore = false;
\r
322 if (part instanceof IWorkbenchPart3) {
\r
323 IWorkbenchPart3 part3 = (IWorkbenchPart3) part;
\r
324 ignore = Boolean.parseBoolean(part3.getPartProperty(PROP_PINNED));
\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
334 return this == part || ignore;
\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
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
350 ignoredViews.add(id);
\r
356 return ignoredViews;
\r
359 private boolean isViewIgnored(String partID) {
\r
360 return getIgnoredViews().contains(partID);
\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
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
377 public void partActivated(IWorkbenchPart part) {
\r
378 // if (bootstrapSelection == null && bootstrapOnly)
\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
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
398 bootstrapSelection = null;
\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
408 super.partClosed(part);
\r
412 protected void partHidden(IWorkbenchPart part) {
\r
413 // Fast views are quite unusable if this code is enabled.
\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
420 ISelectionListener immediateSelectionListener = new ISelectionListener() {
\r
422 private Throttler throttler = new Throttler(SWTThread.getThreadAccess(PlatformUI.getWorkbench().getDisplay()), 500, 3);
\r
425 public void selectionChanged(final IWorkbenchPart part, final ISelection selection) {
\r
427 // Do not process selections from self
\r
428 if(PropertyPageView.this == part) return;
\r
430 throttler.schedule(new Runnable() {
\r
433 public void run() {
\r
434 PropertyPageView.this.doSelectionChanged(part, selection);
\r
442 public ISelection getLastSelection() {
\r
443 return lastSelection;
\r
449 * @see org.eclipse.ui.ISelectionListener#selectionChanged(org.eclipse.ui.IWorkbenchPart,
\r
450 * org.eclipse.jface.viewers.ISelection)
\r
453 public void selectionChanged(IWorkbenchPart part, ISelection sel) {
\r
454 doSelectionChanged(part, sel);
\r
460 * @return <code>true</code> if the changed selection affected the view,
\r
461 * <code>false</code> otherwise
\r
463 boolean doSelectionChanged(IWorkbenchPart part, ISelection sel) {
\r
465 // we ignore our own selection or null selection
\r
466 if (isPropertyView(part) || sel == null) {
\r
469 // ignore workbench selections when pinned also
\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
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
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
492 if (lastPart != null) {
\r
493 lastPart.removePropertyListener(partPropertyListener);
\r
496 lastSelection = sel;
\r
497 lastSelections.put(part, sel);
\r
498 if (lastPart != null) {
\r
499 lastPart.addPropertyListener(partPropertyListener);
\r
502 updatePartName(ppage, sel);
\r
503 if (!sameSelection) {
\r
504 ppage.selectionChanged(part, sel);
\r
511 void updatePartName(IPropertyPage ppage, ISelection sel) {
\r
512 ppage.updatePartName(sel, partNameUpdateCallback);
\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
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
531 void doSetPartName(String partName) {
\r
532 // Is the page view disposed ??
\r
533 if (contextProvider == null)
\r
535 if (partName == null) {
\r
536 // Return to default
\r
537 partName = "Selection";
\r
539 setPartName(partName);
\r
542 public boolean isWorkbenchSelectionPinned() {
\r
543 return pinSelection;
\r
546 public void pinWorkbenchSelection(boolean pin) {
\r
547 if (pin == pinSelection)
\r
550 pinSelection = pin;
\r
551 setPartProperty(PROP_PINNED, Boolean.toString(pin));
\r
554 setTitleImage(resourceManager.createImage(pinned));
\r
556 setTitleImage(resourceManager.createImage(notPinned));
\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
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
577 private void updateContentDescription(boolean selectionPinned, IWorkbenchPart sourcePart) {
\r
578 if (selectionPinned) {
\r
579 if (sourcePart == null) {
\r
580 setContentDescription("No selection");
\r
582 sourcePart = getSourcePart(sourcePart);
\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
590 desc.append(sourcePart.getTitle());
\r
593 setContentDescription(desc.toString());
\r
596 setContentDescription("");
\r
600 IPropertyListener partPropertyListener = new IPropertyListener() {
\r
602 public void propertyChanged(Object source, int propId) {
\r
603 if (propId == IWorkbenchPart.PROP_TITLE) {
\r
604 updateContentDescription(pinSelection, lastPart);
\r
610 public IWorkbenchPart getContributingPart() {
\r