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