blob: 19e37a53ae1b6b7d9afb9ea64f05e922c462fa75 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.bpel.common.ui.composite;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.bpel.common.ui.CommonUIPlugin;
import org.eclipse.bpel.common.ui.Messages;
import org.eclipse.bpel.common.ui.Policy;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.EditorPart;
/**
* The CompositeEditor contains a collection of embedded editors.
* Each embedded editor provides its own contributions. The contributions
* are available for the user when the embedded editor is activated.
* Embedded editors have to be registered through the embeddedEditors
* extension point like the example bellow:
*
* <extension point="org.eclipse.bpel.common.ui.embeddedEditors">
* <editor
* id="com.examples.MyEmbeddedEditor"
* class="com.examples.MyEmbeddedEditor"
* contributorClass="com.examples.MyEmbeddedEditorActionBarContributor"/>
* </extension>
*
* Notes:
* Embedded editors can never be the workbench active part. The
* active part is aways the CompositeEditor.
*/
public abstract class CompositeEditor extends EditorPart {
protected EmbeddedEditorsCollection embeddedEditors;
protected IPropertyListener propertyListener;
protected CompositeEditorManager editorManager;
protected IEditorPart mainEditor;
/**
* Since subclasses might implement their own part listeners
* we make this attribute private so that there are no name
* conflicts and subclasses do not overwrite this listener by accident.
*/
private IPartListener partListener;
/**
* Represents the collection of embedded editors
* and the active editor index. It also helps to
* maintain the collection and the index in sync.
*/
class EmbeddedEditorsCollection {
private List<IEditorPart> editors;
private IEditorPart[] editorsArrayCache;
private int activeEditorIndex;
EmbeddedEditorsCollection() {
editors = new ArrayList<IEditorPart>(10);
editorsArrayCache = null;
activeEditorIndex = -1; // -1 means there are no active editors
}
void add(IEditorPart editor) {
editors.add(editor);
editorsArrayCache = null;
}
/**
* We should never be able to remove the active editor.
*/
void remove(IEditorPart editor) {
IEditorPart currentActiveEditor = getActiveEditor();
if (currentActiveEditor == editor) {
throw new IllegalArgumentException(Messages.CompositeEditor_0);
}
editors.remove(editor);
editorsArrayCache = null;
// we need to keep the index updated after removing an editor
setActiveEditor(currentActiveEditor);
}
IEditorPart[] getEmbeddedEditors() {
if (editorsArrayCache == null) {
editorsArrayCache = (IEditorPart[]) editors.toArray(new IEditorPart[editors.size()]);
}
return editorsArrayCache;
}
IEditorPart getActiveEditor() {
// there are no active editors yet
if (activeEditorIndex == -1) {
return null;
}
IEditorPart[] editors = getEmbeddedEditors();
return editors[activeEditorIndex];
}
boolean contains(IEditorPart editor) {
return editors.contains(editor);
}
void setActiveEditor(IEditorPart editor) {
if (editor == null) {
activeEditorIndex = -1;
return;
}
IEditorPart[] editors = getEmbeddedEditors();
for (int i = 0; i < editors.length; i++) {
if (editor == editors[i]) {
activeEditorIndex = i;
return;
}
}
}
void clearEditors() {
setActiveEditor(null);
editors.clear();
editorsArrayCache = null;
}
}
public CompositeEditor() {
embeddedEditors = new EmbeddedEditorsCollection();
}
/**
* Creates an embedded editor corresponding to the given editorId and
* connects it to the CompositeEditor.
* <p>
* It is important to notice that the returned editor will never be
* the workbench active part.
* <p>
* For each composite editor one of its embedded editors should be
* designated as the main editor. In order to do that you have to
* call <code>CompositeEditor#setMainEditor</code>.
*
* @param editor the embedded editor
* @param editorComposite the editor's parent composite
* @return IEditorPart the embedded editor instance
*
* @see CompositeEditor#setMainEditor
*/
public IEditorPart connectEditor(String editorId, IEditorInput input, Composite editorComposite) throws CoreException {
final IEditorPart editor = editorManager.createEditor(editorId, input);
embeddedEditors.add(editor);
Composite composite = new Composite(editorComposite, SWT.NONE);
composite.setLayout(new FillLayout());
editor.createPartControl(composite);
editor.addPropertyListener(getPropertyListener());
// This is the fundamental part of our implementation.
// Editors are activated when we get an Activate
// event from the editor's composite.
associate(editor, composite);
return editor;
}
/**
* Associates the control with the given editor. When the control
* is activated all the editor contributions will be activated
* as well. This is useful when the CompositeEditor is not only
* composed by embedded editor but also by other controls. If no editor
* is associated with these controls the available contributions
* (menus, toolbars, status line, etc...) will be the contributions
* for the last active editor and might cause confusion to the user.
*/
public void associate(final IEditorPart editor, Control control) {
control.addListener(SWT.Activate, new Listener() {
public void handleEvent(Event event) {
activateEditor(editor);
}
});
}
/**
* Activates contributions from the given editor and
* deactivates contributions from the current editor.
*/
protected void activateEditor(IEditorPart newEditor) {
IEditorPart oldEditor = embeddedEditors.getActiveEditor();
// It can only be null when the first editor is activated.
// If that is the case just go ahead and activate newEditor
if (oldEditor != null) {
if (oldEditor == newEditor) {
return;
}
editorManager.deactivate(oldEditor);
}
editorManager.activate(newEditor);
embeddedEditors.setActiveEditor(newEditor);
}
/**
* Removes the given editor from this composite editor. If the given
* editor is the current active editor the main editor will be activated
* instead. The main editor cannot be disconnected by calling this
* method.
*
* @exception CoreException if the editor cannot be disconnected. Reasons include:
* <ul>
* <li> The editor is the main editor.</li>
* <li> The editor is the current active editor and the main editor was not defined.</li>
* </ul>
*/
public void disconnectEditor(IEditorPart editor) throws CoreException {
if (editor == embeddedEditors.getActiveEditor()) {
if (mainEditor != null && editor != mainEditor) {
activateEditor(mainEditor);
} else {
IStatus status = new Status(IStatus.ERROR, CommonUIPlugin.PLUGIN_ID, ICompositeEditorConstants.COULD_NOT_DISCONNECT_EDITOR, NLS.bind(Messages.CompositeEditor_Cannot_disconnect_active_editor, (new Object[] { editor.getTitle() })), null);
throw new CoreException(status);
}
}
embeddedEditors.remove(editor);
editor.removePropertyListener(getPropertyListener());
CompositeEditorSite site = (CompositeEditorSite) editor.getSite();
CompositeEditorActionBars actionBars = (CompositeEditorActionBars) site.getActionBars();
actionBars.dispose();
site.dispose();
editor.dispose();
}
/**
* Listens and propagates all property changes from embedded editors.
* This is the default implementation. Subclasses may overwrite
* and specialize the behaviour.
*/
protected IPropertyListener getPropertyListener() {
if (propertyListener == null) {
propertyListener = new IPropertyListener() {
public void propertyChanged(Object source, int propId) {
firePropertyChange(propId);
}
};
}
return propertyListener;
}
/**
* Disconects all of the embedded editors. Subclasses should implement
* internalDispose() instead of dispose();
*/
@Override
public final void dispose() {
embeddedEditors.setActiveEditor(null);
internalDispose();
IEditorPart[] editors = embeddedEditors.getEmbeddedEditors();
for (int i = 0; i < editors.length; i++) {
try {
disconnectEditor(editors[i]);
} catch (CoreException e) {
// should never hapen because there should
// be no active editors at this point
}
}
getEditorSite().getPage().removePartListener(getPartlistener());
super.dispose();
getEditorSite().setSelectionProvider(null);
// discard our references to editors (to help reduce memory leakage)
embeddedEditors.clearEditors();
embeddedEditors = null;
}
/**
* Should be implemented by clients.
*/
protected void internalDispose() {
}
@Override
public boolean isDirty() {
IEditorPart[] editors = embeddedEditors.getEmbeddedEditors();
for (int i = 0; i < editors.length; i++) {
if (editors[i].isDirty()) {
return true;
}
}
return false;
}
/**
* Default implementation saves all of the embedded editors but
* it can be overwritten by subclasses.
*/
@Override
public void doSave(IProgressMonitor monitor) {
monitor = Policy.monitorFor(monitor);
try {
IEditorPart[] editors = getEmbeddedEditorsSaveOrder(embeddedEditors.getEmbeddedEditors());
String message = Messages.CompositeEditor_2;
monitor.beginTask(message, Math.max(1, editors.length));
for (int i = 0; i < editors.length; i++) {
final IProgressMonitor subMonitor = Policy.subMonitorFor(monitor, 1);
final IEditorPart editor = editors[i];
ISafeRunnable runnable = new ISafeRunnable() {
public void run() throws Exception {
editor.doSave(subMonitor);
}
public void handleException(Throwable exception) {
}
};
SafeRunnable.run(runnable);
}
} finally {
monitor.done();
}
}
/**
* Defines the order the embedded editors should be saved.
*
* @param editors the embedded editors that will be saved
* @return the same editors in the order they should be saved
*/
protected IEditorPart[] getEmbeddedEditorsSaveOrder(IEditorPart[] editors) {
return editors;
}
/**
* Default implementation. It can be overwritten by subclasses.
*/
@Override
public void setFocus() {
IEditorPart active = embeddedEditors.getActiveEditor();
if (active != null) {
active.setFocus();
}
}
/**
* Designate the given editor to be the main editor.
* The editor will be activated.
*
* @param editor the main editor
* @exception CoreException if the editor cannot be set as the main editor. Reasons include:
* <ul>
* <li> The editor is not connected to this composite editor.</li>
* </ul>
*/
public void setMainEditor(IEditorPart editor) throws CoreException {
if (!embeddedEditors.contains(editor)) {
IStatus status = new Status(IStatus.ERROR, CommonUIPlugin.PLUGIN_ID, ICompositeEditorConstants.EDITOR_NOT_CONNECTED, Messages.CompositeEditor_3 + editor.getTitle(), null);
throw new CoreException(status);
}
mainEditor = editor;
activateEditor(editor);
}
/**
* Returns the main editor or null if none has been defined.
*/
public IEditorPart getMainEditor() {
return mainEditor;
}
/**
* Returns the current active editor.
*/
public IEditorPart getActiveEditor() {
return embeddedEditors.getActiveEditor();
}
@Override
public void init(IEditorSite site, IEditorInput input) throws PartInitException {
setSite(site);
setInput(input);
editorManager = new CompositeEditorManager(site);
getEditorSite().getPage().addPartListener(getPartlistener());
getEditorSite().setSelectionProvider(new CompositeEditorSelectionProvider(this));
}
/**
* The workbench caches IActionBars according to the editor type (editor id),
* so editors of the same type share the same IActionBars instance.
* The CompositeEditor controls embedded editors' contributions by deactivating
* the previous active embedded editor and activating the new active embedded editor.
* If the actions are not deactivated they remain visible for the IActionBars.
* When switching between two CompositeEditor instances of the same type (id)
* the origin CompositeEditor does not know it has to deactivate its current active
* embedded editor and it causes incorrect or duplicate contributions to appear.
* The solution is to coordinate the current embedded editor activation and deactivation
* with the CompositeEditor (IWorkbenchPart) activation and deactivation.
*/
private IPartListener getPartlistener() {
if (partListener == null) {
partListener = new IPartListener() {
public void partActivated(IWorkbenchPart part) {
if (part == CompositeEditor.this) {
IEditorPart lastActiveEditor = embeddedEditors.getActiveEditor();
if (lastActiveEditor != null) {
editorManager.activate(lastActiveEditor);
}
}
}
public void partBroughtToTop(IWorkbenchPart part) {
}
public void partClosed(IWorkbenchPart part) {
}
public void partDeactivated(IWorkbenchPart part) {
if (part == CompositeEditor.this) {
editorManager.deactivate(embeddedEditors.getActiveEditor());
}
}
public void partOpened(IWorkbenchPart part) {
}
};
}
return partListener;
}
}