/*******************************************************************************
* Copyright (c) 2010-2015 Henshin developers. 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:
* TU Berlin, University of Luxembourg, SES S.A.
*******************************************************************************/
package de.tub.tfs.muvitor.ui;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EventObject;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.draw2d.ConnectionLayer;
import org.eclipse.draw2d.FigureCanvas;
import org.eclipse.draw2d.MarginBorder;
import org.eclipse.draw2d.Viewport;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.parts.ScrollableThumbnail;
import org.eclipse.draw2d.parts.Thumbnail;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.gef.EditDomain;
import org.eclipse.gef.EditPartFactory;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.KeyHandler;
import org.eclipse.gef.KeyStroke;
import org.eclipse.gef.LayerConstants;
import org.eclipse.gef.SnapToGrid;
import org.eclipse.gef.commands.CommandStack;
import org.eclipse.gef.commands.CommandStackListener;
import org.eclipse.gef.dnd.TemplateTransferDragSourceListener;
import org.eclipse.gef.dnd.TemplateTransferDropTargetListener;
import org.eclipse.gef.editparts.ScalableFreeformRootEditPart;
import org.eclipse.gef.editparts.ZoomManager;
import org.eclipse.gef.rulers.RulerProvider;
import org.eclipse.gef.ui.actions.ActionBarContributor;
import org.eclipse.gef.ui.actions.ActionRegistry;
import org.eclipse.gef.ui.actions.DeleteAction;
import org.eclipse.gef.ui.actions.GEFActionConstants;
import org.eclipse.gef.ui.actions.SelectionAction;
import org.eclipse.gef.ui.actions.UpdateAction;
import org.eclipse.gef.ui.actions.WorkbenchPartAction;
import org.eclipse.gef.ui.actions.ZoomComboContributionItem;
import org.eclipse.gef.ui.actions.ZoomInAction;
import org.eclipse.gef.ui.actions.ZoomOutAction;
import org.eclipse.gef.ui.palette.FlyoutPaletteComposite;
import org.eclipse.gef.ui.palette.PaletteViewer;
import org.eclipse.gef.ui.palette.PaletteViewerProvider;
import org.eclipse.gef.ui.parts.GraphicalEditorWithFlyoutPalette;
import org.eclipse.gef.ui.parts.GraphicalViewerKeyHandler;
import org.eclipse.gef.ui.parts.ScrollingGraphicalViewer;
import org.eclipse.gef.ui.parts.SelectionSynchronizer;
import org.eclipse.gef.ui.rulers.RulerComposite;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.SubActionBars;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.actions.RetargetAction;
import org.eclipse.ui.part.IPage;
import org.eclipse.ui.part.IPageSite;
import org.eclipse.ui.part.Page;
import org.eclipse.ui.part.PageBookView;
import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.osgi.service.prefs.Preferences;
import de.tub.tfs.muvitor.actions.ExportViewerImageAction;
import de.tub.tfs.muvitor.actions.MoveNodeAction;
import de.tub.tfs.muvitor.actions.MuvitorActionBarContributor;
import de.tub.tfs.muvitor.actions.SelectAllInMultiViewerAction;
import de.tub.tfs.muvitor.actions.TrimViewerAction;
import de.tub.tfs.muvitor.animation.IGraphicalViewerProvider;
import de.tub.tfs.muvitor.gef.palette.MuvitorPaletteRoot;
import de.tub.tfs.muvitor.ui.utils.MuvitorNotifierService;
import de.tub.tfs.muvitor.ui.utils.SelectionProviderIntermediate;
import de.tub.tfs.muvitor.ui.utils.ZoomManagerDelegate;
/**
* This is an {@link IPage} that contains some {@link GraphicalViewer}s as well
* as a {@link FlyoutPaletteComposite} and that manages changing of the current
* viewer. It is meant to be used in a {@link MuvitorPageBookView} like a
* regular graphical GEF editor. Like an GEF editor this page has its own
* {@link EditDomain} and {@link ActionRegistry} but uses the
* {@link CommandStack} and some general action instances (which we call shared)
* from the main {@link MuvitorTreeEditor}.
*
* <p>
* On calling {@link MuvitorTreeEditor#showView(String, EObject)}) with the ID
* (as in plugin.xml) of this page's parent {@link MuvitorPageBookView}, the top
* model element and a parent editor are set in the constructor. A
* {@link ZoomManagerDelegate} for a {@link ZoomComboContributionItem} is
* provided via {@link #getAdapter(Class)} to the workbench page's part service.
*
* <p>
* This class should be used in the MuvitorKit (with a central
* {@link MuvitorTreeEditor}) only. But for documentation, the following
* enumerates what the main editor provides to this page via getAdapter(): <br>
* <ul>
* <li>a shared central {@link CommandStack} which will be used for this page's
* {@link EditDomain} and to update actions.
* <li>a {@link SelectionSynchronizer} to synchronize this page's
* {@link GraphicalViewer} with {@link EditPartViewer}s in other view components
* of this editor that show parts of the main editor's model.
* <li>a {@link KeyHandler} (optional, may be null) which will be set as parent
* for the Keyhandler of this page's {@link GraphicalViewer}.
* </ul>
* </p>
*
* <p>
* When initialized and in {@link #createControl(Composite)} this class does the
* following:
* <ol>
* <li>The page registers itself as an EMF eAdapter to the {@link EObject} model
* to be able to react on notifications of the EMF model. Subclasses may use
* this as described below.
* <li>The page registers itself as an {@link CommandStackListener} on the
* editor's {@link CommandStack} to update actions on command stack changes.
* <li>A {@link FlyoutPaletteComposite} is being created which uses the
* {@link Preferences} of the {@link Plugin} delivered by the parent editor.
* <li>Some {@link MultiViewerPageViewer}s (the number depends on
* {@link #getViewerContents()} are being created and added to the
* {@link SelectionSynchronizer} delivered by the parent editor. The objects
* returned by {@link #getViewerContents()} are set as contents of these
* viewers. <br>
* <li>A {@link ScrollableThumbnail} showing a miniature overview of the current
* viewer is being installed below the palette.
* <li>The first viewer is set as the current viewer.
* <li>Actions are being created: {@link ZoomInAction}, {@link ZoomOutAction}
* and custom Actions defined in the abstract method
* {@link #createCustomActions()}. Handles for some standard
* {@link RetargetAction}s from the {@link ActionFactory} besides zooming and
* undo/redo are being registered if they have been created in
* {@link #createCustomActions()}: COPY, CUT, PASTE, DELETE. <br>
* </ol>
* </p>
* <b>For a guide on how to create custom actions have a look at the
* documentation of {@link #createCustomActions()}.</b>
*
* <p>
* This page uses an own minimally modified kind of edit part viewer, the
* {@link MultiViewerPageViewer}. Such a viewer notices if a mouse click occurs
* on it and sets itself as the current viewer which performs the following
* updates by calling {@link #setCurrentViewer(MultiViewerPageViewer)} for
* itself:
* <ol>
* <li>The clicked viewer is set as {@link ISelectionProvider} of the
* {@link IPageSite}, so all components and in particular all
* {@link SelectionAction}s of the editor are aware of the current selection in
* the current viewer.
* <li>The page registers itself as an {@link ISelectionChangedListener} on the
* viewer to be able to update the {@link UpdateAction}s in its
* {@link ActionRegistry} on selection changes.
* <li>The {@link ScrollableThumbnail} is being redirected to show the new
* current viewer.
* <li>The {@link ZoomManager} of the clicked viewer is set as delegating target
* in the {@link ZoomManagerDelegate}, so the central
* {@link ZoomComboContributionItem} in the editor's action bar acts on the
* currently selected viewer.
* </ol>
* <b>The contents of an existing viewer may be replaced with another object via
* {@link #setViewersContents(int, EObject)}. As well, you may hide or restore a
* particular viewer on this page with
* {@link #setViewerVisibility(int, boolean)}</b>
* </p>
*
* <p>
* <b>The following methods have to be implemented by subclasses.</b> Consider
* the documentation of each method as well, as they provide some hints!
* <ul>
* <li> {@link #createCustomActions()}: custom actions may be registered here.
* <li> {@link #createContextMenuProvider(EditPartViewer)}: must return a
* {@link ContextMenuProviderWithActionRegistry} defining the context menu for
* the passed viewer.
* <li> {@link #createEditPartFactory()}: must return a suitable
* {@link EditPartFactory} to create edit parts for the model elements.
* <li> {@link #createPaletteRoot()}: must return a {@link MuvitorPaletteRoot}
* defining what the palette should contain.
* <li> {@link #getThumbSashWeights()}: must return an array of two integers
* describing the ratio of how the fly-out composite's space is divided between
* the palette and the thumbnail.
* <li> {@link #getViewerContents()} specifies the objects that will be set as
* contents for the viewers.
* <li> {@link #setupKeyHandler(KeyHandler)}: In this method {@link KeyStroke}s
* may be associated with previously defined actions for the viewers.
* <li>
* {@link #notifyChanged(Notification)}: Optionally, this may be overridden for
* reacting on model change notifications. See there for details on overriding.
* </ul>
* </p>
*
* <p>
* This page is prepared to work with the MuvitorKits animation package; it
* implements the {@link IGraphicalViewerProvider} interface.
* </p>
*
* <p>
* Originally, this class was considered to roughly work like
* {@link GraphicalEditorWithFlyoutPalette}.
* </p>
*
* @author Tony Modica
*/
public abstract class MuvitorPage extends Page implements IAdaptable,
CommandStackListener, IGraphicalViewerProvider, ISelectionListener {
/**
* This local class extends {@link ScrollingGraphicalViewer} by a
* {@link MouseListener} that sets this viewer as the page's current viewer
* when a mouse click occurs on this viewer.
*
* @author Tony Modica
*/
final public class MultiViewerPageViewer extends ScrollingGraphicalViewer {
/**
* A listener that notices when the mouse acts on this viewer.
*/
private final MouseListener mouseListener;
public MultiViewerPageViewer() {
mouseListener = new MouseAdapter() {
@Override
public final void mouseDown(final MouseEvent e) {
setCurrentViewer(MultiViewerPageViewer.this);
}
};
}
/**
* @return The page that hosts this graphical viewer.
*/
public final MuvitorPage getHostPage() {
return MuvitorPage.this;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.gef.ui.parts.GraphicalViewerImpl#hookControl()
*/
@Override
protected final void hookControl() {
super.hookControl();
super.getControl().addMouseListener(mouseListener);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.gef.ui.parts.GraphicalViewerImpl#unhookControl()
*/
@Override
protected final void unhookControl() {
super.getControl().removeMouseListener(mouseListener);
super.unhookControl();
}
}
static public final class MuvitorRulerProvider extends RulerProvider {
@Override
public final Object getRuler() {
return MuvitorRulerProvider.this;
}
@Override
public final int getUnit() {
return RulerProvider.UNIT_PIXELS;
}
}
/**
* The action registry that holds the actions for this page.
*/
private final ActionRegistry actionRegistry = new ActionRegistry();
/**
* The EMF adapter listening to this pages's model EObject.
*/
private final Adapter adapter = new AdapterImpl() {
@Override
public void notifyChanged(final Notification msg) {
MuvitorPage.this.notifyChanged(msg);
MuvitorNotifierService.notifyListeners(msg);
}
};
/**
* The viewer that is
* <ul>
* <li>showed in the {@link Thumbnail},
* <li>the current {@link ISelectionProvider} for this page's
* {@link IPageSite} and
* <li>observed for selection changes
* <li>providing a {@link ZoomManager} for the zoom actions via a
* {@link ZoomManagerDelegate}.
* </ul>
*
* @see #setCurrentViewer(roneditor.ui.part.createActions.MultiViewerPageViewer)
*/
private MultiViewerPageViewer currentViewer;
/**
* The {@link EditDomain} managing this page like an encapsulated editor.
*/
private EditDomain editDomain;
/**
* The main {@link Control} of this page.
*/
private FlyoutPaletteComposite flyoutPaletteComposite;
/**
* A shared {@link KeyHandler} for the viewers on this page.
*/
private KeyHandler keyHandler;
/**
* This enables this page to switch the actual selection-providing viewer.
*
* @see #setCurrentViewer(MultiViewerPageViewer)
*/
private final SelectionProviderIntermediate selectionProvider = new SelectionProviderIntermediate();
/**
* A thumbnail showing a miniature of what the viewer contains.
*/
private ScrollableThumbnail thumbnail;
private final MuvitorPageBookView view;
/**
* The list of viewers that are hosted by this page. Its length matches
* {@link #getNumberOfViewers()} and the length of viewerContents.
*/
private final List<MultiViewerPageViewer> viewers = new ArrayList<MultiViewerPageViewer>();
/**
* This subclassed {@link ZoomManager} is set for the zoom actions and the
* returned by {@link #getAdapter(Class)} for the
* {@link ZoomComboContributionItem} created in the
* {@link ActionBarContributor}. It delegates to the {@link ZoomManager} of
* {@link #currentViewer}.
*
* @see #setCurrentViewer(MultiViewerPageViewer)
*/
private final ZoomManagerDelegate zoomManagerDelegate = new ZoomManagerDelegate();
/**
* @param view
* the MuvitorPageBookView that hosts this page
*/
public MuvitorPage(final MuvitorPageBookView view) {
this.view = view;
}
/**
* If some command manipulates the model and is put to the command stack the
* actions are updated. Remember that the command stack is shared with the
* parent {@link MuvitorTreeEditor}!
*/
/*
* (non-Javadoc)
*
* @see
* org.eclipse.gef.commands.CommandStackListener#commandStackChanged(java
* .util.EventObject)
*/
@Override
public final void commandStackChanged(final EventObject event) {
updateActions();
}
/**
* This method creates all the visual parts and configures them:
*
* <ol>
* <li>A {@link FlyoutPaletteComposite} is created and associated with the
* {@link Preferences} of the {@link #editor}'s {@link Plugin}.
* <li>With {@link #createGraphicalViewerComposite()} a composite hosting
* the {@link MultiViewerPageViewer}s is being created and set as graphical
* control for the {@link #flyoutPaletteComposite}.
* <li>The {@link ScrollableThumbnail} is being created with
* {@link #installThumbnailInPalette()}.
* <li>The first viewer in {@link #viewers} is set with
* {@link #setCurrentViewer(MuvitorPage.MultiViewerPageViewer)} .
* <li>The {@link #selectionProvider} intermediate is set as selection
* provider for this page's site.
* <li>This page starts listening on the global selection service for
* selection changes.
* <li>
* Actions are being created via {@link #createActions()}.
* </ol>
*
* @see org.eclipse.ui.part.Page#createControl(org.eclipse.swt.widgets.Composite)
*/
@Override
public final void createControl(final Composite parent) {
final Plugin plugin = MuvitorActivator.getDefault();
flyoutPaletteComposite = new FlyoutPaletteComposite(
parent,
SWT.NONE,
getSite().getPage(),
new PaletteViewerProvider(getEditDomain()),
FlyoutPaletteComposite.createFlyoutPreferences(plugin.getPluginPreferences()));
// This sets the state of the flyout palette to "pinned open"
plugin.getPluginPreferences().setValue("org.eclipse.gef.pstate", 4);
// create actions, this is done here because zoom actions need a
// viewer's zoom manager
createActions();
// add key strokes defined by concrete subclasses
setupKeyHandler(getSharedKeyHandler());
// create control that hosts graphical viewer
final Composite viewerControl = createGraphicalViewerComposite();
flyoutPaletteComposite.setGraphicalControl(viewerControl);
// install thumbnail
installThumbnailInPalette();
// set first viewer as default current viewer
setCurrentViewer(getViewers().get(0));
// set selection provider intermediate for the page's site
getSite().setSelectionProvider(selectionProvider);
// a listener to the global selection service
getSite().getPage().addSelectionListener(this);
}
/**
* Stops listening to the model, removes viewers from the {@link #editor}'s
* {@link SelectionSynchronizer}, stops listening to the global selection
* service, and deactivates the {@link #thumbnail}.
* <p>
* May be extended by subclasses to perform further cleaning up for
* initializations done in {@link #init(IPageSite)} but super implementation
* must be called!
*/
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.part.Page#dispose()
*/
@Override
public void dispose() {
getModel().eAdapters().remove(adapter);
getActionRegistry().dispose();
((CommandStack) getEditor().getAdapter(CommandStack.class)).removeCommandStackListener(this);
for (final GraphicalViewer viewer : getViewers()) {
if (getSelectionSynchronizer() != null)
getSelectionSynchronizer().removeViewer(viewer);
}
getSite().getPage().removeSelectionListener(this);
if (thumbnail != null) {
thumbnail.deactivate();
thumbnail = null;
}
super.dispose();
}
/**
* Extended to provide {@link #zoomManagerDelegate} as {@link ZoomManager}.
*
* Subclasses may extend further, but must call super implementation!
*/
@Override
public Object getAdapter(final Class clazz) {
if (clazz == ZoomManager.class) {
return zoomManagerDelegate;
} else if (clazz == IPropertySheetPage.class) {
return getEditor().getAdapter(clazz);
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.part.Page#getControl()
*/
@Override
public final Control getControl() {
return flyoutPaletteComposite;
}
/**
* @return The current viewer of this page
*
* @see #setCurrentViewer(MultiViewerPageViewer)
*/
public final MultiViewerPageViewer getCurrentViewer() {
return currentViewer;
}
/**
* @return the parent editor
*/
public final MuvitorTreeEditor getEditor() {
return (MuvitorTreeEditor) view.getEditor();
}
/**
* @return The model of this page (and its view)
*/
public final EObject getModel() {
return view.getModel();
}
/**
* @return The number of viewers that are hosted on this page.
*
* @see #getViewerContents()
* @see #createGraphicalViewerComposite()
*/
public final int getNumberOfViewers() {
return viewers.size();
}
/**
* This can be used to display messages in this page's status line.
*
* @return The {@link IStatusLineManager} from the {@link IActionBars} of
* this page's {@link IPageSite}
*/
public final IStatusLineManager getStatusLineManager() {
return getSite().getActionBars().getStatusLineManager();
}
/**
* This can be used to add actions to this page's tool bar.
*
* @return The {@link IToolBarManager} from the {@link IActionBars} of this
* page's {@link IPageSite}
*/
public final IToolBarManager getToolBarManager() {
return getSite().getActionBars().getToolBarManager();
}
/*
* (non-Javadoc)
*
* @see
* muvitorkit.animation.IGraphicalViewerProvider#getViewer(org.eclipse.emf
* .ecore.EObject)
*/
@Override
public final GraphicalViewer getViewer(final EObject forModel) {
for (final MultiViewerPageViewer viewer : viewers) {
if (viewer.getContents() != null
&& viewer.getContents().getModel() == forModel) {
return viewer;
}
}
return null;
}
/**
* A method to access the object which is being displayed in a specific
* viewer.
*
* @param viewerPosition
* the position (starting with 0) of the viewer
* @return the model of the top edit part of viewers[viewerPosition]
* @see #setViewersContents(int, EObject)
*/
public final EObject getViewersContents(final int viewerPosition) {
Assert.isTrue(viewerPosition < getNumberOfViewers(),
"AbstractMultiViewerPage tried to retrieve contents for viewer on position"
+ viewerPosition + " but has only "
+ getNumberOfViewers() + "viewers!");
return (EObject) getViewers().get(viewerPosition).getContents().getModel();
}
/**
* Registers this page as eAdapter listener on its model and as listener on
* the parent {@link MuvitorTreeEditor}'s {@link CommandStack}.
*
* <p>
* Subclasses may extendto perform further initializations butm ust call
* super implementation! Remember to clean them up in {@link #dispose()} if
* needed!.
*/
@Override
public void init(final IPageSite pageSite) {
super.init(pageSite);
getModel().eAdapters().add(adapter);
((CommandStack) getEditor().getAdapter(CommandStack.class)).addCommandStackListener(this);
}
/**
* If a selection change occurs in the one of the editor components
* (possibly in the current viewer) the {@link UpdateAction}s in this page's
* {@link ActionRegistry} are being updated.
*/
/*
* (non-Javadoc)
*
* @seeorg.eclipse.ui.ISelectionListener#selectionChanged(org.eclipse.ui.
* IWorkbenchPart, org.eclipse.jface.viewers.ISelection)
*/
@Override
public final void selectionChanged(final IWorkbenchPart part,
final ISelection selection) {
updateActions();
}
/**
* Sets focus to a part in the page.
*/
@Override
public final void setFocus() {
if (currentViewer != null) {
currentViewer.getControl().setFocus();
}
}
/**
* A method to set a new object to be shown in a specific viewer.
*
* @param viewerPosition
* the position (starting with 0) of the viewer
* @see #getViewerContents()
*/
public final void setViewersContents(final int viewerPosition,
final EObject model) {
Assert.isTrue(viewerPosition < getNumberOfViewers(),
"AbstractMultiViewerPage tried to set contents for viewer on position"
+ viewerPosition + " but has only "
+ getNumberOfViewers() + "viewers!");
getViewers().get(viewerPosition).setContents(model);
// hide empty viewers
if (model == null) {
setViewerVisibility(viewerPosition, false);
}
}
/**
* This method sets the visibility of a viewer's control and relayouts the
* SashForm so that the visible viewers on it occupy the remaining space.
*
* @param viewerPosition
* the position (starting with 0) of the viewer
*/
public final void setViewerVisibility(final int viewerPosition,
final boolean visible) {
Assert.isTrue(viewerPosition < getNumberOfViewers(),
"AbstractMultiViewerPage tried to switch visibility of viewer on position"
+ viewerPosition + " but has only "
+ getNumberOfViewers() + "viewers!");
final Control control = getViewers().get(viewerPosition).getControl();
final Control rulercomposite = control.getParent();
rulercomposite.setVisible(visible);
final SashForm sashForm = (SashForm) rulercomposite.getParent();
sashForm.layout(true);
}
/**
* Creates and registers some standard GEF actions and some generic Muvitor
* actions for this page's {@link ActionRegistry}. See the code below for
* details. Instances of universal actions like DeleteAction are shared with
* the {@link MuvitorTreeEditor}.
*
* <p>
* See the code below for generic and standard actions that are installed by
* default in Muvitors.
*
* @see #createCustomActions()
*/
private final void createActions() {
// create standard zoom actions for this page
registerActionAsHandler(new ZoomInAction(zoomManagerDelegate));
registerActionAsHandler(new ZoomOutAction(zoomManagerDelegate));
// register shared standard GEF actions from the editor
// save is treated specially and does not need this
registerSharedActionAsHandler(ActionFactory.REVERT.getId());
registerSharedActionAsHandler(ActionFactory.DELETE.getId());
registerSharedActionAsHandler(ActionFactory.UNDO.getId());
registerSharedActionAsHandler(ActionFactory.REDO.getId());
registerActionAsHandler(new SelectAllInMultiViewerAction(this));
// register shared actions from editor to put them in the local context
// menu
registerSharedAction(ExportViewerImageAction.ID);
registerSharedAction(TrimViewerAction.ID);
registerSharedAction(GEFActionConstants.TOGGLE_RULER_VISIBILITY);
registerSharedAction(GEFActionConstants.TOGGLE_GRID_VISIBILITY);
// register shared standard node-moving actions from the main editor for
// the local key handler
registerSharedAction(MoveNodeAction.LEFT);
registerSharedAction(MoveNodeAction.RIGHT);
registerSharedAction(MoveNodeAction.UP);
registerSharedAction(MoveNodeAction.DOWN);
registerSharedAction(MoveNodeAction.PREC_LEFT);
registerSharedAction(MoveNodeAction.PREC_RIGHT);
registerSharedAction(MoveNodeAction.PREC_UP);
registerSharedAction(MoveNodeAction.PREC_DOWN);
// register shared standard aligning actions from the main editor for
// the context menu
registerSharedAction(GEFActionConstants.ALIGN_LEFT);
registerSharedAction(GEFActionConstants.ALIGN_CENTER);
registerSharedAction(GEFActionConstants.ALIGN_RIGHT);
registerSharedAction(GEFActionConstants.ALIGN_TOP);
registerSharedAction(GEFActionConstants.ALIGN_MIDDLE);
registerSharedAction(GEFActionConstants.ALIGN_BOTTOM);
createCustomActions();
}
/**
* @return
*/
protected int[] getViewerSashWeights() {
return null;
}
/**
* This method creates an configures some {@link MultiViewerPageViewer}
* according to {@link #getNumberOfViewers()} and adds them to a
* {@link SashForm}:
*
* <p>
* For each newly created graphical viewer
* <ol>
* <li>the shared {@link #editDomain} is set,
* <li>a shared {@link KeyHandler} (configured via
* {@link #setupKeyHandler(KeyHandler)}) is set,
* <li>it is registered with the {@link #editor}'s
* {@link SelectionSynchronizer},
* <li>a shared {@link EditPartFactory} returned by
* {@link #createEditPartFactory()} is set,
* <li>a unique {@link ContextMenuProviderWithActionRegistry} returned by
* {@link #createContextMenuProvider(EditPartViewer)},
* <li>an object of the array {@link #getViewerContents()} returns is set as
* the contents of the viewers. This depends on the viewers' position.
* <li>The {@link ZoomManager} of the viewer is configured with some zoom
* levels.
* </ol>
* </p>
*
* @return The {@link SashForm} composite holding all the viewers.
*/
private final Composite createGraphicalViewerComposite() {
// create composite that hold the graphical viewers
final SashForm viewerComposite = new SashForm(flyoutPaletteComposite,
SWT.BORDER);
viewerComposite.setLayout(new FillLayout());
viewerComposite.setBackground(flyoutPaletteComposite.getDisplay().getSystemColor(
SWT.COLOR_LIST_BACKGROUND));
viewerComposite.setForeground(flyoutPaletteComposite.getDisplay().getSystemColor(
SWT.COLOR_LIST_FOREGROUND));
viewerComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
// prepare shared edit part factory
final EditPartFactory editPartFactory = createEditPartFactory();
// prepare contents to set
final EObject[] contents = getViewerContents();
final int contentsSize = contents.length;
// create viewers
for (int i = 0; i < contentsSize; i++) {
final MultiViewerPageViewer viewer = new MultiViewerPageViewer();
getEditDomain().addViewer(viewer);
final RulerComposite rulerComp = new RulerComposite(
viewerComposite, SWT.NONE);
// set the new viewer on the ruler composite
viewer.createControl(rulerComp);
viewers.add(viewer);
// create a GraphicalViewerKeyhandler with the shared keyhandler as
// parent
final KeyHandler graphicalViewerKeyHandler = new GraphicalViewerKeyHandler(
viewer);
graphicalViewerKeyHandler.setParent(getSharedKeyHandler());
viewer.setKeyHandler(graphicalViewerKeyHandler);
if (getSelectionSynchronizer() != null)
getSelectionSynchronizer().addViewer(viewer);
final ScalableFreeformRootEditPart rootEditPart = new ScalableFreeformRootEditPart();
viewer.setRootEditPart(rootEditPart);
viewer.setEditPartFactory(editPartFactory);
setViewersContents(i, contents[i]);
final ContextMenuProviderWithActionRegistry cmp = createContextMenuProvider(viewer);
cmp.setActionRegistry(getActionRegistry());
viewer.setContextMenu(cmp);
if (isContextMenuRegistered()) {
getEditor().getEditorSite().registerContextMenu(cmp,
getCurrentViewer());
}
// set antialias on connection layer, this may be turned off if
// causing trouble
((ConnectionLayer) rootEditPart.getLayer(LayerConstants.CONNECTION_LAYER)).setAntialias(SWT.ON);
// initialize viewer's ZoomManager
final ZoomManager zoomManager = (ZoomManager) viewer.getProperty(ZoomManager.class.toString());
final List<String> zoomLevels = new ArrayList<String>(3);
zoomLevels.add(ZoomManager.FIT_ALL);
zoomLevels.add(ZoomManager.FIT_WIDTH);
zoomLevels.add(ZoomManager.FIT_HEIGHT);
zoomManager.setZoomLevelContributions(zoomLevels);
zoomManager.setZoomAnimationStyle(ZoomManager.ANIMATE_ZOOM_IN_OUT);
// some optional coloring
viewer.getControl().setBackground(
PlatformUI.getWorkbench().getDisplay().getSystemColor(
SWT.COLOR_LIST_BACKGROUND));
viewer.getControl().setForeground(
PlatformUI.getWorkbench().getDisplay().getSystemColor(
SWT.COLOR_LIST_FOREGROUND));
// prepare viewer for showing rulers and grid
rulerComp.setGraphicalViewer(viewer);
viewer.setProperty(RulerProvider.PROPERTY_HORIZONTAL_RULER,
new MuvitorRulerProvider());
viewer.setProperty(RulerProvider.PROPERTY_VERTICAL_RULER,
new MuvitorRulerProvider());
viewer.setProperty(RulerProvider.PROPERTY_RULER_VISIBILITY,
Boolean.FALSE);
viewer.setProperty(SnapToGrid.PROPERTY_GRID_SPACING, new Dimension(
20, 20));
viewer.addDragSourceListener(new TemplateTransferDragSourceListener(
viewer) {
/*
* (non-Javadoc)
*
* @see org.eclipse.gef.dnd.TemplateTransferDragSourceListener#
* dragFinished(org.eclipse.swt.dnd.DragSourceEvent)
*/
@Override
public void dragFinished(DragSourceEvent event) {
// TODO Auto-generated method stub
super.dragFinished(event);
}
});
viewer.addDropTargetListener(new TemplateTransferDropTargetListener(
viewer) {
/*
* (non-Javadoc)
*
* @see org.eclipse.gef.dnd.TemplateTransferDropTargetListener#
* updateTargetRequest()
*/
@Override
protected void updateTargetRequest() {
// TODO Auto-generated method stub
super.updateTargetRequest();
}
});
}
if (getViewerSashWeights() != null) {
viewerComposite.setWeights(getViewerSashWeights());
}
customizeGraphicalViewerComposite(viewerComposite);
return viewerComposite;
}
/**
* @param viewComposite
*/
protected void customizeGraphicalViewerComposite(Composite viewComposite) {
}
/**
* @return A lazily created {@link EditDomain} with slightly modified
* selection behavior.
*/
private final EditDomain getEditDomain() {
if (null == editDomain) {
editDomain = new EditDomain();
editDomain.setPaletteRoot(createPaletteRoot());
final CommandStack commandStack = (CommandStack) getEditor().getAdapter(
CommandStack.class);
Assert.isNotNull(commandStack,
"The editor did not deliver a command stack instance for the page!");
editDomain.setCommandStack(commandStack);
}
return editDomain;
}
/**
* @return The {@link #editor}'s {@link SelectionSynchronizer}
*
* @see MuvitorTreeEditor#getSelectionSynchronizer()
*/
private final SelectionSynchronizer getSelectionSynchronizer() {
final SelectionSynchronizer synchronizer = (SelectionSynchronizer) getEditor().getAdapter(
SelectionSynchronizer.class);
return synchronizer;
}
/**
* Creates a {@link Thumbnail} on the {@link Control} of this page's
* {@link PaletteViewer}.
*
* @see #getThumbSashWeights()
*/
private final void installThumbnailInPalette() {
// get the FigureCanvas of the PaletteViewer
final FigureCanvas figureCanvas = (FigureCanvas) getEditDomain().getPaletteViewer().getControl();
// install a SashForm on the parent of the FigureCanvas
final SashForm sashForm = new SashForm(figureCanvas.getParent(),
SWT.VERTICAL);
// move the original figureCanvas onto the SashForm
figureCanvas.setParent(sashForm);
// create a new FigureCanvas and put a new Thumbnail on it
final FigureCanvas thumbCanvas = new FigureCanvas(sashForm);
thumbnail = new ScrollableThumbnail();
thumbnail.setBorder(new MarginBorder(3));
thumbCanvas.setContents(thumbnail);
// adjust the position of the thumbnail on the canvas
sashForm.setWeights(getThumbSashWeights());
}
/**
* Subclasses must implement to specify the
* {@link ContextMenuProviderWithActionRegistry} that should be used by this
* page's {@link GraphicalViewer}s and is responsible to show the created
* actions in the context menu.
*
* @param viewer
* The graphical viewer for context menu provider
* @return The {@link ContextMenuProviderWithActionRegistry} for the
* specified {@link GraphicalViewer}.
*
* @see #createGraphicalViewerComposite()
*/
abstract protected ContextMenuProviderWithActionRegistry createContextMenuProvider(
EditPartViewer viewer);
/**
* To be implemented by subclasses.
*
* <p>
* In his method, you can create IAction instances to be used in this page;
* in the viewers' context menu, by keys, or on the page's tool bar. For
* this, there are four methods that should cover all needs:
* {@link #registerAction(IAction)},
* {@link #registerActionAsHandler(IAction)},
* {@link #registerSharedAction(String)}, and
* {@link #registerSharedActionAsHandler(String)}. See their documentation
* for a description, when to use which method.
*
* <p>
* To put actions on the tool bar, you may add them to the tool bar manager
* (see {@link #getToolBarManager()}) after registering.
*
* <p>
* {@link WorkbenchPartAction}s and {@link SelectionAction}s created here
* should get the editor ({@link #getEditor()}) as workbench part in their
* constructor.
*
* <p>
* <b>Consider sharing actions instances with the main editor, if possible!
* </b>
*
* @see #createActions()
*/
abstract protected void createCustomActions();
/**
* Subclasses must implement to specify the {@link EditPartFactory} that
* should be used by this page's {@link GraphicalViewer}s.
*
* @return The {@link EditPartFactory} for the {@link GraphicalViewer}s of
* this page.
*
* @see #createGraphicalViewerComposite()
*/
abstract protected EditPartFactory createEditPartFactory();
/**
* Subclasses must implement to specify the PaletteRoot that should be used
* by this page's {@link EditDomain} and which defines the contents of the
* palette.
* <p>
* Use a {@link MuvitorPaletteRoot} for convenience.
*
* @return The {@link MuvitorPaletteRoot} for the {@link EditDomain} of this
* page.
*/
abstract protected MuvitorPaletteRoot createPaletteRoot();
/**
* @return the actionRegistry
*/
protected final ActionRegistry getActionRegistry() {
return actionRegistry;
}
/**
* Configures the {@link #keyHandler} with standard key strokes like DEL
* (for {@link DeleteAction}) and F2 (for direct edit).
*
* @return A lazily created shared {@link KeyHandler} for the viewers on
* this page.
*
* @see #setupKeyHandler(KeyHandler)
*/
protected final KeyHandler getSharedKeyHandler() {
if (keyHandler == null) {
keyHandler = new KeyHandler();
// this may be null
keyHandler.setParent((KeyHandler) getEditor().getAdapter(
KeyHandler.class));
}
return keyHandler;
}
/**
* The two values in the returned array specify how the fly-out composite's
* space is being divided between the palette entries and the thumbnail in
* the palette. When implementing, this template should be used: <br>
* <code> return new int[] { Palette_Weight, Thumb_Weight }; </code> The
* pair (3,1) is a good default value, but you may override this method.
*
* @return An array of 2 integers describing the ratio of the palette to the
* thumbnail.
*/
protected int[] getThumbSashWeights() {
return new int[] { 3, 1 };
}
/**
* The viewer's are arranged horizontally and will be showing these objects
* in the order of the returned array from left to right. The length of the
* array determines the value of {@link #getNumberOfViewers()}!
*
* @return The objects that will be set as contents in each of
* {@link #viewers}.
*
* @see #setViewersContents(int, EObject)
* @see #createGraphicalViewerComposite()
*/
abstract protected EObject[] getViewerContents();
/**
* Method to get the list of viewers if needed to implement advanced editor
* features. This list is unmodifiable.
*
* <p>
* To set new contents for a viewer
* {@link #setViewersContents(int, EObject)} should be used!
* <p>
*
* @return the viewers
* @see #setViewersContents(int, EObject)
*/
protected final List<MultiViewerPageViewer> getViewers() {
return Collections.unmodifiableList(viewers);
}
/**
* This method returns <code>false</code> by default. If the context menu
* should be registered with the editor so it can be customized by the
* plugin.xml override this method and return <code>true</code>.
*
* @return <code>false</code>
*/
protected boolean isContextMenuRegistered() {
return false;
}
/**
* By default, an Adapter will be registered with this page's model that
* passes notifications to this method, which subclasses are expected to
* override.
*/
protected void notifyChanged(final Notification msg) {
}
/**
* Registers an {@link IAction} instance with this page's
* {@link ActionRegistry}.
*
* <p>
* Call this method in {@link #createCustomActions()} if you want to use an
* action instance in the context menu or put it on the page's tool bar.
*
* <p>
* <b>Do not use this method if you</b>
* <ul>
* <li>want to register a handle action instance for a
* {@link RetargetAction} defined in the {@link MuvitorActionBarContributor}
* (use {@link #registerActionAsHandler(IAction)} instead), or
* <li>if you want to or could reuse an action instance from the parent
* editor (use {@link #registerSharedAction(String)} instead), or
* <li>both together (like this page does already e.g. with the
* {@link DeleteAction}) (use {@link #registerSharedActionAsHandler(String)}
* instead).
* </ul>
*
* @param action
* the action to be registered
* @return the action for convenience
*
* @see #createCustomActions()
* @see #createActions()
* @see #getToolBarManager()
*/
protected final IAction registerAction(final IAction action) {
getActionRegistry().registerAction(action);
return action;
}
/**
* Registers an {@link IAction} instance with this page's
* {@link ActionRegistry} and sets it as the handler action (in this page)
* for a {@link RetargetAction} that has been defined in the
* {@link MuvitorActionBarContributor} with the same ID as the action.
*
* <p>
* Call this method in {@link #createCustomActions()} if you want to use an
* action instance (as a handler for a RetargetAction) in the context menu
* or put it on the page's tool bar.
*
* <p>
* <b>Do not use this method if you</b>
* <ul>
* <li>just want to register a regular action not being a handle (use
* {@link #registerAction(IAction)} instead), or
* <li>if you want to or could reuse an action instance from the parent
* editor (use {@link #registerSharedAction(String)} instead), or
* <li>reuse a handler from the editor (like this page does already with the
* {@link DeleteAction}) (use {@link #registerSharedActionAsHandler(String)}
* instead).
* </ul>
*
* <em>Technical remark: Registering handles has to be done only once since
* {@link PageBookView#partActivated(org.eclipse.ui.IWorkbenchPart)} takes
* care about refreshing the global action handlers for the ViewSite with
* the global action handlers defined for the {@link SubActionBars} of this
* {@link IPageSite}. </em>
*
* @param action
* the action to be registered
* @return the action for convenience
*
* @see #createCustomActions()
* @see #createActions()
* @see #getToolBarManager()
*/
protected final IAction registerActionAsHandler(final IAction action) {
final String id = action.getId();
getSite().getActionBars().setGlobalActionHandler(id,
registerAction(action));
return action;
}
/**
* Registers an {@link IAction} instance from the parent
* {@link MuvitorTreeEditor}'s {@link ActionRegistry} with this page's
* action registry.
*
* <p>
* Call this method in {@link #createCustomActions()} if you want to reuse
* an action instance from the editor in the context menu or put it on the
* page's tool bar. Considering this for most Actions is strongly advised!
*
* <p>
* <b>Do not use this method if you</b>
* <ul>
* <li>just want to register a new action instance not being shared with the
* parent editor (use {@link #registerAction(IAction)} instead), or
* <li>want to register a handle action instance for a
* {@link RetargetAction} defined in the {@link MuvitorActionBarContributor}
* (use {@link #registerActionAsHandler(IAction)} instead), or
* <li>if you want to reuse a handler from the editor (like this page does
* already with the {@link DeleteAction}) (use
* {@link #registerSharedActionAsHandler(String)} instead).
* </ul>
*
* @param id
* the ID of the action to be retrieved from the parent editor's
* action registry
* @return the action from the parent editor for convenience
*
* @see #createCustomActions()
* @see #createActions()
* @see #getToolBarManager()
*/
protected final IAction registerSharedAction(final String id) {
final ActionRegistry editorRegistry = (ActionRegistry) getEditor().getAdapter(
ActionRegistry.class);
Assert.isNotNull(editorRegistry,
"The editor did not deliver an ActionRegistry instance for the page!");
final IAction action = editorRegistry.getAction(id);
Assert.isNotNull(action,
"The editor did not deliver an action with the id '" + id
+ "' for the page!");
return registerAction(action);
}
/**
* Registers an {@link IAction} instance from the parent editor's
* {@link ActionRegistry} with this page's action registry and sets it as
* the handler action (in this page) for a {@link RetargetAction} that has
* been defined in the {@link MuvitorActionBarContributor} with the same ID
* as the action.
*
* <p>
* Call this method in {@link #createCustomActions()} if you want to reuse
* an action handler for a RetargetAction from the editor in the context
* menu or put it on the page's tool bar. Considering this for most handle
* Actions is strongly advised!
*
* <p>
* <b>Do not use this method if you</b>
* <ul>
* <li>just want to register a regular action not being a handle (use
* {@link #registerAction(IAction)} instead), or
* <li>if you want to register a new action handler not being shared with
* the parent editor (use {@link #registerActionAsHandler(IAction)}
* instead), or
* <li>if you want to or could reuse an action instance from the parent
* editor (use {@link #registerSharedAction(String)} instead).
* </ul>
*
* <em>Technical remark: Registering handles has to be done only once since
* {@link PageBookView#partActivated(org.eclipse.ui.IWorkbenchPart)} takes
* care about refreshing the global action handlers for the ViewSite with
* the global action handlers defined for the {@link SubActionBars} of this
* {@link IPageSite}. </em>
*
* @param id
* the ID of the action to be retrieved from the parent editor's
* action registry
* @return the action from the parent editor for convenience
*
* @see #createCustomActions()
* @see #createActions()
* @see #getToolBarManager()
*/
protected final IAction registerSharedActionAsHandler(final String id) {
final IAction editorAction = registerSharedAction(id);
getSite().getActionBars().setGlobalActionHandler(id, editorAction);
return editorAction;
}
/**
* This method sets the current graphical viewer of this page which implies
* that the passed viewer
*
* <ul>
* <li>will be shown in the {@link Thumbnail},
* <li>becomes the current {@link ISelectionProvider} in the
* {@link SelectionProviderIntermediate} for this page's {@link IPageSite}
* and
* <li>provides the {@link ZoomManager} for the zoom actions the
* {@link ZoomManagerDelegate} delegates to.
* </ul>
*
* @param viewer
*/
protected final void setCurrentViewer(final MultiViewerPageViewer viewer) {
Assert.isNotNull(viewer,
"AbstractMultiViewerPage can not be set to viewer 'null'!");
// update current viewer only if the passed is different to it
if (currentViewer == viewer) {
// this may happen if we switch from the tree viewer to this viewer,
// so we have to update the actions
updateActions();
return;
}
currentViewer = viewer;
// if (getSite().getPage().getActiveEditor() == getEditor()) {
currentViewer.getControl().setFocus();
// }
// set current viewer as actual selection provider for this page's site
selectionProvider.setSelectionProviderDelegate(currentViewer);
updateActions();
// update current viewer as source of Thumbnail
final ScalableFreeformRootEditPart rootEditPart = (ScalableFreeformRootEditPart) currentViewer.getRootEditPart();
thumbnail.setViewport((Viewport) rootEditPart.getFigure());
thumbnail.setSource(rootEditPart.getLayer(LayerConstants.PRINTABLE_LAYERS));
thumbnail.setVisible(true);
// update current viewer's ZoomManager for ZoomManagerDelegate
zoomManagerDelegate.setCurrentZoomManager((ZoomManager) currentViewer.getProperty(ZoomManager.class.toString()));
}
/**
* Subclasses must implement to associate {@link KeyStroke}s with Actions in
* the passed {@link KeyHandler} which is the one used for the
* {@link GraphicalViewers}.
* <p>
* May be left empty.
*
* @see #createControl(Composite)
*/
abstract protected void setupKeyHandler(KeyHandler kh);
/**
* Updates all {@link UpdateAction}s registered in the
* {@link #actionRegistry}.
*/
protected final void updateActions() {
for (@SuppressWarnings("unchecked")
final Iterator<IAction> iter = getActionRegistry().getActions(); iter.hasNext();) {
final IAction action = iter.next();
if (action instanceof UpdateAction) {
((UpdateAction) action).update();
}
}
}
}