(refs #7568) Reinitialize diagram editor when element is switched
[simantics/platform.git] / bundles / org.simantics.modeling.ui / src / org / simantics / modeling / ui / diagramEditor / DiagramEditor.java
1 /*******************************************************************************
2  * Copyright (c) 2012, 2017 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  *     Semantum Oy - #7586
12  *******************************************************************************/
13 package org.simantics.modeling.ui.diagramEditor;
14
15 import java.lang.reflect.Constructor;
16 import java.util.function.Predicate;
17
18 import org.eclipse.core.runtime.IConfigurationElement;
19 import org.eclipse.core.runtime.IExecutableExtension;
20 import org.eclipse.core.runtime.IProgressMonitor;
21 import org.eclipse.core.runtime.Platform;
22 import org.eclipse.swt.SWT;
23 import org.eclipse.swt.widgets.Composite;
24 import org.eclipse.ui.IEditorInput;
25 import org.eclipse.ui.IEditorPart;
26 import org.eclipse.ui.IEditorReference;
27 import org.eclipse.ui.IEditorSite;
28 import org.eclipse.ui.IPartListener2;
29 import org.eclipse.ui.IWorkbenchPage;
30 import org.eclipse.ui.IWorkbenchPart;
31 import org.eclipse.ui.IWorkbenchPartReference;
32 import org.eclipse.ui.IWorkbenchWindow;
33 import org.eclipse.ui.PartInitException;
34 import org.eclipse.ui.PlatformUI;
35 import org.eclipse.ui.part.EditorPart;
36 import org.osgi.framework.Bundle;
37 import org.simantics.db.Resource;
38 import org.simantics.diagram.ui.WorkbenchSelectionProvider;
39 import org.simantics.g2d.diagram.IDiagram;
40 import org.simantics.modeling.ui.diagramEditor.DiagramViewer.DiagramViewerHost;
41 import org.simantics.ui.workbench.IResourceEditorInput;
42 import org.simantics.ui.workbench.IResourceEditorInput2;
43 import org.simantics.ui.workbench.IResourceEditorPart2;
44 import org.simantics.ui.workbench.ResourceEditorSupport;
45 import org.simantics.utils.DataContainer;
46 import org.simantics.utils.threads.IThreadWorkQueue;
47 import org.simantics.utils.threads.SWTThread;
48 import org.simantics.utils.ui.ErrorLogger;
49
50 /**
51  * A class for diagram editor parts that contains logic for destruction and
52  * (re)initialization of the actual diagram viewer and its controls during the
53  * life cycle of this editor part.
54  * 
55  * <p>
56  * To use this class in an editor part extension, define the following in the
57  * <code>class</code> attribute of the extension:
58  * 
59  * <pre>
60  * class="org.simantics.modeling.ui.diagramEditor.DiagramEditor:viewer=%VIEWER%"
61  * </pre>
62  * 
63  * where <code>%VIEWER%</code> is the name of the class that either is or
64  * extends {@link org.simantics.modeling.ui.diagramEditor.DiagramViewer}. The
65  * <code>viewer</code> argument tells {@link DiagramEditor} where to find the
66  * initializer for the diagram editor controls. The initializer must have a
67  * default constructor.
68  * 
69  * <p>
70  * This class is not intended to be extended by clients. Customizations should
71  * be performed through the viewer class.
72  * 
73  * @author Tuukka Lehtonen
74  * @author Antti Villberg
75  */
76 public class DiagramEditor extends EditorPart implements IResourceEditorPart2, IPartListener2, DiagramViewerHost, IExecutableExtension {
77
78     /**
79      * The {@value #ARG_VIEWER} argument for this editor part class tells the
80      * name of the class to use for initializing the diagram viewer, i.e.
81      * {@link #viewer}. The class is instantiated through reflection using the
82      * class loader of the bundle named {@link #viewerContributor}.
83      * 
84      * @see #setInitializationData(IConfigurationElement, String, Object)
85      */
86     public static final String    ARG_VIEWER = "viewer";
87
88     private Composite             parent;
89
90     private String                viewerContributor;
91     private String                viewerClassName;
92
93     private ResourceEditorSupport support;
94     private DiagramViewer         viewer;
95
96     /**
97      * Used for distributing the reference to the IDiagram eventually loaded by
98      * the diagram viewer to both the diagram viewer and
99      * {@link #createSelectionProvider()}. {@link DiagramViewerLoadJob} is what
100      * ultimately does the actual loading and sets this container's value.
101      * @see #createSelectionProvider()
102      * @see DiagramViewerLoadJob
103      */
104     protected DataContainer<IDiagram>    diagramContainer = new DataContainer<IDiagram>();
105     protected IThreadWorkQueue           swt;
106     protected WorkbenchSelectionProvider selectionProvider;
107
108     /**
109      * Reads the class arguments from the string in the data argument.
110      * 
111      * @see org.eclipse.ui.part.EditorPart#setInitializationData(org.eclipse.core.runtime.IConfigurationElement,
112      *      java.lang.String, java.lang.Object)
113      * @see #createViewer()
114      */
115     @Override
116     public void setInitializationData(IConfigurationElement cfig, String propertyName, Object data) {
117         super.setInitializationData(cfig, propertyName, data);
118
119         if (data instanceof String) {
120             viewerContributor = cfig.getContributor().getName();
121
122             String[] parameters = ((String) data).split(";");
123
124             for (String parameter : parameters) {
125                 String[] keyValue = parameter.split("=");
126                 if (keyValue.length > 2) {
127                     ErrorLogger.defaultLogWarning("Invalid parameter '" + parameter + ". Complete view argument: " + data, null);
128                     continue;
129                 }
130                 String key = keyValue[0];
131                 String value = keyValue.length > 1 ? keyValue[1] : "";
132
133                 if (ARG_VIEWER.equals(key)) {
134                     viewerClassName = value;
135                 } 
136             }
137         }
138     }
139
140     protected DiagramViewer createViewer() throws PartInitException {
141         if (viewerClassName == null)
142             throw new PartInitException(
143                     "DiagramViewer contributor class was not specified in editor extension's class attribute viewer-argument. contributor is '"
144                             + viewerContributor + "'");
145
146         try {
147             Bundle b = Platform.getBundle(viewerContributor);
148             if (b == null)
149                 throw new PartInitException("DiagramViewer '" + viewerClassName + "' contributor bundle '"
150                         + viewerContributor + "' was not found in the platform.");
151
152             Class<?> clazz = b.loadClass(viewerClassName);
153             if (!DiagramViewer.class.isAssignableFrom(clazz))
154                 throw new PartInitException("DiagramViewer class '" + viewerClassName + "' is not assignable to "
155                         + DiagramViewer.class + ".");
156
157             Constructor<?> ctor = clazz.getConstructor();
158             return (DiagramViewer) ctor.newInstance();
159         } catch (Exception e) {
160             throw new PartInitException("Failed to instantiate DiagramViewer implementation '" + viewerClassName
161                     + "' from bundle '" + viewerContributor + "'. See exception for details.", e);
162         }
163     }
164
165     @Override
166     public IResourceEditorInput getResourceInput() {
167         return viewer.getResourceInput();
168     }
169
170     @Override
171     public IResourceEditorInput2 getResourceInput2() {
172         return viewer.getResourceInput2();
173     }
174
175     public DiagramViewer getViewer() {
176         return viewer;
177     }
178
179     public Resource getRuntimeResource() {
180         DiagramViewer viewer = this.viewer;
181         return viewer != null ? viewer.getRuntime() : null;
182     }
183     
184     public Resource getInputResource() {
185         DiagramViewer viewer = this.viewer;
186         return viewer != null ? viewer.getInputResource() : null;
187     }
188
189     @Override
190     public void doSave(IProgressMonitor monitor) {
191     }
192
193     @Override
194     public void doSaveAs() {
195     }
196
197     @Override
198     public boolean isDirty() {
199         return false;
200     }
201
202     @Override
203     public boolean isSaveAsAllowed() {
204         return false;
205     }
206
207     @Override
208     public void init(IEditorSite site, IEditorInput input) throws PartInitException {
209         setSite(site);
210         setInput(input);
211
212         viewer = createViewer();
213
214         // selectionProvider MUST be created and attached to the workbench site:
215         //   1. only once during the life-cycle of this editor part
216         //   2. in SWT UI thread
217         //   3. at least before returning from #createPartControl
218         swt = SWTThread.getThreadAccess(PlatformUI.getWorkbench().getDisplay());
219         selectionProvider = createSelectionProvider();
220
221         viewer.init(this, site, input, diagramContainer, selectionProvider);
222
223         getSite().getPage().addPartListener(this);
224
225         support = new ResourceEditorSupport(this, viewer.getInputValidator());
226         support.activateValidation();
227     }
228
229     @Override
230     public void createPartControl(Composite parent) {
231         this.parent = parent;
232         initializeViewer();
233     }
234
235     private void initializeViewer() {
236         parent.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_WHITE));
237         viewer.createPartControl(parent);
238         // It is possible that something goes wrong and the parent gets disposed already
239         if(parent.isDisposed()) return;
240         parent.layout(true);
241     }
242
243     @Override
244     public void setFocus() {
245         if (viewer != null)
246             viewer.setFocus();
247     }
248
249     /**
250      * Override this to customize the kind of selection provider created for
251      * this {@link DiagramEditor}.
252      * 
253      * @return the selection provider to set for the site
254      */
255     protected WorkbenchSelectionProvider createSelectionProvider() {
256         return new DiagramViewerSelectionProvider(swt, getSite(), diagramContainer);
257     }
258
259     @SuppressWarnings("unchecked")
260     public <T> T getAdapter(Class<T> adapter) {
261         if (adapter == DiagramViewer.class)
262             return (T) viewer;
263         if (viewer == null)
264             return (T) super.getAdapter(adapter);
265
266         Object result = viewer.getAdapter(adapter);
267         if (result != null)
268             return (T) result;
269         return super.getAdapter(adapter);
270     }
271
272     @Override
273     public void dispose() {
274         getSite().getPage().removePartListener(this);
275
276         if (support != null) {
277             support.dispose();
278             support = null;
279         }
280
281         DISPOSING_POLICY.removeDisposer(disposer);
282         tryDisposeViewer();
283
284         super.dispose();
285     }
286
287     @Override
288     public void doSetPartName(String name) {
289         setPartName(name);
290     }
291
292     @Override
293     public void doSetTitleToolTip(String name) {
294         setTitleToolTip(name);
295     }
296
297     // BEGIN: IPartListener2 implementation
298
299     @Override
300     public void partActivated(IWorkbenchPartReference partRef) {
301     }
302
303     @Override
304     public void partBroughtToTop(IWorkbenchPartReference partRef) {
305     }
306
307     @Override
308     public void partClosed(IWorkbenchPartReference partRef) {
309     }
310
311     @Override
312     public void partDeactivated(IWorkbenchPartReference partRef) {
313     }
314
315     @Override
316     public void partOpened(IWorkbenchPartReference partRef) {
317     }
318
319     /**
320      * Disposes of the diagram viewer if not already disposed.
321      */
322     @Override
323     public void partHidden(IWorkbenchPartReference partRef) {
324         IWorkbenchPart part = partRef.getPart(false);
325         if (this.equals(part)) {
326             DISPOSING_POLICY.addDisposer(disposer);
327         }
328     }
329     
330     private static final DisposingPolicy DISPOSING_POLICY = 
331             new DisposingPolicy();
332
333     private Runnable disposer = () -> tryDisposeViewer();
334
335     private void tryDisposeViewer() {
336         if (viewer != null) {
337             Composite viewerComposite = viewer.getComposite();
338             viewer.dispose();
339             viewer = null;
340             if (viewerComposite != null) {
341                 viewerComposite.dispose();
342             }
343         }
344     }
345
346     /**
347      * Initializes the diagram viewer if not already initialized.
348      */
349     @Override
350     public void partVisible(IWorkbenchPartReference partRef) {
351         IWorkbenchPart part = partRef.getPart(false);
352         if (this.equals(part)) {
353             DISPOSING_POLICY.removeDisposer(disposer);
354             if (viewer == null) {
355                 try {
356                     viewer = createViewer();
357                     viewer.init(this, getEditorSite(), getEditorInput(), diagramContainer, selectionProvider);
358                     initializeViewer();
359                 } catch (PartInitException e) {
360                     // This should never happen!
361                     ErrorLogger.defaultLogError(e);
362                 }
363             }
364         }
365     }
366
367     @Override
368     public void partInputChanged(IWorkbenchPartReference partRef) {
369     }
370
371     // END: IPartListener2 implementation
372
373     /**
374      * Reinitialize this diagram editor from scratch.
375      * 
376      * <p>Must be invoked from the SWT thread.</p>
377      */
378     public void reinitializeViewer() {
379         if (viewer != null) {
380             DISPOSING_POLICY.removeDisposer(disposer);
381             tryDisposeViewer();
382             try {
383                 viewer = createViewer();
384                 viewer.init(this, getEditorSite(), getEditorInput(), diagramContainer, selectionProvider);
385                 initializeViewer();
386             } catch (PartInitException e) {
387                 // This should never happen!
388                 ErrorLogger.defaultLogError(e);
389             }
390         }
391     }
392
393     /**
394      * Reinitializes all {@link DiagramEditor} instances in all workbench windows for which
395      * the specified predicate returns <code>true</code>.
396      * 
397      * <p>Must be invoked from the SWT thread.</p>
398      * 
399      * @param predicate
400      *            tester for editor inputs
401      */
402     public static void reinitializeDiagram(Predicate<IEditorInput> predicate) {
403         for (IWorkbenchWindow window : PlatformUI.getWorkbench().getWorkbenchWindows()) {
404             for (IWorkbenchPage page : window.getPages()) {
405                 for (IEditorReference editorRef : page.getEditorReferences()) {
406                     try {
407                         IEditorInput input = editorRef.getEditorInput();
408                         if (predicate.test(input)) {
409                             IEditorPart editor = editorRef.getEditor(false);
410                             if (editor instanceof DiagramEditor)
411                                 ((DiagramEditor) editor).reinitializeViewer();
412                         }
413                     } catch (PartInitException e) {
414                         ErrorLogger.defaultLogError(e);
415                     }
416                 }
417             }
418         }
419     }
420
421     /**
422      * Reinitializes all DiagramEditor instances in all workbench windows that have
423      * the specified <code>diagram</code> as their input.
424      * 
425      * <p>Must be invoked from the SWT thread.</p>
426      * 
427      * @param diagram
428      *            the diagram resource for which to reinitialize all DiagramEditors
429      *            for
430      */
431     public static void reinitializeDiagram(Resource diagram) {
432         reinitializeDiagram(input -> input instanceof IResourceEditorInput
433                 && ((IResourceEditorInput) input).getResource().equals(diagram));
434     }
435
436 }