/******************************************************************************* * MontiCore Language Workbench * Copyright (c) 2015, 2016, MontiCore, All rights reserved. * * This project is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3.0 of the License, or (at your option) any later version. * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this project. If not, see <http://www.gnu.org/licenses/>. *******************************************************************************/ package de.monticore.genericgraphics.view.layout.kieler; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.PolylineConnection; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.PointList; import org.eclipse.gef.commands.Command; import de.cau.cs.kieler.core.kgraph.KEdge; import de.cau.cs.kieler.core.kgraph.KGraphElement; import de.cau.cs.kieler.core.kgraph.KLabel; import de.cau.cs.kieler.core.kgraph.KNode; import de.cau.cs.kieler.kiml.klayoutdata.KEdgeLayout; import de.cau.cs.kieler.kiml.klayoutdata.KPoint; import de.cau.cs.kieler.kiml.klayoutdata.KShapeLayout; import de.cau.cs.kieler.kiml.ui.diagram.LayoutMapping; import de.monticore.genericgraphics.controller.editparts.IMCConnectionEdgeEditPart; import de.monticore.genericgraphics.controller.editparts.IMCEditPart; import de.monticore.genericgraphics.controller.editparts.IMCGraphicalEditPart; import de.monticore.genericgraphics.controller.editparts.IMCShapeEditPart; import de.monticore.genericgraphics.controller.editparts.IMCViewElementEditPart; import de.monticore.genericgraphics.model.graphics.IEdgeViewElement; import de.monticore.genericgraphics.model.graphics.IShapeViewElement; import de.monticore.genericgraphics.model.graphics.IViewElement; import de.monticore.genericgraphics.view.figures.connections.MCBendpoint; /** * <p> * A {@link Command} for application of a {@link LayoutMapping}. * </p> * <p> * This command applies the computed view data in the form of a * {@link LayoutMapping} from the KIELER project to the {@link IViewElement * IViewElements} of {@link IMCViewElementEditPart IMCViewElementEditParts}. <br> * see * http://rtsys.informatik.uni-kiel.de/confluence/pages/viewpage.action?pageId * =328078 * </p> * * @author Tim Enger */ public class ApplyLayoutCommand extends Command { private LayoutMapping<IMCEditPart> mapping; // store view element values for undo private Map<IMCEditPart, IViewElement> undoMap; // indicates if absolute or relative bendpoints should // be created private boolean absoluteBPs; // apparently the data of the figures it not updated immediately // so use the VE element data to compute relative bendpoints // it's a workaround until someone finds out how to // update the figures before creating bendpoints // so at the moment we need to store a mapping from ve to figures // to use it, when creating bendpoints private Map<IFigure, IShapeViewElement> figs; private double zoomFactor; /** * Constructor * * @param mapping The {@link LayoutMapping} to apply. * @param absoluteBPs If <tt>true</tt> absolute bendpoints are created for the * connections, otherwise relative ones. * @param zoomFactor The current zoom factor of the editor, important for * relative bendpoint creation */ public ApplyLayoutCommand(LayoutMapping<IMCEditPart> mapping, boolean absoluteBPs, double zoomFactor) { this.mapping = mapping; this.absoluteBPs = absoluteBPs; this.zoomFactor = zoomFactor; createUndoMap(); figs = new HashMap<IFigure, IShapeViewElement>(); } @Override public void execute() { Map<KShapeLayout, IMCShapeEditPart> shapes = new HashMap<KShapeLayout, IMCShapeEditPart>(); Map<KEdgeLayout, IMCConnectionEdgeEditPart> edges = new HashMap<KEdgeLayout, IMCConnectionEdgeEditPart>(); Map<KShapeLayout, IMCShapeEditPart> labels = new HashMap<KShapeLayout, IMCShapeEditPart>(); // first collect all data for (Entry<KGraphElement, IMCEditPart> e : mapping.getGraphMap().entrySet()) { KGraphElement ge = e.getKey(); IMCEditPart ep = e.getValue(); if (ep instanceof IMCShapeEditPart) { IShapeViewElement sve = ((IMCShapeEditPart) ep).getViewElement(); if (ge instanceof KNode) { KShapeLayout layout = ((KNode) ge).getData(KShapeLayout.class); shapes.put(layout, (IMCShapeEditPart) ep); figs.put(((IMCGraphicalEditPart) ep).getFigure(), sve); } else if (ge instanceof KLabel) { KShapeLayout layout = ((KLabel) ge).getData(KShapeLayout.class); labels.put(layout, (IMCShapeEditPart) ep); } } else if (ep instanceof IMCConnectionEdgeEditPart && ge instanceof KEdge) { KEdgeLayout layout = ((KEdge) ge).getData(KEdgeLayout.class); edges.put(layout, (IMCConnectionEdgeEditPart) ep); } } // process shapes for (Entry<KShapeLayout, IMCShapeEditPart> entry : shapes.entrySet()) { setShapeViewElement(entry.getValue(), entry.getKey()); } // process edges for (Entry<KEdgeLayout, IMCConnectionEdgeEditPart> entry : edges.entrySet()) { IMCConnectionEdgeEditPart ep = entry.getValue(); KEdgeLayout layout = entry.getKey(); setEdgeViewElement(ep, layout); // we have to manually set the points of the connection in order to // use relative locators // if we would not do this here, the connection would not be updated // before the Label locators ask for start & end point // took me only one night to find that out and try // a lot of different other ways in order to avoid the following // which cannot be avoided, or I missed something :) PolylineConnection con = (PolylineConnection) ep.getFigure(); PointList points = new PointList(); points.addPoint(new Point(Math.round(layout.getSourcePoint().getX()), Math.round(layout.getSourcePoint().getY()))); for (KPoint point : layout.getBendPoints()) { points.addPoint(new Point(Math.round(point.getX()), Math.round(point.getY()))); } points.addPoint(new Point(Math.round(layout.getTargetPoint().getX()), Math.round(layout.getTargetPoint().getY()))); con.setPoints(points); con.getParent().revalidate(); con.getParent().repaint(); } // process the labels for (Entry<KShapeLayout, IMCShapeEditPart> entry : labels.entrySet()) { setShapeViewElement(entry.getValue(), entry.getKey()); } } @Override public boolean canUndo() { return true; } @Override public void undo() { for (Entry<IMCEditPart, IViewElement> entry : undoMap.entrySet()) { IMCEditPart ep = entry.getKey(); if (!(ep instanceof IMCViewElementEditPart)) { continue; } IViewElement ve = ((IMCViewElementEditPart) ep).getViewElement(); IViewElement oldVE = entry.getValue(); if (ve instanceof IShapeViewElement && oldVE instanceof IShapeViewElement) { IShapeViewElement sve = (IShapeViewElement) ve; IShapeViewElement oldSVE = (IShapeViewElement) oldVE; sve.setX(oldSVE.getX()); sve.setY(oldSVE.getY()); sve.setWidth(oldSVE.getWidth()); sve.setHeight(oldSVE.getHeight()); sve.notifyObservers(); } else if (ve instanceof IEdgeViewElement && oldVE instanceof IEdgeViewElement) { IEdgeViewElement eve = (IEdgeViewElement) ve; IEdgeViewElement oldCVE = (IEdgeViewElement) oldVE; eve.setConstraints(oldCVE.getConstraints()); eve.notifyObservers(); } } } /** * creates the undo map, that stores the "old" {@link IViewElement} values for * every {@link IMCEditPart} that is member of the mapping. */ private void createUndoMap() { undoMap = new HashMap<IMCEditPart, IViewElement>(); for (IMCEditPart ep : mapping.getGraphMap().values()) { if (ep instanceof IMCViewElementEditPart) { IMCViewElementEditPart vep = (IMCViewElementEditPart) ep; undoMap.put(vep, (IViewElement) vep.getViewElement().clone()); } } } /** * Sets the values of the {@link IEdgeViewElement} according to the given * {@link KEdgeLayout}. * * @param cve The {@link IShapeViewElement} * @param e The {@link KEdgeLayout} */ private void setEdgeViewElement(IMCConnectionEdgeEditPart ep, KEdgeLayout e) { IEdgeViewElement eve = ep.getViewElement(); PolylineConnection con = (PolylineConnection) ep.getFigure(); List<MCBendpoint> points = new ArrayList<MCBendpoint>(); // set the view element according to layout data for (KPoint p : e.getBendPoints()) { MCBendpoint bp; if (absoluteBPs) { bp = new MCBendpoint(new Point((int) p.getX(), (int) p.getY())); } else { bp = createRelativeBendpoint(p, con); } points.add(bp); } eve.setConstraints(points); eve.notifyObservers(); } /** * Sets the values of the {@link IShapeViewElement} according to the given * {@link KShapeLayout}. * * @param sve The {@link IShapeViewElement} * @param s The {@link KShapeLayout} */ private void setShapeViewElement(IMCShapeEditPart ep, KShapeLayout s) { IShapeViewElement sve = ep.getViewElement(); // set the view element according to layout data sve.setX(Math.round(s.getXpos())); sve.setY(Math.round(s.getYpos())); // preferred size it used in VE, then leave it as it is // if (sve.getWidth() > 0) { // sve.setWidth(Math.round(s.getWidth())); // } // if (sve.getHeight() > 0) { // sve.setHeight(Math.round(s.getHeight())); // } // TODO // the size is not computes correctly and I don't know why, // so leave the preferred size for the moment sve.setWidth(-1); sve.setHeight(-1); sve.notifyObservers(); } private MCBendpoint createRelativeBendpoint(KPoint point, PolylineConnection con) { Point newL = new Point((int) point.getX(), (int) point.getY()); con.translateToRelative(newL); // apparently the data of the figures it not updated immediately // so use the VE element data to compute relative bendpoints // it's a workaround until someone finds out how to // update the figures before creating bendpoints IFigure startFig = con.getSourceAnchor().getOwner(); IShapeViewElement startVE = figs.get(startFig); Point ref1; if (startVE != null) { Dimension size = startFig.getPreferredSize(); ref1 = new Point((int) (startVE.getX() + 0.5 * size.width), (int) (startVE.getY() + 0.5 * size.height)); } else { ref1 = con.getSourceAnchor().getReferencePoint(); } IFigure targetFig = con.getTargetAnchor().getOwner(); IShapeViewElement targetVE = figs.get(targetFig); Point ref2; if (targetVE != null) { Dimension size = targetFig.getPreferredSize(); ref2 = new Point((int) (targetVE.getX() + 0.5 * size.width), (int) (targetVE.getY() + 0.5 * size.height)); } else { ref2 = con.getTargetAnchor().getReferencePoint(); } con.translateToRelative(ref1); con.translateToRelative(ref2); Dimension relStart = newL.getDifference(ref1); Dimension relTarget = newL.getDifference(ref2); // bendpoints seems to dependent on the zoom level // so scale them relStart.scale(zoomFactor); relTarget.scale(zoomFactor); return new MCBendpoint(relStart, relTarget); } }