Customizing Layouts

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.

Creating Layouts for RMP UML Modeler Diagrams

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:

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.

Triggering a Layout Request

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 ArrangeRequests 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);
    }
}

Legal notices