Layouts are used to arrange shapes on diagrams or within shape compartments. A layout can be triggered for the current selection, or for all children within a container.
The layout service allows for the arrangement of diagram elements for a given layout type.
To contribute a layout provider to the service extend the
org.eclipse.gmf.runtime.diagram.ui.layoutProviders
extension-point and set
the layoutProvider class
to be a class which extends
org.eclipse.gmf.runtime.diagram.ui.services.layout.AbstractLayoutEditPartProvider
.
<extension point="org.eclipse.gmf.runtime.diagram.ui.layoutProviders"> <layoutProvider class="com.ibm.examples.providers.MyLayoutProvider"> <Priority name="Medium"/> </layoutProvider> </extension>
GMF provides a set of layout classes for the layout provider to extend:
AbstractLayoutNodeProvider
LeftRightProvider
TopDownProvider
CompositeLeftRightProvider
CompositeTopDownProvider
In this example the MyLayoutProvider
class extends
AbstractLayoutNodeProvider
in order to provide a customized layout algorithm.
MyLayoutProvider
implements the provides
method such that
it provides for only the layout type myLayoutType
. To override the default layout behaviour,
the provider should provide for the
LayoutType.DEFAULT
layout type from GMF.
Override the
layoutLayoutNodes
method and return a Runnable
which will execute
the custom layout algorithm on the given list of nodes. In this example, the layout algorithm
will layout the nodes in a pyramid pattern.
public class MyLayoutProvider extends AbstractLayoutNodeProvider { public static final String LAYOUT_TYPE = "myLayoutType"; public boolean provides(IOperation operation) { View cview = getContainer(operation); if (cview == null) return false; IAdaptable layoutHint = ((ILayoutNodeOperation) operation).getLayoutHint(); String layoutType = (String) layoutHint.getAdapter(String.class); return LAYOUT_TYPE.equals(layoutType); } /** * Layout nodes in a pyramid shape * o * o o * o o o * Last row may not be completely filled, depending on number * of nodes to arrange. */ public Runnable layoutLayoutNodes(final List layoutNodes, final boolean offsetFromBoundingBox, final IAdaptable layoutHint) { return new Runnable() { public void run() { // calculate the grid size int gridWidth = 0; int gridHeight = 0; ListIterator li = layoutNodes.listIterator(); while (li.hasNext()) { ILayoutNode lnode = (ILayoutNode)li.next(); if (lnode.getWidth() > gridWidth) gridWidth = lnode.getWidth(); if (lnode.getHeight() > gridHeight) gridHeight = lnode.getHeight(); } // add a small buffer in HiMetric units gridWidth += 100; gridHeight += 100; // determine number of rows int rowsize = (int)Math.floor(Math.sqrt(layoutNodes.size() * 2)); int boxXOffset = 1000; int boxYOffset = 1000; // set node bounds li = layoutNodes.listIterator(); for (int i = 1; i <= rowsize; ++i) { int xoffset = (rowsize - i) * gridWidth + boxYOffset; int yoffset = (i - 1) * gridHeight + boxXOffset; for (int j = 1; j <= i && li.hasNext(); ++j, xoffset += (gridWidth * 2)) { ILayoutNode lnode = (ILayoutNode)li.next(); Bounds bounds = (Bounds)lnode.getNode().getLayoutConstraint(); bounds.setX(xoffset); bounds.setY(yoffset); lnode.getNode().setLayoutConstraint(bounds); } } } }; } }
The LayoutService
can now be queried for this custom layout provider to layout a list of nodes,
or the children of a container.
Instead of querying the
LayoutService
directly,
the ArrangeRequest
can be used to obtain a command to arrange a set of
EditPart
objects. To obtain the layout command construct an
ArrangeRequest
and query the
EditPart#getCommand(Request request)
method on the container edit part.
The ContainerEditPolicy
understands the arrange request and will return a layout command.
This example will make use of a menu bar action to invoke laying out the nodes within a container.
Start by defining the
org.eclipse.ui.actionSets
extension to create a new menu action.
<extension point="org.eclipse.ui.actionSets"> <actionSet id="com.ibm.example.actionSet" label="My Layout Action Set" visible="true"> <menu label="My Layout" id="myLayoutMenu"> </menu> <action label="Run My Layout" tooltip="Run My Layout" class="com.ibm.examples.actions.MyLayoutActionDelegate" menubarPath="myLayoutMenu/additions" id="com.ibm.examples.actions.MyLayoutActionDelegate"> </action> </actionSet> </extension>
The ContainerEditPolicy
understands
ArrangeRequest
s of type
ActionId.ACTION_ARRANGE_ALL
and
ActionId.ACTION_ARRANGE_SELECTION
.
ActionId.ACTION_ARRANGE_ALL
is used to layout all children within a container.
This example action delegate demonstrates how to construct the arrange request and query
the container edit part for the layout command.
Calculation for enablement of this action is done by ensuring that the parent of the first selected
object is either the diagram or a shape compartment, whose layout manager is an instance
of
XYLayout
because the
XYLayout
allows redefining the layout based on X / Y coordinates.
Construct the ArrangeRequest
with the
ActionId.ACTION_ARRANGE_ALL
request type and the layout type of choice
(this example will invoke the layout created
above). Then query the getCommand
method on the parent
diagram or shape compartment edit part.
A neat visual enhancement is to utilize the
Animation
class to animate the layout. Before executing the layout command, call
Animation.markBegin()
to mark the beginning of the animation process. Then after
executing the layout command run the animation by calling
Animation.run()
or optionally
Animation.run(int duration)
.
public class MyLayoutActionDelegate extends AbstractActionDelegate implements IWorkbenchWindowActionDelegate { public static final String LAYOUT_TYPE = "myLayoutType"; protected void doRun(IProgressMonitor progressMonitor) { EditPart parent = getParentEditPart(); ArrangeRequest request = new ArrangeRequest(ActionIds.ACTION_ARRANGE_ALL, LAYOUT_TYPE); Command command = parent.getCommand(request); Animation.markBegin(); command.execute(); Animation.run(500); } public void selectionChanged(IAction act, ISelection selection) { super.selectionChanged(act, selection); // calculate enabled if (getStructuredSelection().isEmpty()) { getAction().setEnabled(false); return; } EditPart parent = getParentEditPart(); if (parent == null || !(parent.getContentPane().getLayoutManager() instanceof XYLayout)) { getAction().setEnabled(false); } getAction().setEnabled(true); } protected EditPart getParentEditPart() { EditPart parent = (EditPart)((IAdaptable)getStructuredSelection(). getFirstElement()).getAdapter(EditPart.class); while (parent != null && !(parent instanceof DiagramEditPart) && !(parent instanceof ShapeCompartmentEditPart)) { parent = parent.getParent(); } return parent; } }
ActionId.ACTION_ARRANGE_SELECTION
is used to layout a subset of children within a container.
This example action delegate demonstrates how to construct the arrange request and query
the container edit part for the layout command.
Calculation for enablement of this action is done by ensuring that at least 2 or more
non-connection edit parts are selected, and that these selected elements share a common parent
whose layout manager is an instance
of
XYLayout
.
ToolUtilities.getSelectionWithoutDependants(List selectedParts)
is utilized to
obtain a list containing the top level selected edit parts based on the passed in selection list.
Construct the
ArrangeRequest
with the
ActionId.ACTION_ARRANGE_SELECTION
request type and the layout type of choice
(this example will invoke the layout created
above) and set the list of edit parts to arrange in the request using
ArrangeRequest#setPartsToArrange(List ep)
.
Then query the getCommand
method on the common parent edit part.
public class MyLayoutActionDelegate extends AbstractActionDelegate implements IWorkbenchWindowActionDelegate { public static final String LAYOUT_TYPE = "myLayoutType"; protected void doRun(IProgressMonitor progressMonitor) { List editParts = new ArrayList(); List selection = ToolUtilities.getSelectionWithoutDependants( getStructuredSelection().toList()); for (Iterator i = selection.iterator(); i.hasNext(); ) { Object next = i.next(); if (!(next instanceof ConnectionEditPart) && next instanceof EditPart) { editParts.add(next); } } ArrangeRequest request = new ArrangeRequest(ActionIds.ACTION_ARRANGE_SELECTION, LAYOUT_TYPE); request.setPartsToArrange(editParts); Command command = ((EditPart)editParts.get(0)).getParent().getCommand(request); Animation.markBegin(); command.execute(); Animation.run(500); } public void selectionChanged(IAction act, ISelection selection) { super.selectionChanged(act, selection); // calculate enabled if (getStructuredSelection().isEmpty()) { getAction().setEnabled(false); return; } List newSelection = ToolUtilities.getSelectionWithoutDependants( getStructuredSelection().toList()); if (newSelection.size() < 2) { getAction().setEnabled(false); return; } List editParts = new ArrayList(); EditPart parent = null; for (Iterator i = newSelection.iterator(); i.hasNext(); ) { Object next = i.next(); if (!(next instanceof ConnectionEditPart) && next instanceof EditPart) { if (parent == null) { parent = ((EditPart)next).getParent(); if (!(parent instanceof IGraphicalEditPart) || !(((IGraphicalEditPart)parent).getContentPane().getLayoutManager() instanceof XYLayout)) { getAction().setEnabled(false); return; } } else if (parent != ((EditPart)next).getParent()) { getAction().setEnabled(false); return; } editParts.add(next); } } getAction().setEnabled(editParts.size() > 1); } }