ModeCreateMessage.java

/* $Id$
 *****************************************************************************
 * Copyright (c) 2009-2012 Contributors - see below
 * 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:
 *    bobtarling
 *****************************************************************************
 *
 * Some portions of this file was previously release using the BSD License:
 */

// Copyright (c) 2007 The Regents of the University of California. All
// Rights Reserved. Permission to use, copy, modify, and distribute this
// software and its documentation without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph appear in all copies. This software program and
// documentation are copyrighted by The Regents of the University of
// California. The software program and documentation are supplied "AS
// IS", without any accompanying services from The Regents. The Regents
// does not warrant that the operation of the program will be
// uninterrupted or error-free. The end-user understands that the program
// was developed for research purposes and is advised not to rely
// exclusively on the program for any reason. IN NO EVENT SHALL THE
// UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
// SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
// UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

package org.argouml.sequence2.diagram;

import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.argouml.model.Model;
import org.tigris.gef.base.Editor;
import org.tigris.gef.base.LayerPerspective;
import org.tigris.gef.base.ModeCreatePolyEdge;
import org.tigris.gef.presentation.Fig;
import org.tigris.gef.presentation.FigEdge;
import org.tigris.gef.presentation.FigPoly;

/**
 * Mode to create a link between two FigClassifierRoles.
 * TODO: Provide a ModeFactory and then this class can become package scope.
 * @author penyaskito
 */
public class ModeCreateMessage extends ModeCreatePolyEdge {

    /**
     * Logger.
     */
    private static final Logger LOG =
        Logger.getLogger(ModeCreateMessage.class.getName());

    private static final int DEFAULT_ACTIVATION_HEIGHT = 50;
    private static final int DEFAULT_MESSAGE_GAP = 20;

    /**
     * The constructor.
     *
     * @param par the editor
     */
    public ModeCreateMessage(Editor par) {
        super(par);
        LOG.log(Level.FINE, "ModeCreateMessage created with editor:{0}", editor);
    }

    /**
     * The constructor.
     */
    public ModeCreateMessage() {
        super();
        LOG.log(Level.FINE, "ModeCreateMessage created without editor.");
    }

    @Override
    public void endAttached(FigEdge fe) {
        super.endAttached(fe);
        final SequenceDiagramGraphModel gm =
            (SequenceDiagramGraphModel) getEditor().getGraphModel();

        final FigMessage figMessage = (FigMessage) fe;

        Object message = fe.getOwner();
        FigClassifierRole dcr = (FigClassifierRole) fe.getDestFigNode();
        FigClassifierRole scr = (FigClassifierRole) fe.getSourceFigNode();

        ensureSpace(figMessage);

        if (figMessage.isSynchCallMessage()) {
            // Auto-create a return message for a call message

            // TODO: Maybe a return message already exists. Check first and
            // and if the first found has no activator then set this call
            // message as the activator and skip the code below.

            // get the source of the return message
            final Object returnMessageSource =
                Model.getFacade().getReceiver(message);
            // get the dest of the return message
            final Object returnMessageDest =
                Model.getFacade().getSender(message);

            // create the return message modelelement with the interaction
            // and the collaboration
            final Object returnMessage = gm.connectMessage(
                    returnMessageSource,
                    returnMessageDest,
                    Model.getMessageSort().getReply());

            // Correct the activator value
            Model.getCollaborationsHelper().setActivator(
                    returnMessage, message);

            final LayerPerspective layer =
                (LayerPerspective) editor.getLayerManager().getActiveLayer();

            FigMessage returnEdge = null;

            List<Fig> figs = layer.getContents();
            for (Fig fig : figs) {
                if (fig.getOwner() == returnMessage) {
                    returnEdge = (FigMessage) fig;
                    break;
                }
            }

            returnEdge.setSourcePortFig(fe.getDestPortFig());
            returnEdge.setSourceFigNode(dcr);
            returnEdge.setDestPortFig(fe.getSourcePortFig());
            returnEdge.setDestFigNode(scr);

            final Point[] points = returnEdge.getPoints();
            for (int i = 0; i < points.length; ++i) {
                // TODO: this shouldn't be hardcoded
                // 20 is the height of the spline
                // 50 is the default activation height
                points[i].y = fe.getFirstPoint().y + DEFAULT_ACTIVATION_HEIGHT;
            }
            returnEdge.setPoints(points);

            if (returnEdge.isSelfMessage()) {
                returnEdge.convertToArc();
            }

            // Mark the contain FigPoly as complete.
            // TODO: I think more work is needed in GEF to either do this
            // automatically when both ends are set or at the very least
            // Give a setComplete method on FigPolyEdge that calls its
            // contained poly
            FigPoly poly = (FigPoly) returnEdge.getFig();
            poly.setComplete(true);
        } else if (figMessage.isReplyMessage()) {
            figMessage.determineActivator();
        }
        FigPoly poly = (FigPoly) fe.getFig();
        poly.setComplete(true);

        dcr.createActivations();
        dcr.renderingChanged();
        if (dcr != scr) {
            scr.createActivations();
            scr.renderingChanged();
        }
    }

