Java tutorial
/** * Copyright (c) 2009 Anyware Technologies and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Anyware Technologies - initial API and implementation * * $Id: EmfFormEditor.java,v 1.37 2010/03/17 10:44:45 bcabe Exp $ */ package org.eclipse.pde.emfforms.editor; import java.io.IOException; import java.io.InputStream; import java.util.*; import java.util.List; import org.eclipse.core.databinding.DataBindingContext; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.databinding.observable.value.WritableValue; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.*; import org.eclipse.emf.common.command.*; import org.eclipse.emf.common.notify.AdapterFactory; import org.eclipse.emf.common.ui.MarkerHelper; import org.eclipse.emf.common.ui.dialogs.DiagnosticDialog; import org.eclipse.emf.common.ui.viewer.IViewerProvider; import org.eclipse.emf.common.util.Diagnostic; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.databinding.EMFDataBindingContext; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EValidator; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.edit.command.CreateChildCommand; import org.eclipse.emf.edit.domain.*; import org.eclipse.emf.edit.provider.ComposedAdapterFactory; import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory; import org.eclipse.emf.edit.provider.resource.ResourceItemProviderAdapterFactory; import org.eclipse.emf.edit.ui.action.EditingDomainActionBarContributor; import org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider; import org.eclipse.emf.edit.ui.util.EditUIMarkerHelper; import org.eclipse.emf.edit.ui.util.EditUIUtil; import org.eclipse.emf.edit.ui.view.ExtendedPropertySheetPage; import org.eclipse.jface.dialogs.*; import org.eclipse.jface.viewers.*; import org.eclipse.jface.window.Window; import org.eclipse.pde.emfforms.editor.IEmfFormEditorConfig.VALIDATE_ON_SAVE; import org.eclipse.pde.emfforms.internal.Activator; import org.eclipse.pde.emfforms.internal.editor.*; import org.eclipse.pde.emfforms.internal.validation.ValidatingEContentAdapter; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.*; import org.eclipse.ui.*; import org.eclipse.ui.actions.WorkspaceModifyOperation; import org.eclipse.ui.dialogs.SaveAsDialog; import org.eclipse.ui.forms.editor.FormEditor; import org.eclipse.ui.forms.editor.IFormPage; import org.eclipse.ui.ide.IGotoMarker; import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.views.contentoutline.IContentOutlinePage; import org.eclipse.ui.views.properties.IPropertySheetPage; import org.eclipse.ui.views.properties.PropertySheetPage; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; /** * A {@link FormEditor} allowing to edit an EMF {@link EObject} in a convenient * way * * @param <O> * The type of the {@link EObject} to edit. */ public abstract class EmfFormEditor<O extends EObject> extends FormEditor implements IEditingDomainProvider, ISelectionProvider, IViewerProvider, IResourceChangeListener, IGotoMarker { /** * This keeps track of the editing domain that is used to track all changes * to the model. */ private EditingDomain _editingDomain; /** */ private IEmfFormEditorConfig<EmfFormEditor<O>, O> _editorConfig; /** * This is the one adapter factory used for providing views of the model. */ private ComposedAdapterFactory _adapterFactory; private final IObservableValue _observableValue = new WritableValue(); private O _currentEObject; /** * This keeps track of all the * {@link org.eclipse.jface.viewers.ISelectionChangedListener}s that are * listening to this editor. */ protected Collection<ISelectionChangedListener> selectionChangedListeners = new ArrayList<ISelectionChangedListener>(); protected ISelection editorSelection = StructuredSelection.EMPTY; private CommandStackListener _commandStackListener = new CommandStackListener() { public void commandStackChanged(final EventObject event) { getContainer().getDisplay().asyncExec(new Runnable() { public void run() { handleCommandStackChanged(event); } }); } }; private PropertySheetPage propertySheetPage; /** * The MarkerHelper is responsible for creating workspace resource markers presented in Eclipse's Problems View. */ protected MarkerHelper markerHelper = new EditUIMarkerHelper(); private DataBindingContext _bindingContext; private ValidatingEContentAdapter _validator; private boolean isSaving = false; private EmfContentOutlinePage contentOutlinePage; private ResourceDeltaVisitor<O> _visitor; public EmfFormEditor() { this._editorConfig = getFormEditorConfig(); init(); } /** * @return the {@link IEmfFormEditorConfig} the editor will use as its configuration */ protected IEmfFormEditorConfig<EmfFormEditor<O>, O> getFormEditorConfig() { return new DefaultEmfFormEditorConfig<EmfFormEditor<O>, O>(this); } private void init() { _editingDomain = createEditingDomain(); _bindingContext = new EMFDataBindingContext(); _validator = new ValidatingEContentAdapter(this); // Add a listener to set the most recent command's affected objects to // be the selection of the viewer with focus. // getEditingDomain().getCommandStack().addCommandStackListener(_commandStackListener); } /** * This is called during startup. */ @Override public void init(IEditorSite site, IEditorInput editorInput) { setSite(site); setInputWithNotify(editorInput); setPartName(editorInput.getName()); // -- To manage Copy/Cut/Paste site.setSelectionProvider(this); } @Override public void dispose() { getEditingDomain().getCommandStack().removeCommandStackListener(_commandStackListener); if (_adapterFactory != null) { _adapterFactory.dispose(); } ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); super.dispose(); } /** * This sets up the editing domain for the model editor. */ protected EditingDomain createEditingDomain() { _adapterFactory = new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE); _adapterFactory.addAdapterFactory(new ResourceItemProviderAdapterFactory()); _adapterFactory.addAdapterFactory(this.getSpecificAdapterFactory()); _adapterFactory.addAdapterFactory(new ReflectiveItemProviderAdapterFactory()); // Create the command stack that will notify this editor as commands are // executed. BasicCommandStack commandStack = new BasicCommandStack(); if (getEditorConfig().isUsingSharedClipboard()) return new SharedClipboardAdapterFactoryEditingDomain(_adapterFactory, commandStack, new HashMap<Resource, Boolean>()); // else return new AdapterFactoryEditingDomain(_adapterFactory, commandStack, new HashMap<Resource, Boolean>()); } protected abstract AdapterFactory getSpecificAdapterFactory(); @Override protected void addPages() { // Creates the model from the editor input createModel(); try { for (IEmfFormPage page : getPagesToAdd()) addPage(page); addSourcePage(); int i = 0; for (Image img : getPagesImages()) setPageImage(i++, img); } catch (PartInitException e) { Activator.logException(e, Messages.EmfFormEditor_InitError); } } private void addSourcePage() throws PartInitException { if (!getEditorConfig().isShowSourcePage()) return; boolean isXmlUiBundlePresent = false; BundleContext bc = Activator.getDefault().getBundle().getBundleContext(); for (Bundle b : bc.getBundles()) { if ("org.eclipse.wst.xml.ui".equals(b.getSymbolicName())) { //$NON-NLS-1$ isXmlUiBundlePresent = true; break; } } if (isXmlUiBundlePresent) addPage(new XmlSourcePage(this)); else addPage(new SimpleSourcePage(this)); } /** * @throws PartInitException */ protected abstract List<? extends IEmfFormPage> getPagesToAdd() throws PartInitException; protected abstract List<Image> getPagesImages(); @Override public void doSave(IProgressMonitor monitor) { try { isSaving = true; internalDoValidate(monitor); // Do the work within an operation because this is a long running // activity that modifies the workbench. WorkspaceModifyOperation operation = new WorkspaceModifyOperation() { // This is the method that gets invoked when the operation runs. @Override public void execute(IProgressMonitor monitor) throws CoreException { try { internalDoSave(monitor); } catch (IOException ioe) { throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0, Messages.EmfFormEditor_SaveError, ioe)); } } }; try { // This runs the options, and shows progress. new ProgressMonitorDialog(getSite().getShell()).run(true, false, operation); // Refresh the necessary state. // if we have a BasicCommandStack (that's to say almost all the time), don't flush it but call saveIsDone instead if (getEditingDomain().getCommandStack() instanceof BasicCommandStack) ((BasicCommandStack) getEditingDomain().getCommandStack()).saveIsDone(); else getEditingDomain().getCommandStack().flush(); firePropertyChange(IEditorPart.PROP_DIRTY); } catch (Exception exception) { // Something went wrong that shouldn't. MessageDialog.openError(getSite().getShell(), Messages.EmfFormEditor_SaveError_Title, Messages.EmfFormEditor_SaveError_Msg); Activator.logException(exception, Messages.EmfFormEditor_SaveError_Exception); } } catch (OperationCanceledException oce) { // Do nothing } finally { isSaving = false; } } /** * @param monitor * @throws IOException */ protected void internalDoSave(IProgressMonitor monitor) throws IOException { // Save only resources that have actually changed. final Map<Object, Object> saveOptions = new HashMap<Object, Object>(); saveOptions.put(Resource.OPTION_SAVE_ONLY_IF_CHANGED, Resource.OPTION_SAVE_ONLY_IF_CHANGED_MEMORY_BUFFER); // Save the resources to the file system. boolean first = true; for (Resource resource : getEditingDomain().getResourceSet().getResources()) { if ((first || !resource.getContents().isEmpty() || isPersisted(resource)) && !getEditingDomain().isReadOnly(resource)) { resource.save(saveOptions); first = false; } } } /** * @param monitor */ protected void internalDoValidate(IProgressMonitor monitor) { VALIDATE_ON_SAVE validateOnSave = getEditorConfig().getValidateOnSave(); // result of the Validation Diagnostic diagnostic = Diagnostic.OK_INSTANCE; // if the validation is asked by the user if (validateOnSave != VALIDATE_ON_SAVE.NO_VALIDATION) diagnostic = _validator.validate(getCurrentEObject()); if (diagnostic.getSeverity() != Diagnostic.OK) { switch (validateOnSave) { case VALIDATE_AND_WARN: int dialogResult = new DiagnosticDialog(getSite().getShell(), Messages.EmfFormEditor_DiaDialog_InvalidModel_Title, null, diagnostic, Diagnostic.ERROR | Diagnostic.WARNING) { @Override protected Control createMessageArea(Composite composite) { message = Messages.EmfFormEditor_ValidationWarn_Msg; return super.createMessageArea(composite); } @Override protected void createButtonsForButtonBar(Composite parent) { // create OK and Details buttons createButton(parent, IDialogConstants.OK_ID, IDialogConstants.YES_LABEL, true); createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.NO_LABEL, true); createDetailsButton(parent); } }.open(); if (dialogResult != Window.OK) { if (monitor != null) monitor.setCanceled(true); throw new OperationCanceledException(); } break; case VALIDATE_AND_ABORT: new DiagnosticDialog(getSite().getShell(), Messages.EmfFormEditor_DiaDialog_InvalidModel_Title, null, diagnostic, Diagnostic.ERROR | Diagnostic.WARNING) { @Override protected Control createMessageArea(Composite composite) { message = Messages.EmfFormEditor_ValidationError_Msg; return super.createMessageArea(composite); } }.open(); monitor.setCanceled(true); throw new OperationCanceledException(); default: // fall through } } } /** * This returns whether something has been persisted to the URI of the * specified resource. The implementation uses the URI converter from the * editor's resource set to try to open an input stream. */ protected boolean isPersisted(Resource resource) { boolean result = false; try { InputStream stream = getEditingDomain().getResourceSet().getURIConverter() .createInputStream(resource.getURI()); if (stream != null) { result = true; stream.close(); } } catch (IOException e) { // Ignore } return result; } /** * This also changes the editor's input. */ @Override public void doSaveAs() { SaveAsDialog saveAsDialog = new SaveAsDialog(getSite().getShell()); saveAsDialog.open(); IPath path = saveAsDialog.getResult(); if (path != null) { IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path); if (file != null) { doSaveAs(URI.createPlatformResourceURI(file.getFullPath().toString(), true), new FileEditorInput(file)); } } } protected void doSaveAs(URI uri, IEditorInput editorInput) { (getEditingDomain().getResourceSet().getResources().get(0)).setURI(uri); setInputWithNotify(editorInput); setPartName(editorInput.getName()); IProgressMonitor progressMonitor = getActionBars().getStatusLineManager() != null ? getActionBars().getStatusLineManager().getProgressMonitor() : new NullProgressMonitor(); doSave(progressMonitor); } /** * This is for implementing {@link IEditorPart} and simply tests the command * stack. */ @Override public boolean isDirty() { return ((BasicCommandStack) getEditingDomain().getCommandStack()).isSaveNeeded(); } @Override public final boolean isSaveAsAllowed() { return getEditorConfig().isSaveAsAllowed(); } @Override protected PDEFormToolkit createToolkit(Display display) { return getEditorConfig().createPDEFormToolkit(display); } /** * This is the method called to load a resource into the editing domain's * resource set based on the editor's input. */ public void createModel() { URI resourceURI = EditUIUtil.getURI(getEditorInput()); Resource resource; try { // Load the resource through the editing domain. resource = getEditingDomain().getResourceSet().getResource(resourceURI, true); } catch (Exception e) { resource = getEditingDomain().getResourceSet().getResource(resourceURI, false); } setMainResource(resource); ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE); } public void setMainResource(Resource resource) { if (_currentEObject != null) _currentEObject.eAdapters().remove(_validator); _currentEObject = (O) resource.getContents().get(0); _observableValue.setValue(_currentEObject); _currentEObject.eAdapters().add(_validator); validate(); } /** * This returns the editing domain as required by the * {@link IEditingDomainProvider} interface. This is important for * implementing the static methods of {@link AdapterFactoryEditingDomain} * and for supporting {@link org.eclipse.emf.edit.ui.action.CommandAction}. */ public EditingDomain getEditingDomain() { return _editingDomain; } public EditingDomainActionBarContributor getActionBarContributor() { return (EditingDomainActionBarContributor) getEditorSite().getActionBarContributor(); } public IActionBars getActionBars() { return getActionBarContributor().getActionBars(); } /** * @return The {@link O} currently edited */ public O getCurrentEObject() { return _currentEObject; } public IObservableValue getInputObservable() { return _observableValue; } /** * @param viewer */ public void addViewerToListenTo(StructuredViewer viewer) { // This listener is used to manage copy/paste... viewer.addSelectionChangedListener(new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent selectionChangedEvent) { setSelection(selectionChangedEvent.getSelection()); } }); } public void addSelectionChangedListener(ISelectionChangedListener listener) { selectionChangedListeners.add(listener); } public ISelection getSelection() { return editorSelection; } public boolean isSaving() { return isSaving; } public void removeSelectionChangedListener(ISelectionChangedListener listener) { selectionChangedListeners.remove(listener); } public void setSelection(ISelection selection) { editorSelection = selection; for (ISelectionChangedListener listener : selectionChangedListeners) { listener.selectionChanged(new SelectionChangedEvent(this, selection)); } } public Viewer getViewer() { AbstractEmfFormPage page = (AbstractEmfFormPage) pages.get(getActivePage()); return page.getViewer(); } /** * @param event */ protected void handleCommandStackChanged(final EventObject event) { // the edited object has been changed firePropertyChange(IWorkbenchPartConstants.PROP_DIRTY); // since the title of the editor is, most of the time, computed using some object attribute, ask the editor to refresh it! firePropertyChange(IWorkbenchPartConstants.PROP_TITLE); IFormPage activePage = getActivePageInstance(); if (activePage != null) { if (((AbstractEmfFormPage) activePage).getViewer() != null) { // Select the affected objects in the viewer Command mostRecentCommand = ((CommandStack) event.getSource()).getMostRecentCommand(); if (mostRecentCommand != null) { if (mostRecentCommand instanceof CreateChildCommand) { Collection<?> objects = mostRecentCommand.getAffectedObjects(); if (objects != null && !objects.isEmpty()) ((AbstractEmfFormPage) activePage).getViewer().setSelection( new StructuredSelection(objects.toArray()[objects.size() - 1]), true); } } } } } /** * This accesses a cached version of the content outliner. */ private IContentOutlinePage getContentOutlinePage() { if (contentOutlinePage == null) { contentOutlinePage = new EmfContentOutlinePage(this); contentOutlinePage.setViewerInput(getEditorConfig().getOutlineInput(getCurrentEObject())); contentOutlinePage.setViewerComparator(getOutlineComparator()); // Listen to selection so that we can handle it is a special way. contentOutlinePage.addSelectionChangedListener(new ISelectionChangedListener() { // This ensures that we handle selections correctly. public void selectionChanged(SelectionChangedEvent event) { handleContentOutlineSelection(event.getSelection(), getViewer()); } }); addSelectionChangedListener(new ISelectionChangedListener() { // This ensures that we handle selections correctly. public void selectionChanged(SelectionChangedEvent event) { handleContentOutlineSelection(event.getSelection(), contentOutlinePage.getViewer()); } }); } return contentOutlinePage; } /** * Subclasses may override this method to provide their own {@link ViewerComparator} to be used in the outline's {@link TreeViewer}. It could be useful in case you want to rearrange elements * @return the {@link ViewerComparator} to use to render the outline's {@link TreeViewer}. Default implementation return <code>null</code> */ protected ViewerComparator getOutlineComparator() { return null; } /** * This deals with how we want selection in the outline to affect the other views.<br><br> * <strong>Clients can override this method</strong> to have some special behaviour on selection changed events in the outline, such * as giving focus to a particular page of the editor. * @param selection the current selection in the outline * @param viewerToSnych the viewer of the active page (if any) */ public void handleContentOutlineSelection(ISelection selection, Viewer viewerToSnych) { if (!selection.isEmpty() && selection instanceof IStructuredSelection && viewerToSnych != null) { if (((IStructuredSelection) selection) .getFirstElement() != ((IStructuredSelection) viewerToSnych.getSelection()).getFirstElement()) { viewerToSnych.setSelection( new StructuredSelection(((IStructuredSelection) selection).getFirstElement())); } } } public void resourceChanged(IResourceChangeEvent event) { IResourceDelta delta = event.getDelta(); try { _visitor = new ResourceDeltaVisitor<O>(this); delta.accept(_visitor); } catch (CoreException ce) { Activator.log(ce); } } public ComposedAdapterFactory getAdapterFactory() { return _adapterFactory; } @SuppressWarnings("unchecked") @Override public Object getAdapter(Class key) { if (key.equals(IContentOutlinePage.class)) { return getFormEditorConfig().isShowOutlinePage() ? getContentOutlinePage() : null; } if (key.equals(IPropertySheetPage.class)) { return getPropertySheetPage(); } if (key.equals(IGotoMarker.class)) { return this; } return super.getAdapter(key); } public IPropertySheetPage getPropertySheetPage() { if (propertySheetPage == null) { propertySheetPage = new ExtendedPropertySheetPage((AdapterFactoryEditingDomain) _editingDomain) { @Override public void setSelectionToViewer(List<?> selection) { // TODO // EmfFormEditor.this.setSelectionToViewer(selection); EmfFormEditor.this.setFocus(); } @Override public void setActionBars(IActionBars actionBars) { super.setActionBars(actionBars); if (getActionBarContributor() != null) { getActionBarContributor().shareGlobalActions(this, actionBars); } } }; propertySheetPage.setPropertySourceProvider(new AdapterFactoryContentProvider(_adapterFactory)); } return propertySheetPage; } /** * {@inheritDoc} */ public void gotoMarker(IMarker marker) { if (getViewer() == null) return; // atm we only handle in-viewer selection try { if (marker.getType().equals(EValidator.MARKER)) { String uriAttribute = marker.getAttribute(EValidator.URI_ATTRIBUTE, null); if (uriAttribute != null) { URI uri = URI.createURI(uriAttribute); EObject eObject = getEditingDomain().getResourceSet().getEObject(uri, true); if (eObject != null && (getEditingDomain() instanceof AdapterFactoryEditingDomain)) { AdapterFactoryEditingDomain editingDomain = (AdapterFactoryEditingDomain) getEditingDomain(); if (getViewer() != null) { getViewer().setSelection(new StructuredSelection( Collections.singleton(editingDomain.getWrapper(eObject)).toArray())); } } } } } catch (CoreException exception) { Activator.log(exception); } } public DataBindingContext getDataBindingContext() { return _bindingContext; } @Override public void setFocus() { super.setFocus(); // for some reason, the code below is needed to workaround a bug on GTK where the actual // content of the first page of the editor doesn't correctly get focus when the editor is opened :-/) if (getActivePageInstance() != null) getActivePageInstance().setFocus(); } public abstract String getID(); public IEmfFormEditorConfig<EmfFormEditor<O>, O> getEditorConfig() { return _editorConfig; } /* package */void validate() { // TODO perform validation in a separate job _validator.validate(); } }