diff --git a/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java b/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java index c9994252..d09c9243 100644 --- a/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java +++ b/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java @@ -241,16 +241,6 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP */ void setMetric(boolean isMetric); - /** - * Groups all selected components. - */ - void groupSelectedComponents(); - - /** - * Ungroups all selected components. - */ - void ungroupSelectedComponents(); - /** * Locks or unlocks the specified layer. All components within +- 0.5 range will be affected by * the change as well. diff --git a/diylc/diylc-core/src/org/diylc/common/ISelectionProcessor.java b/diylc/diylc-core/src/org/diylc/common/ISelectionProcessor.java index 5edadf15..5b0e6147 100644 --- a/diylc/diylc-core/src/org/diylc/common/ISelectionProcessor.java +++ b/diylc/diylc-core/src/org/diylc/common/ISelectionProcessor.java @@ -1,19 +1,26 @@ package org.diylc.common; import java.awt.Point; +import java.util.Collection; import java.util.List; import org.diylc.core.ExpansionMode; import org.diylc.core.IDIYComponent; public interface ISelectionProcessor { + + public static final int CLOCKWISE = 1; + public static final int COUNTER_CLOCKWISE = 1; + + public static final int HORIZONTAL = 0; + public static final int VERTICAL = 1; /** * Returns the current {@link ComponentTransferable}. * * @return */ - List> getSelectedComponents(); + Collection> getSelectedComponents(); /** * Updates the selection with the specified list of component. Also, updates control point map @@ -21,7 +28,7 @@ public interface ISelectionProcessor { * * @param newSelection */ - void updateSelection(List> newSelection); + void updateSelection(Collection> newSelection); /** * Finds all components at the specified location, sorted by z-index from top to bottom. @@ -53,4 +60,16 @@ public interface ISelectionProcessor { * @param direction 1 for clockwise, -1 for counter-clockwise */ void rotateSelection(int direction); + + /** + * Groups all selected components. + */ + void groupSelectedComponents(); + + /** + * Ungroups all selected components. + */ + void ungroupSelectedComponents(); + + void mirrorSelection(int direction); } diff --git a/diylc/diylc-core/src/org/diylc/presenter/ComponentProcessor.java b/diylc/diylc-core/src/org/diylc/presenter/ComponentProcessor.java index d972e94e..4a6671ce 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/ComponentProcessor.java +++ b/diylc/diylc-core/src/org/diylc/presenter/ComponentProcessor.java @@ -6,8 +6,10 @@ import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -185,19 +187,22 @@ public class ComponentProcessor { * @param selectedComponents * @return */ - public List getMutualSelectionProperties(List> selectedComponents) throws Exception { + public List getMutualSelectionProperties(Collection> selectedComponents) throws Exception { if (selectedComponents.isEmpty()) { return null; } List properties = new ArrayList(); - IDIYComponent firstComponent = selectedComponents.get(0); + + List> selectedList = new ArrayList>(selectedComponents); + + IDIYComponent firstComponent = selectedList.get(0); properties.addAll(extractProperties(firstComponent.getClass())); // Initialize values for (PropertyWrapper property : properties) { property.readFrom(firstComponent); } for (int i = 1; i < selectedComponents.size(); i++) { - IDIYComponent component = selectedComponents.get(i); + IDIYComponent component = selectedList.get(i); List newProperties = extractProperties(component.getClass()); for (PropertyWrapper property : newProperties) { property.readFrom(component); @@ -219,14 +224,6 @@ public class ComponentProcessor { } } } - // for (PropertyWrapper property : properties) { - // try { - // property.readUniqueFrom(component); - // } catch (Exception e) { - // // TODO Auto-generated catch block - // e.printStackTrace(); - // } - // } } Collections.sort(properties, ComparatorFactory.getInstance().getDefaultPropertyComparator()); return properties; diff --git a/diylc/diylc-core/src/org/diylc/presenter/DrawingManager.java b/diylc/diylc-core/src/org/diylc/presenter/DrawingManager.java index 25a03a8b..912db2ea 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/DrawingManager.java +++ b/diylc/diylc-core/src/org/diylc/presenter/DrawingManager.java @@ -11,6 +11,7 @@ import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.Area; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -97,7 +98,7 @@ public class DrawingManager { * @return */ public List> drawProject(Graphics2D g2d, Project project, Set drawOptions, - IComponentFiler filter, Rectangle selectionRect, List> selectedComponents, + IComponentFiler filter, Rectangle selectionRect, Collection> selectedComponents, Set> lockedComponents, Set> groupedComponents, List controlPointSlot, List> componentSlot, boolean dragInProgress) { failedComponents.clear(); diff --git a/diylc/diylc-core/src/org/diylc/presenter/Presenter.java b/diylc/diylc-core/src/org/diylc/presenter/Presenter.java index 15448fd2..c52634bf 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/Presenter.java +++ b/diylc/diylc-core/src/org/diylc/presenter/Presenter.java @@ -41,6 +41,7 @@ import org.diylc.common.IComponentFiler; import org.diylc.common.IKeyProcessor; import org.diylc.common.IPlugIn; import org.diylc.common.IPlugInPort; +import org.diylc.common.ISelectionProcessor; import org.diylc.common.Orientation; import org.diylc.common.OrientationHV; import org.diylc.common.PropertyWrapper; @@ -96,7 +97,7 @@ public class Presenter implements IPlugInPort { // Maps component class names to ComponentType objects. private List plugIns; - private List> selectedComponents; + private Set> selectedComponents; // Maps components that have at least one dragged point to set of indices // that designate which of their control points are being dragged. private Map, Set> controlPointMap; @@ -131,7 +132,7 @@ public class Presenter implements IPlugInPort { this.view = view; plugIns = new ArrayList(); messageDispatcher = new MessageDispatcher(true); - selectedComponents = new ArrayList>(); + selectedComponents = new HashSet>(); lockedComponents = new HashSet>(); currentProject = new Project(); // cloner = new Cloner(); @@ -558,6 +559,7 @@ public class Presenter implements IPlugInPort { || !newSelection.contains(topComponent)) { newSelection.clear(); } + newSelection.addAll(findAllGroupedComponents(topComponent)); } } @@ -754,7 +756,7 @@ public class Presenter implements IPlugInPort { } @Override - public List> getSelectedComponents() { + public Collection> getSelectedComponents() { return selectedComponents; } @@ -1056,6 +1058,7 @@ public class Presenter implements IPlugInPort { @Override public void rotateSelection(int direction) { if (!selectedComponents.isEmpty()) { + LOG.trace("Rotating selected components"); Project oldProject = currentProject.clone(); rotateComponents(this.selectedComponents, direction, isSnapToGrid()); messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED, oldProject, currentProject.clone(), @@ -1069,7 +1072,7 @@ public class Presenter implements IPlugInPort { * @param direction 1 for clockwise, -1 for counter-clockwise */ @SuppressWarnings("unchecked") - private void rotateComponents(List> components, int direction, boolean snapToGrid) { + private void rotateComponents(Collection> components, int direction, boolean snapToGrid) { Point center = getCenterOf(components, snapToGrid); AffineTransform rotate = AffineTransform.getRotateInstance(Math.PI / 2 * direction, center.x, center.y); @@ -1129,7 +1132,56 @@ public class Presenter implements IPlugInPort { } } - private Point getCenterOf(List> components, boolean snapToGrid) { + @SuppressWarnings("unchecked") + @Override + public void mirrorSelection(int direction) { + if (!selectedComponents.isEmpty()) { + LOG.trace("Mirroring selected components"); + Project oldProject = currentProject.clone(); + + Point center = getCenterOf(selectedComponents, isSnapToGrid()); + + boolean canMirror = true; + for (IDIYComponent component : selectedComponents) { + ComponentType type = + ComponentProcessor.getInstance().extractComponentTypeFrom( + (Class>) component.getClass()); + if (!type.isStretchable() && component.getControlPointCount() > 1) { + canMirror = false; + break; + } + } + + if (!canMirror) + if (view.showConfirmDialog( + "Selection contains components that cannot bi mirrored. Do you want to exclude them?", "Mirror Selection", + IView.YES_NO_OPTION, IView.QUESTION_MESSAGE) != IView.YES_OPTION) + return; + + for (IDIYComponent component : selectedComponents) { + ComponentType type = + ComponentProcessor.getInstance().extractComponentTypeFrom( + (Class>) component.getClass()); + if (type.isStretchable() || component.getControlPointCount() == 1) { + drawingManager.invalidateComponent(component); + for (int i = 0; i < component.getControlPointCount(); i++) { + Point p = component.getControlPoint(i); + if (direction == ISelectionProcessor.HORIZONTAL) { + component.setControlPoint(new Point(p.x - 2 * (p.x - center.x), p.y), i); + } else { + component.setControlPoint(new Point(p.x, p.y - 2 * (p.y - center.y)), i); + } + } + } + } + + messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED, oldProject, currentProject.clone(), + "Mirror Selection"); + messageDispatcher.dispatchMessage(EventType.REPAINT); + } + } + + private Point getCenterOf(Collection> components, boolean snapToGrid) { // Determine center of rotation int minX = Integer.MAX_VALUE; int minY = Integer.MAX_VALUE; @@ -1455,8 +1507,8 @@ public class Presenter implements IPlugInPort { messageDispatcher.dispatchMessage(EventType.REPAINT); } - public void updateSelection(List> newSelection) { - this.selectedComponents = newSelection; + public void updateSelection(Collection> newSelection) { + this.selectedComponents = new HashSet>(newSelection); Map, Set> controlPointMap = new HashMap, Set>(); for (IDIYComponent component : selectedComponents) { Set indices = new HashSet(); @@ -1843,17 +1895,15 @@ public class Presenter implements IPlugInPort { if (this.selectedComponents.isEmpty()) { throw new RuntimeException("No components selected"); } - ComponentType selectedType = - ComponentProcessor.getInstance().extractComponentTypeFrom( - (Class>) this.selectedComponents.get(0).getClass()); - for (int i = 1; i < this.selectedComponents.size(); i++) { - ComponentType newType = - ComponentProcessor.getInstance().extractComponentTypeFrom( - (Class>) this.selectedComponents.get(i).getClass()); - if (newType.getInstanceClass() != selectedType.getInstanceClass()) { + ComponentType selectedType = null; + Iterator> iterator = this.selectedComponents.iterator(); + while (iterator.hasNext()) { + ComponentType type = ComponentProcessor.getInstance().extractComponentTypeFrom((Class>) iterator.next().getClass()); + if (selectedType == null) + selectedType = type; + else if (selectedType.getInstanceClass() != type.getInstanceClass()) throw new RuntimeException("Template can be applied on multiple components of the same type only"); - } - } + } return getTemplatesFor(selectedType.getCategory(), selectedType.getName()); } diff --git a/diylc/diylc-library/lib/diylc-core.jar b/diylc/diylc-library/lib/diylc-core.jar index 413ef6b1..16a668b3 100644 Binary files a/diylc/diylc-library/lib/diylc-core.jar and b/diylc/diylc-library/lib/diylc-core.jar differ diff --git a/diylc/diylc-swing/lib/diylc-core.jar b/diylc/diylc-swing/lib/diylc-core.jar index 413ef6b1..16a668b3 100644 Binary files a/diylc/diylc-swing/lib/diylc-core.jar and b/diylc/diylc-swing/lib/diylc-core.jar differ diff --git a/diylc/diylc-swing/library/main.jar b/diylc/diylc-swing/library/main.jar index fd09da1f..ff8d26b9 100644 Binary files a/diylc/diylc-swing/library/main.jar and b/diylc/diylc-swing/library/main.jar differ diff --git a/diylc/diylc-swing/src/org/diylc/images/IconLoader.java b/diylc/diylc-swing/src/org/diylc/images/IconLoader.java index dec8d92b..be34240f 100644 --- a/diylc/diylc-swing/src/org/diylc/images/IconLoader.java +++ b/diylc/diylc-swing/src/org/diylc/images/IconLoader.java @@ -36,7 +36,8 @@ public enum IconLoader { "cloud_edit.png"), CloudUpload("cloud_upload.png"), NavLeftBlue("nav_left_blue.png"), NavRightBlue( "nav_right_blue.png"), MissingImage("missing_image.png"), CloudBg("cloud_bg.png"), CloudBig("cloud_big.png"), CloudWait( "cloud_wait.png"), Spinning("spinning.gif"), Megaphone("megaphone.png"), Download("download.png"), Eye("eye.png"), Messages( - "messages.png"), SearchBox("search-box.png"), Screwdriver("screwdriver.png"), Hammer("hammer.png"); + "messages.png"), SearchBox("search-box.png"), Screwdriver("screwdriver.png"), Hammer("hammer.png"), FlipHorizontal("flip_horizontal.png"), + FlipVertical("flip_vertical.png"); protected String name; diff --git a/diylc/diylc-swing/src/org/diylc/images/flip_horizontal.png b/diylc/diylc-swing/src/org/diylc/images/flip_horizontal.png new file mode 100644 index 00000000..adc07a75 Binary files /dev/null and b/diylc/diylc-swing/src/org/diylc/images/flip_horizontal.png differ diff --git a/diylc/diylc-swing/src/org/diylc/images/flip_vertical.png b/diylc/diylc-swing/src/org/diylc/images/flip_vertical.png new file mode 100644 index 00000000..3a1d19d4 Binary files /dev/null and b/diylc/diylc-swing/src/org/diylc/images/flip_vertical.png differ diff --git a/diylc/diylc-swing/src/org/diylc/swing/ActionFactory.java b/diylc/diylc-swing/src/org/diylc/swing/ActionFactory.java index 4f79c19c..29f607eb 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/ActionFactory.java +++ b/diylc/diylc-swing/src/org/diylc/swing/ActionFactory.java @@ -7,6 +7,7 @@ import java.awt.event.KeyEvent; import java.awt.print.PrinterException; import java.io.File; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Set; @@ -17,6 +18,7 @@ import javax.swing.KeyStroke; import org.apache.log4j.Logger; import org.diylc.appframework.miscutils.ConfigurationManager; import org.diylc.common.IPlugInPort; +import org.diylc.common.ISelectionProcessor; import org.diylc.common.ITask; import org.diylc.common.PropertyWrapper; import org.diylc.core.ExpansionMode; @@ -142,6 +144,10 @@ public class ActionFactory { public RotateSelectionAction createRotateSelectionAction(IPlugInPort plugInPort, int direction) { return new RotateSelectionAction(plugInPort, direction); } + + public MirrorSelectionAction createMirrorSelectionAction(IPlugInPort plugInPort, int direction) { + return new MirrorSelectionAction(plugInPort, direction); + } public SendToBackAction createSendToBackAction(IPlugInPort plugInPort) { return new SendToBackAction(plugInPort); @@ -681,7 +687,7 @@ public class ActionFactory { } } - private static List> cloneComponents(List> components) { + private static List> cloneComponents(Collection> components) { List> result = new ArrayList>(components.size()); for (IDIYComponent component : components) { try { @@ -920,6 +926,33 @@ public class ActionFactory { plugInPort.rotateSelection(direction); } } + + public static class MirrorSelectionAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + private IPlugInPort plugInPort; + private int direction; + + public MirrorSelectionAction(IPlugInPort plugInPort, int direction) { + super(); + this.plugInPort = plugInPort; + this.direction = direction; + if (direction == ISelectionProcessor.HORIZONTAL) { + putValue(AbstractAction.NAME, "Mirror Horizontally"); + putValue(AbstractAction.SMALL_ICON, IconLoader.FlipHorizontal.getIcon()); + } else { + putValue(AbstractAction.NAME, "Mirror Vertically"); + putValue(AbstractAction.SMALL_ICON, IconLoader.FlipVertical.getIcon()); + } + } + + @Override + public void actionPerformed(ActionEvent e) { + LOG.info("Mirror Selection triggered: " + direction); + plugInPort.mirrorSelection(direction); + } + } public static class SendToBackAction extends AbstractAction { diff --git a/diylc/diylc-swing/src/org/diylc/swing/plugins/canvas/CanvasPlugin.java b/diylc/diylc-swing/src/org/diylc/swing/plugins/canvas/CanvasPlugin.java index 56f56023..d34741f3 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/plugins/canvas/CanvasPlugin.java +++ b/diylc/diylc-swing/src/org/diylc/swing/plugins/canvas/CanvasPlugin.java @@ -32,6 +32,7 @@ import org.diylc.common.BadPositionException; import org.diylc.common.EventType; import org.diylc.common.IPlugIn; import org.diylc.common.IPlugInPort; +import org.diylc.common.ISelectionProcessor; import org.diylc.core.ExpansionMode; import org.diylc.core.IDIYComponent; import org.diylc.core.Template; @@ -55,6 +56,7 @@ public class CanvasPlugin implements IPlugIn, ClipboardOwner { private JPopupMenu popupMenu; private JMenu selectionMenu; private JMenu expandMenu; + private JMenu transformMenu; private JMenu applyTemplateMenu; private ActionFactory.CutAction cutAction; @@ -72,6 +74,8 @@ public class CanvasPlugin implements IPlugIn, ClipboardOwner { private ActionFactory.ExpandSelectionAction expandSelectionSameTypeAction; private ActionFactory.RotateSelectionAction rotateClockwiseAction; private ActionFactory.RotateSelectionAction rotateCounterclockwiseAction; + private ActionFactory.MirrorSelectionAction mirrorHorizontallyAction; + private ActionFactory.MirrorSelectionAction mirrorVerticallyAction; private IPlugInPort plugInPort; private ISwingUI swingUI; @@ -244,12 +248,7 @@ public class CanvasPlugin implements IPlugIn, ClipboardOwner { popupMenu.addSeparator(); popupMenu.add(getEditSelectionAction()); popupMenu.add(getDeleteSelectionAction()); - popupMenu.add(getRotateClockwiseAction()); - popupMenu.add(getRotateCounterclockwiseAction()); - popupMenu.add(getGroupAction()); - popupMenu.add(getUngroupAction()); - popupMenu.add(getSendToBackAction()); - popupMenu.add(getBringToFrontAction()); + popupMenu.add(getTransformMenu()); popupMenu.add(getSaveAsTemplateAction()); popupMenu.add(getApplyTemplateMenu()); popupMenu.add(getExpandMenu()); @@ -278,6 +277,21 @@ public class CanvasPlugin implements IPlugIn, ClipboardOwner { return expandMenu; } + public JMenu getTransformMenu() { + if (transformMenu == null) { + transformMenu = new JMenu("Transform Selection"); + transformMenu.add(getRotateClockwiseAction()); + transformMenu.add(getRotateCounterclockwiseAction()); + transformMenu.add(getMirrorHorizontallyAction()); + transformMenu.add(getMirrorVerticallyAction()); + transformMenu.add(getGroupAction()); + transformMenu.add(getUngroupAction()); + transformMenu.add(getSendToBackAction()); + transformMenu.add(getBringToFrontAction()); + } + return transformMenu; + } + public JMenu getApplyTemplateMenu() { if (applyTemplateMenu == null) { applyTemplateMenu = new JMenu("Apply Template"); @@ -384,6 +398,22 @@ public class CanvasPlugin implements IPlugIn, ClipboardOwner { return rotateCounterclockwiseAction; } + public ActionFactory.MirrorSelectionAction getMirrorHorizontallyAction() { + if (mirrorHorizontallyAction == null) { + mirrorHorizontallyAction = + ActionFactory.getInstance().createMirrorSelectionAction(plugInPort, ISelectionProcessor.HORIZONTAL); + } + return mirrorHorizontallyAction; + } + + public ActionFactory.MirrorSelectionAction getMirrorVerticallyAction() { + if (mirrorVerticallyAction == null) { + mirrorVerticallyAction = + ActionFactory.getInstance().createMirrorSelectionAction(plugInPort, ISelectionProcessor.VERTICAL); + } + return mirrorVerticallyAction; + } + public ActionFactory.SaveAsTemplateAction getSaveAsTemplateAction() { if (saveAsTemplateAction == null) { saveAsTemplateAction = ActionFactory.getInstance().createSaveAsTemplateAction(plugInPort); diff --git a/diylc/diylc-swing/src/org/diylc/swing/plugins/statusbar/StatusBar.java b/diylc/diylc-swing/src/org/diylc/swing/plugins/statusbar/StatusBar.java index 278138ff..01a11fc6 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/plugins/statusbar/StatusBar.java +++ b/diylc/diylc-swing/src/org/diylc/swing/plugins/statusbar/StatusBar.java @@ -296,7 +296,7 @@ public class StatusBar extends JPanel implements IPlugIn { } break; case SELECTION_CHANGED: - List> selection = (List>) params[0]; + Collection> selection = (Collection>) params[0]; Collection> stuckComponents = (Collection>) params[1]; Collection componentNames = new HashSet(); for (IDIYComponent component : selection) {