/* * @(#)CombinePathsAction.java * * Copyright (c) 2006-2008 The authors and contributors of JHotDraw. * You may not use, copy or modify this file, except in compliance with the * accompanying license terms. */ package org.jhotdraw.samples.svg.action; import java.awt.geom.AffineTransform; import org.jhotdraw.draw.*; import org.jhotdraw.draw.action.*; import org.jhotdraw.samples.svg.figures.*; import org.jhotdraw.util.*; import java.util.*; import javax.swing.undo.*; import static org.jhotdraw.samples.svg.SVGAttributeKeys.*; /** * CombinePathsAction. * <p> * FIXME - Transforms are lost during Undo/Redo. * * @author Werner Randelshofer * @version $Id$ */ public class CombineAction extends AbstractSelectedAction { private static final long serialVersionUID = 1L; public static final String ID = "edit.combinePaths"; private CompositeFigure prototype; /** * If this variable is true, this action groups figures. * If this variable is false, this action ungroups figures. */ private boolean isCombineAction; private ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.samples.svg.Labels"); /** Creates a new instance. */ public CombineAction(DrawingEditor editor) { this(editor, new SVGPathFigure(true), true); } public CombineAction(DrawingEditor editor, SVGPathFigure prototype) { this(editor, prototype, true); } public CombineAction(DrawingEditor editor, SVGPathFigure prototype, boolean isGroupingAction) { super(editor); this.prototype = prototype; this.isCombineAction = isGroupingAction; labels = ResourceBundleUtil.getBundle("org.jhotdraw.samples.svg.Labels"); labels.configureAction(this, ID); updateEnabledState(); } @Override protected void updateEnabledState() { if (getView() != null) { setEnabled(isCombineAction ? canGroup() : canUngroup()); } else { setEnabled(false); } } protected boolean canGroup() { boolean canCombine = getView().getSelectionCount() > 1; if (canCombine) { for (Figure f : getView().getSelectedFigures()) { if (!(f instanceof SVGPathFigure)) { canCombine = false; break; } } } return canCombine; } protected boolean canUngroup() { return getView() != null && getView().getSelectionCount() == 1 && prototype != null && getView().getSelectedFigures().iterator().next().getClass().equals( prototype.getClass()) && ((CompositeFigure) getView().getSelectedFigures().iterator().next()).getChildCount() > 1; } @Override public void actionPerformed(java.awt.event.ActionEvent e) { if (isCombineAction) { combineActionPerformed(e); } else { splitActionPerformed(e); } } public void combineActionPerformed(java.awt.event.ActionEvent e) { final DrawingView view = getView(); Drawing drawing = view.getDrawing(); if (canGroup()) { final List<Figure> ungroupedPaths = drawing.sort(view.getSelectedFigures()); final int[] ungroupedPathsIndices = new int[ungroupedPaths.size()]; final int[] ungroupedPathsChildCounts = new int[ungroupedPaths.size()]; int i = 0; for (Figure f : ungroupedPaths) { ungroupedPathsIndices[i] = drawing.indexOf(f); ungroupedPathsChildCounts[i] = ((CompositeFigure) f).getChildCount(); //System.out.print("CombineAction indices[" + i + "] = " + ungroupedPathsIndices[i]); //System.out.println(" childCount[" + i + "] = " + ungroupedPathsChildCounts[i]); i++; } final CompositeFigure group = (CompositeFigure) prototype.clone(); combinePaths(view, group, ungroupedPaths, ungroupedPathsIndices[0]); UndoableEdit edit = new AbstractUndoableEdit() { private static final long serialVersionUID = 1L; @Override public String getPresentationName() { return labels.getTextProperty("edit.combinePaths"); } @Override public void redo() throws CannotRedoException { super.redo(); combinePaths(view, group, ungroupedPaths, ungroupedPathsIndices[0]); } @Override public void undo() throws CannotUndoException { super.undo(); splitPath(view, group, ungroupedPaths, ungroupedPathsIndices, ungroupedPathsChildCounts); } @Override public boolean addEdit(UndoableEdit anEdit) { return super.addEdit(anEdit); } }; fireUndoableEditHappened(edit); } } @SuppressWarnings("unchecked") public void splitActionPerformed(java.awt.event.ActionEvent e) { final DrawingView view = getView(); Drawing drawing = view.getDrawing(); if (canUngroup()) { final CompositeFigure group = (CompositeFigure) view.getSelectedFigures().iterator().next(); final LinkedList<Figure> ungroupedPaths = new LinkedList<Figure>(); final int[] ungroupedPathsIndices = new int[group.getChildCount()]; final int[] ungroupedPathsChildCounts = new int[group.getChildCount()]; int i = 0; int index = drawing.indexOf(group); for (Figure f : group.getChildren()) { SVGPathFigure path = new SVGPathFigure(true); for (Map.Entry<AttributeKey<?>, Object> entry : group.getAttributes().entrySet()) { path.set((AttributeKey<Object>)entry.getKey(), entry.getValue()); } ungroupedPaths.add(path); ungroupedPathsIndices[i] = index + i; ungroupedPathsChildCounts[i] = 1; i++; } splitPath(view, group, ungroupedPaths, ungroupedPathsIndices, ungroupedPathsChildCounts); UndoableEdit edit = new AbstractUndoableEdit() { private static final long serialVersionUID = 1L; @Override public String getPresentationName() { return labels.getTextProperty("edit.splitPath"); } @Override public void redo() throws CannotRedoException { super.redo(); splitPath(view, group, ungroupedPaths, ungroupedPathsIndices, ungroupedPathsChildCounts); } @Override public void undo() throws CannotUndoException { super.undo(); combinePaths(view, group, ungroupedPaths, ungroupedPathsIndices[0]); } }; fireUndoableEditHappened(edit); } } public void splitPath(DrawingView view, CompositeFigure group, List<Figure> ungroupedPaths, int[] ungroupedPathsIndices, int[] ungroupedPathsChildCounts) { view.clearSelection(); Iterator<Figure> groupedFigures = new LinkedList<Figure>(group.getChildren()).iterator(); group.basicRemoveAllChildren(); view.getDrawing().remove(group); SVGPathFigure pathFigure = (SVGPathFigure) group; pathFigure.flattenTransform(); for (int i = 0; i < ungroupedPaths.size(); i++) { CompositeFigure path = (CompositeFigure) ungroupedPaths.get(i); view.getDrawing().add(ungroupedPathsIndices[i], path); path.willChange(); for (int j = 0; j < ungroupedPathsChildCounts[i]; j++) { Figure child = groupedFigures.next(); child.willChange(); path.basicAdd(child); } path.changed(); } view.addToSelection(ungroupedPaths); } @SuppressWarnings("unchecked") public void combinePaths(DrawingView view, CompositeFigure group, Collection<Figure> figures, int groupIndex) { view.getDrawing().basicRemoveAll(figures); view.clearSelection(); view.getDrawing().add(groupIndex, group); group.willChange(); group.basicRemoveAllChildren(); // Verify if all figures have the same transform AffineTransform tx = figures.iterator().next().get(TRANSFORM); for (Figure f : figures) { AffineTransform ftx = f.get(TRANSFORM); if (ftx == tx || ftx != null && tx != null && ftx.equals(tx)) { } else { tx = null; break; } } for (Map.Entry<AttributeKey<?>, Object> entry : figures.iterator().next().getAttributes().entrySet()) { group.set((AttributeKey<Object>)entry.getKey(), entry.getValue()); } // In case all figures have the same transforms, we set it here. // In case the transforms are different, we set null here. group.set(TRANSFORM, tx); for (Figure f : figures) { SVGPathFigure path = (SVGPathFigure) f; // In case the transforms are different, we flatten it in the figures. if (tx == null) { path.flattenTransform(); } List<Figure> children = new LinkedList<Figure>(path.getChildren()); path.basicRemoveAllChildren(); for (Figure child : children) { SVGBezierFigure bez = (SVGBezierFigure) child; child.willChange(); group.basicAdd(child); } } group.changed(); view.addToSelection(group); } }