    /**
     * Called for a call message. Make sure there is enough space to fit the
     * return message that will be created below.
     * @param figMessage
     */
    private void ensureSpace(final FigMessage figMessage) {
        // Make sure there is the minimum gap above the message being drawn
        final FigMessage firstMessageAbove = getNearestMessage(
                (FigClassifierRole) getSourceFigNode(),
                figMessage,
                false);

        if (firstMessageAbove != null) {
            final int figMessageY = figMessage.getFirstPoint().y;
            final int firstMessageY = firstMessageAbove.getFirstPoint().y;
            if ((figMessageY - firstMessageY) < DEFAULT_MESSAGE_GAP) {
                figMessage.translateEdge(
                        0,
                        firstMessageY + DEFAULT_MESSAGE_GAP - figMessageY);
            }
        }

        // Make sure there is the minimum gap below the message being drawn
        LOG.log(Level.INFO, "Looking for minimum space below");
        final FigMessage firstMessageBelow = getNearestMessage(
                (FigClassifierRole) getSourceFigNode(),
                figMessage,
                true);

        final int heightPlusGap;
        if (figMessage.isSynchCallMessage()) {
            heightPlusGap =
                DEFAULT_ACTIVATION_HEIGHT + DEFAULT_MESSAGE_GAP;
        } else {
            heightPlusGap =
                DEFAULT_MESSAGE_GAP;
        }

        if (firstMessageBelow != null
                && firstMessageBelow.getFirstPoint().y
                < figMessage.getFirstPoint().y + heightPlusGap) {

            final int dy =
                (figMessage.getFirstPoint().y
                + heightPlusGap)
                - firstMessageBelow.getFirstPoint().y;

            for (FigMessage fig : getMessagesBelow(figMessage)) {
                fig.translateEdge(0, dy);
                if (fig.isCreateMessage()) {
                    FigClassifierRole fcr =
                        (FigClassifierRole) fig.getDestFigNode();
                    fcr.positionHead(fig);
                }
            }
        }
    }

    /**
     * Get a list of FigMessages below (higher Y position) than the FigMessage
     * provided.
     * @param figMessage
     * @return a list of FigMessage
     */
    private List<FigMessage> getMessagesBelow(FigMessage figMessage) {
        final List<FigMessage> messagesBelow = new ArrayList<FigMessage>();
        for (Fig f : getEditor().getLayerManager().getContents()) {
            if (f instanceof FigMessage
                    && f != figMessage) {
                FigMessage fm = (FigMessage) f;
                if (fm.getFirstPoint().y >= figMessage.getFirstPoint().y) {
                    messagesBelow.add((FigMessage) f);
                }
            }
        }
        return messagesBelow;
    }

    /**
     * Get the first FigMessage below (higher Y position) the given
     * FigMessage.
     * @param figMessage
     * @return the FigMessage below or null
     */
    private FigMessage getNearestMessage(
            final FigClassifierRole figClassifierRole,
            final FigMessage figMessage,
            final boolean below) {

        FigMessage nearestMessage = null;

        for (FigEdge fe : figClassifierRole.getFigEdges()) {
            if (fe instanceof FigMessage && fe != figMessage) {
                final FigMessage fm = (FigMessage) fe;
                final int y = fm.getFirstPoint().y;
                if (below) {
                    if (isBetween(y, figMessage, nearestMessage)) {
                        nearestMessage = fm;
                    }
                } else {
                    if (isBetween(y, nearestMessage, figMessage)) {
                        nearestMessage = fm;
                    }
                }
            }
        }

        return nearestMessage;
    }

    /**
     * Return true if a given y co-ordinate is between the two given messages
     * @param y the value to test
     * @param message1 the lowest value to check
     * @param message2 the upper value to check
     * @return
     */
    private boolean isBetween(
            final int val,
            final FigMessage message1,
            final FigMessage message2) {
        if ((message1 == null || val >= message1.getFirstPoint().y)
                && (message2 == null || val <= message2.getFirstPoint().y)) {
            return true;
        }
        return false;
    }
}