/******************************************************************************* * Copyright (c) 2014 Arapiki Solutions Inc. * 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: * psmith - initial API and * implementation and/or initial documentation *******************************************************************************/ package com.buildml.eclipse.packages.patterns; import org.eclipse.graphiti.features.context.IAddContext; import org.eclipse.graphiti.features.context.ICreateContext; import org.eclipse.graphiti.features.context.IDeleteContext; import org.eclipse.graphiti.features.context.IMoveShapeContext; import org.eclipse.graphiti.features.context.IResizeShapeContext; import org.eclipse.graphiti.mm.algorithms.Polygon; import org.eclipse.graphiti.mm.algorithms.Rectangle; import org.eclipse.graphiti.mm.algorithms.Text; import org.eclipse.graphiti.mm.algorithms.styles.Orientation; import org.eclipse.graphiti.mm.pictograms.ContainerShape; import org.eclipse.graphiti.mm.pictograms.Diagram; import org.eclipse.graphiti.mm.pictograms.PictogramElement; import org.eclipse.graphiti.mm.pictograms.PictogramLink; import org.eclipse.graphiti.pattern.AbstractPattern; import org.eclipse.graphiti.pattern.IPattern; import org.eclipse.graphiti.services.Graphiti; import org.eclipse.graphiti.services.IGaService; import org.eclipse.graphiti.services.IPeCreateService; import org.eclipse.graphiti.util.ColorConstant; import org.eclipse.graphiti.util.IColorConstant; import com.buildml.eclipse.bobj.UIPackage; import com.buildml.eclipse.bobj.UISubPackage; import com.buildml.eclipse.packages.PackageDiagramEditor; import com.buildml.eclipse.packages.layout.PictogramSize; import com.buildml.eclipse.utils.AlertDialog; import com.buildml.eclipse.utils.GraphitiUtils; import com.buildml.eclipse.utils.UndoOpAdapter; import com.buildml.eclipse.utils.errors.FatalError; import com.buildml.model.IBuildStore; import com.buildml.model.IPackageMemberMgr; import com.buildml.model.IPackageMgr; import com.buildml.model.ISubPackageMgr; import com.buildml.model.IPackageMemberMgr.MemberLocation; import com.buildml.model.undo.SubPackageUndoOp; import com.buildml.utils.errors.ErrorCode; /** * A Graphiti pattern for managing the "SubPackage" graphical element in a BuildML diagram. * * @author Peter Smith <psmith@arapiki.com> */ public class SubPackagePattern extends AbstractPattern implements IPattern { /*=====================================================================================* * FIELDS/TYPES *=====================================================================================*/ /** The IBuildStore that this diagram represents */ private IBuildStore buildStore; /** The managers owned by this BuildStore */ private IPackageMemberMgr pkgMemberMgr; private ISubPackageMgr subPkgMgr; private IPackageMgr pkgMgr; /* * Various colour constants used in displaying this element. */ private static final IColorConstant TEXT_FOREGROUND = IColorConstant.BLACK; private static final IColorConstant LINE_COLOUR = new ColorConstant(150, 186, 150); private static final IColorConstant FILL_COLOUR = new ColorConstant(200, 255, 200); /* * Size of this element (in pixels). */ private static final int SUB_PACKAGE_WIDTH = 80; private static final int SUB_PACKAGE_HEIGHT = 60; private static final int SUB_PACKAGE_TAB_WIDTH = 20; private static final int SUB_PACKAGE_TAB_HEIGHT = 12; /** Font type for labels */ private static final String LABEL_FONT = "courier"; /** Font size */ private static final int LABEL_FONT_SIZE = 9; /** * The geometric coordinates for drawing a sub-package pictogram. */ int coords[] = new int[] { 0, 0, SUB_PACKAGE_TAB_WIDTH, 0, SUB_PACKAGE_TAB_WIDTH, SUB_PACKAGE_TAB_HEIGHT, SUB_PACKAGE_WIDTH - 1, SUB_PACKAGE_TAB_HEIGHT, SUB_PACKAGE_WIDTH - 1, SUB_PACKAGE_HEIGHT - 1, 0, SUB_PACKAGE_HEIGHT - 1 }; /** The (static) maximum size of a file group pictogram, in pixels */ private static PictogramSize SUB_PACKAGE_MAX_SIZE = new PictogramSize(SUB_PACKAGE_WIDTH, SUB_PACKAGE_HEIGHT); /*=====================================================================================* * STATIC METHODS *=====================================================================================*/ /** * Return the (width, height) in pixel of the sub-package pictogram. This is used * for laying-out the package members. * @return The (width, height) in pixels. */ public static PictogramSize getSize() { return SUB_PACKAGE_MAX_SIZE; } /*=====================================================================================* * CONSTRUCTORS *=====================================================================================*/ /** * Create a new {@link SubPackagePattern} object. */ public SubPackagePattern() { super(null); } /*=====================================================================================* * PUBLIC METHODS *=====================================================================================*/ /** * Return the name of this element, as will appears in the Diagram's palette. */ @Override public String getCreateName() { return "Sub Package"; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see org.eclipse.graphiti.pattern.AbstractPattern#isMainBusinessObjectApplicable(java.lang.Object) */ @Override public boolean isMainBusinessObjectApplicable(Object mainBusinessObject) { return (mainBusinessObject instanceof UIPackage) || (mainBusinessObject instanceof UISubPackage); } /*-------------------------------------------------------------------------------------*/ /** * Determine whether a specific business object can be added to the diagram. */ @Override public boolean canAdd(IAddContext context) { Object newObject = context.getNewObject(); return (newObject instanceof UIPackage) || (newObject instanceof UISubPackage); } /*-------------------------------------------------------------------------------------*/ /** * Create the visual representation of a UIFileGroup on the parent Diagram. */ @Override public PictogramElement add(IAddContext context) { PackageDiagramEditor editor = (PackageDiagramEditor)(getDiagramBehavior().getDiagramContainer()); buildStore = editor.getBuildStore(); pkgMemberMgr = buildStore.getPackageMemberMgr(); pkgMgr = buildStore.getPackageMgr(); subPkgMgr = buildStore.getSubPackageMgr(); /* * Case handled: * 1) UIPackage dropped on a Diagram - we create a new SubPackage within this package. * 2) UISubPackage - just display it on the diagram. It already exists in the database. */ Object addedObject = context.getNewObject(); Object targetObject = context.getTargetContainer(); if (!(targetObject instanceof Diagram)) { return null; } int x = context.getX(); int y = context.getY(); /* * Case #1 - add/drag an existing UIPackage onto the Diagram. We must add * a new sub-package onto this diagram (i.e. stored in the database). */ if ((addedObject instanceof UIPackage) && (targetObject instanceof Diagram)) { UIPackage uiPkgType = (UIPackage)addedObject; int pkgTypeId = uiPkgType.getId(); int subPkgId = subPkgMgr.newSubPackage(editor.getPackageId(), pkgTypeId); if (subPkgId < 0) { /* can we create a package of the requested type? */ if (subPkgId == ErrorCode.NOT_FOUND) { AlertDialog.displayErrorDialog("Can't Add Sub-Package", "You may not add a sub-package of this type."); } /* cycle detection - would a loop be created? */ else if (subPkgId == ErrorCode.LOOP_DETECTED) { AlertDialog.displayErrorDialog("Can't Add Sub-Package", "You may not add a sub-package of this type, otherwise a loop will be created " + "in the package hierarchy."); } /* all other errors */ else { AlertDialog.displayErrorDialog("Can't Add Sub-Package", "An unexpected error occurred when attempting to add a new sub-package."); } return null; } /* set the x and y coordinates correctly */ pkgMemberMgr.setMemberLocation(IPackageMemberMgr.TYPE_SUB_PACKAGE, subPkgId, x, y); /* create an undo/redo operation to track this change */ SubPackageUndoOp undoOp = new SubPackageUndoOp(buildStore, subPkgId); undoOp.recordNewSubPackage(); new UndoOpAdapter("New Sub-Package", undoOp).record(); return null; } else if (addedObject instanceof UISubPackage) { return renderPictogram((Diagram)targetObject, (UISubPackage)addedObject, x, y); } /* other cases are not handled */ return null; } /*-------------------------------------------------------------------------------------*/ /** * We can't add FileGroups from the palette. */ @Override public boolean canCreate(ICreateContext context) { return false; } /*-------------------------------------------------------------------------------------*/ /** * The user isn't allowed to resize the object. */ @Override public boolean canResizeShape(IResizeShapeContext context) { return false; } /*=====================================================================================* * PROTECTED METHODS *=====================================================================================*/ /* * (non-Javadoc) * @see org.eclipse.graphiti.pattern.AbstractPattern#isPatternControlled( * org.eclipse.graphiti.mm.pictograms.PictogramElement) */ @Override protected boolean isPatternControlled(PictogramElement pictogramElement) { Object domainObject = getBusinessObjectForPictogramElement(pictogramElement); return isMainBusinessObjectApplicable(domainObject); } /*-------------------------------------------------------------------------------------*/ /* * (non-Javadoc) * @see org.eclipse.graphiti.pattern.AbstractPattern#isPatternRoot( * org.eclipse.graphiti.mm.pictograms.PictogramElement) */ @Override protected boolean isPatternRoot(PictogramElement pictogramElement) { Object domainObject = getBusinessObjectForPictogramElement(pictogramElement); return isMainBusinessObjectApplicable(domainObject); } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see org.eclipse.graphiti.pattern.AbstractPattern#canMoveShape(org.eclipse.graphiti.features.context.IMoveShapeContext) */ @Override public boolean canMoveShape(IMoveShapeContext context) { return context.getTargetContainer() instanceof Diagram; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see org.eclipse.graphiti.pattern.AbstractPattern#moveShape(org.eclipse.graphiti.features.context.IMoveShapeContext) */ @Override public void moveShape(IMoveShapeContext context) { super.moveShape(context); /* * Fetch the x, y and subPkgId. Note that all error checking was done by canMoveShape(). */ int x = context.getX(); int y = context.getY(); UISubPackage subPkg = (UISubPackage)GraphitiUtils.getBusinessObject(context.getPictogramElement()); int subPkgId = subPkg.getId(); /* * Are we moving a UISubPackage around the Diagram? */ Object targetObject = context.getTargetContainer(); if (targetObject instanceof Diagram) { /* determine the UISubPackage's old location */ MemberLocation oldXY = pkgMemberMgr.getMemberLocation(IPackageMemberMgr.TYPE_SUB_PACKAGE, subPkgId); if (oldXY == null){ /* default, in the case of an error */ oldXY = new MemberLocation(); oldXY.x = 0; oldXY.y = 0; } /* create an undo/redo operation that will invoke the underlying database changes */ SubPackageUndoOp op = new SubPackageUndoOp(buildStore, subPkgId); op.recordLocationChange(oldXY.x, oldXY.y, x, y); new UndoOpAdapter("Move Sub-Package", op).invoke(); } } /*-------------------------------------------------------------------------------------*/ /** * Yes, we can delete UISubPackage members. */ @Override public boolean canDelete(IDeleteContext context) { return true; } /*-------------------------------------------------------------------------------------*/ /** * Invoked when the user initiates a "delete" operation on a UISubPackage. */ @Override public void delete(IDeleteContext context) { /* determine the business object that related to the pictogram being deleted */ PictogramLink pl = context.getPictogramElement().getLink(); UISubPackage subPkg = (UISubPackage)(pl.getBusinessObjects().get(0)); int subPkgId = subPkg.getId(); SubPackageUndoOp op = new SubPackageUndoOp(buildStore, subPkgId); op.recordRemoveSubPackage(); /* invoke all changes in one step... */ new UndoOpAdapter("Delete Sub-Package", op).invoke(); } /*=====================================================================================* * PRIVATE METHODS *=====================================================================================*/ /** * Render a pictogram, representing a UISubPackage, onto a Graphiti Diagram. * * @param targetDiagram The Diagram to add the pictogram to. * @param subPkg The UIPackage to show a pictogram for. * @param x The X location within the Diagram. * @param y The Y location within the Diagram. * @return The ContainerShape representing the UISubPackage pictogram. */ private PictogramElement renderPictogram(Diagram targetDiagram, UISubPackage subPkg, int x, int y) { /* create a container that holds the pictogram */ IPeCreateService peCreateService = Graphiti.getPeCreateService(); IGaService gaService = Graphiti.getGaService(); ContainerShape containerShape = peCreateService.createContainerShape(targetDiagram, true); /* * Create an invisible outer rectangle. The smaller polygons and labels will be placed * inside this. The width is always twice that of the polygon, to allow for long file * names to be shown underneath the polygons. The height is carefully selected to allow * for the label underneath. */ Rectangle invisibleRectangle = gaService.createInvisibleRectangle(containerShape); gaService.setLocationAndSize(invisibleRectangle, x, y, SUB_PACKAGE_WIDTH * 2, SUB_PACKAGE_HEIGHT + (3 * LABEL_FONT_SIZE)); /* Now the package box - centered within the invisible rectangle */ Polygon box = gaService.createPolygon(invisibleRectangle, coords); box.setForeground(manageColor(LINE_COLOUR)); box.setBackground(manageColor(FILL_COLOUR)); box.setLineWidth(2); gaService.setLocation(box, SUB_PACKAGE_WIDTH / 2, 0); /* Display the type of the package in the center of the box */ int pkgTypeId = subPkgMgr.getSubPackageType(subPkg.getId()); if (pkgTypeId < 0) { throw new FatalError("Invalid package ID"); } String pkgTypeName = pkgMgr.getName(pkgTypeId); if (pkgTypeName != null) { Text pkgType = gaService.createText(getDiagram(), invisibleRectangle, pkgTypeName, LABEL_FONT, LABEL_FONT_SIZE); pkgType.setFilled(false); pkgType.setForeground(manageColor(TEXT_FOREGROUND)); pkgType.setHorizontalAlignment(Orientation.ALIGNMENT_CENTER); gaService.setLocationAndSize(pkgType, 0, SUB_PACKAGE_HEIGHT + LABEL_FONT_SIZE, SUB_PACKAGE_WIDTH * 2, LABEL_FONT_SIZE * 2); } /* create a link between the shape and the business object, and display it. */ link(containerShape, subPkg); layoutPictogramElement(containerShape); return containerShape; } /*-------------------------------------------------------------------------------------*/ }