Browse Source

Improves Grouping

Improves Selection
TomTroppmann 2 years ago
parent
commit
e2a73255cb

BIN
res/images/buttons/close_button.png


+ 3 - 1
src/holeg/model/AbstractCanvasObject.java

@@ -45,7 +45,7 @@ public abstract class AbstractCanvasObject {
     public AbstractCanvasObject(AbstractCanvasObject other) {
         setName(other.getName());
         setImagePath(other.getImagePath());
-        this.position = other.position;
+        this.position = new Vec2i(other.position);
         this.id = IdCounter.next();
 
     }
@@ -59,6 +59,8 @@ public abstract class AbstractCanvasObject {
     }
 
 
+    public abstract AbstractCanvasObject copy();
+
     /**
      * Getter for the user-defined name (no unique).
      *

+ 60 - 4
src/holeg/model/GroupNode.java

@@ -2,24 +2,53 @@ package holeg.model;
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
+import java.util.Set;
 import java.util.logging.Logger;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import holeg.preferences.ImagePreference;
 import holeg.serialize.PostDeserialize;
+import holeg.ui.model.GuiSettings;
+import holeg.utility.math.vector.Vec2i;
 
 public class GroupNode extends AbstractCanvasObject implements PostDeserialize {
 	private static final Logger log = Logger.getLogger(AbstractCanvasObject.class.getName());
 
-	private final ArrayList<HolonObject> objectList = new ArrayList<>();
-	private final ArrayList<HolonSwitch> switchList = new ArrayList<>();
-	private final ArrayList<Node> nodeList = new ArrayList<>();
-	private final ArrayList<GroupNode> groupNodeList = new ArrayList<>();
+	private final List<HolonObject> objectList = new ArrayList<>();
+	private final List<HolonSwitch> switchList = new ArrayList<>();
+	private final List<Node> nodeList = new ArrayList<>();
+	private final List<GroupNode> groupNodeList = new ArrayList<>();
 
 	public GroupNode(String nodeName) {
 		super(nodeName);
 	}
 
+	public GroupNode(GroupNode other){
+		super(other.name);
+		for (HolonObject obj : other.objectList) {
+			this.add(new HolonObject(obj));
+		}
+		for (HolonSwitch sw : other.switchList) {
+			this.add(new HolonSwitch(sw));
+		}
+		for (Node node : other.nodeList) {
+			this.add(new Node(node));
+		}
+		for (GroupNode groupNode : other.groupNodeList) {
+			this.add(new GroupNode(groupNode));
+		}
+	}
+
+	@Override
+	public AbstractCanvasObject copy() {
+		return new GroupNode(this);
+	}
+
+
+
+
 	@Override
 	public String getImagePath() {
 		return ImagePreference.Canvas.GroupNode;
@@ -114,6 +143,27 @@ public class GroupNode extends AbstractCanvasObject implements PostDeserialize {
 		return objectList.stream().flatMap(HolonObject::elementsStream);
 	}
 
+	public void ungroup(){
+		getObjectsInThisLayer().forEach(obj -> {
+			obj.setGroupNode(null);
+		});
+		getGroupNode().ifPresent(parent -> {
+			parent.addAll(getObjectsInThisLayer());
+			parent.remove(this);
+		});
+
+		clear();
+	}
+
+	public static Vec2i calculateMiddlePosition(Collection<AbstractCanvasObject> objects){
+		Vec2i middle = new Vec2i();
+		if(!objects.isEmpty()){
+			objects.forEach(obj -> middle.addAssign(obj.getPosition()));
+			middle.divideAssign(objects.size());
+		}
+		return middle;
+	}
+
 
 	public void clear() {
 		objectList.clear();
@@ -126,4 +176,10 @@ public class GroupNode extends AbstractCanvasObject implements PostDeserialize {
 	public void postDeserialize() {
 		getObjectsInThisLayer().forEach(obj -> obj.setGroupNode(this));
 	}
+
+
+	@Override
+	public String toString(){
+		return "" +  objectList + switchList + nodeList +groupNodeList ;
+	}
 }

+ 6 - 1
src/holeg/model/HolonObject.java

@@ -53,6 +53,11 @@ public class HolonObject extends AbstractCanvasObject implements PostDeserialize
         }
     }
 
+    @Override
+    public AbstractCanvasObject copy(){
+        return new HolonObject(this);
+    }
+
     /**
      * Getter for all Elements in the HolonObject.
      *
@@ -237,7 +242,7 @@ public class HolonObject extends AbstractCanvasObject implements PostDeserialize
     }
 
     public String toString() {
-        return "[HolonObject: " + "id=" + getId() + ", name=" + name + ", state=" + state + ", elements=[" + elementsStream().map(HolonElement::getName).collect(Collectors.joining(", ")) + "]]";
+        return "[HolonObject: " + "id=" + getId() + ", name=" + name + ", state=" + state + ", pos=" + position+ ", elements=[" + elementsStream().map(HolonElement::getName).collect(Collectors.joining(", ")) + "]]";
     }
 
     public float getEnergyToHolon() {

+ 7 - 0
src/holeg/model/HolonSwitch.java

@@ -44,6 +44,7 @@ public class HolonSwitch extends AbstractCanvasObject implements TimelineDepende
         sampleGraph();
     }
 
+
     /**
      * Create a copy of an existing HolonSwitch.
      *
@@ -63,6 +64,12 @@ public class HolonSwitch extends AbstractCanvasObject implements TimelineDepende
         sampleGraph();
     }
 
+    @Override
+    public AbstractCanvasObject copy(){
+        return new HolonSwitch(this);
+    }
+
+
     @Override
     public String getImagePath() {
         return switch (state) {

+ 6 - 7
src/holeg/model/Node.java

@@ -8,13 +8,7 @@ package holeg.model;
  *
  */
 public class Node extends AbstractCanvasObject {
-	
-	/**
-	 * Create a new node in the system with an user-defined name.
-	 * 
-	 * @param objName
-	 *            String
-	 */
+
 	public Node(String objName) {
 		super(objName);
 	}
@@ -22,6 +16,11 @@ public class Node extends AbstractCanvasObject {
 	public Node(Node node){
 		super(node);
 	}
+
+	@Override
+	public AbstractCanvasObject copy(){
+		return new Node(this);
+	}
 	
 	public String toString(){
 		return "Node ID:" + getId();

+ 3 - 0
src/holeg/preferences/ImagePreference.java

@@ -48,6 +48,9 @@ public class ImagePreference {
 			public static final String Closed = "/images/buttons/closed.png";						
 			public static final String Leaf = "/images/buttons/leaf.png";						
 		}
+		public static class GroupNode{
+			public static final String Close = "/images/buttons/close_button.png";
+		}
 	}
 	public static class Category {
 		public static final String Folder = "/images/buttons/folder.png";

+ 27 - 20
src/holeg/ui/controller/CanvasController.java

@@ -3,6 +3,7 @@ package holeg.ui.controller;
 import holeg.model.*;
 import holeg.ui.model.GuiSettings;
 import holeg.model.Model;
+import holeg.utility.math.vector.Vec2f;
 import holeg.utility.math.vector.Vec2i;
 
 import java.util.Collection;
@@ -47,6 +48,7 @@ public class CanvasController {
     public void deleteObject(AbstractCanvasObject obj) {
         if(obj instanceof GroupNode groupNode) {
             deleteAllObjectsInGroupNode(groupNode);
+            control.OnRemoveGroupNode.broadcast(groupNode);
         }else{
             removeAllConnectionsFromObject(obj);
         }
@@ -95,24 +97,6 @@ public class CanvasController {
         control.getModel().getEdgesOnCanvas().remove(edge);
     }
 
-    /**
-     * Paste all Selected Objects.
-     */
-    public void pasteObjects(Vec2i p) {
-        //TODO(Tom2022-01-27): paste
-        GuiSettings.getSelectedObjects().clear();
-    }
-
-    /**
-     * Cut all Selected Objects.
-     */
-    public void cutObjects() {
-        GuiSettings.setClipboardObjects(new HashSet<>(GuiSettings.getSelectedObjects()));
-        for (AbstractCanvasObject cps : GuiSettings.getClipboardObjects()) {
-            deleteObject(cps);
-        }
-        GuiSettings.getSelectedObjects().clear();
-    }
 
 
     /**
@@ -129,7 +113,30 @@ public class CanvasController {
         control.getModel().getEdgesOnCanvas().removeIf(edge -> edge.getA() == obj || edge.getB() == obj);
     }
 
-    public void addGroupNode(GroupNode groupNode, Set<AbstractCanvasObject> toGroup) {
-        //TODO(Tom2022-01-27):
+    public void group(Set<AbstractCanvasObject> objects) {
+        if(objects.isEmpty()){
+            return;
+        }
+        final GroupNode groupNode = new GroupNode("GroupNode");
+        groupNode.setPosition(GroupNode.calculateMiddlePosition(objects));
+        objects.stream().findFirst().flatMap(AbstractCanvasObject::getGroupNode)
+                .ifPresent(parentGroupNode -> parentGroupNode.add(groupNode));
+        objects.forEach(object -> object.getGroupNode().ifPresent(old -> old.remove(object)));
+        groupNode.addAll(objects);
+
+    }
+
+
+    public void ungroup(Set<AbstractCanvasObject> objects) {
+        for(AbstractCanvasObject obj : objects){
+            if(obj instanceof GroupNode groupNode){
+                Vec2i middle = GroupNode.calculateMiddlePosition(groupNode.getObjectsInThisLayer().collect(Collectors.toList()));
+                groupNode.getObjectsInThisLayer().forEach(child -> {child.setPosition(groupNode.getPosition().subtract(middle).add(child.getPosition()));
+                    child.getPosition().clampX(0, GuiSettings.canvasSize.getX());
+                    child.getPosition().clampY(0, GuiSettings.canvasSize.getY());});
+                groupNode.ungroup();
+                control.OnRemoveGroupNode.broadcast(groupNode);
+            }
+        }
     }
 }

+ 75 - 62
src/holeg/ui/controller/Control.java

@@ -4,8 +4,8 @@ import holeg.model.*;
 import holeg.preferences.ImagePreference;
 import holeg.ui.model.GuiSettings;
 import holeg.ui.model.IdCounter;
-import holeg.ui.view.dialog.CreateTemplatePopUp;
 import holeg.ui.view.category.Category;
+import holeg.ui.view.dialog.CreateTemplatePopUp;
 import holeg.utility.events.Action;
 import holeg.utility.events.Event;
 import holeg.utility.math.vector.Vec2i;
@@ -17,6 +17,7 @@ import java.io.FileWriter;
 import java.io.IOException;
 import java.util.*;
 import java.util.logging.Logger;
+import java.util.stream.Collectors;
 
 import static holeg.serialize.ModelDeserializer.gson;
 
@@ -27,12 +28,15 @@ import static holeg.serialize.ModelDeserializer.gson;
 public class Control {
     private static final Logger log = Logger.getLogger(Control.class.getName());
     private final CanvasController canvasController;
-    private Model model;
     private final SimulationManager simulationManager;
     public Event OnCategoryChanged = new Event();
     public Event OnSelectionChanged = new Event();
     public Event OnCanvasUpdate = new Event();
     public Action<Boolean> OnGuiSetEnabled = new Action<>();
+    public Action<GroupNode> OnShowGroupNode = new Action<>();
+    public Action<GroupNode> OnRemoveGroupNode = new Action<>();
+    public Event OnModelChanged = new Event();
+    private Model model;
 
     public Control(Model model) {
         this.model = model;
@@ -118,6 +122,12 @@ public class Control {
 
     }
 
+
+    public void setSelection(Collection<AbstractCanvasObject> objects) {
+        GuiSettings.getSelectedObjects().clear();
+        addSelectedObjects(objects);
+    }
+
     public void clearSelection() {
         if (!GuiSettings.getSelectedObjects().isEmpty()) {
             GuiSettings.getSelectedObjects().clear();
@@ -125,37 +135,20 @@ public class Control {
         }
     }
 
-    /**
-     * This method is primarily for the multi-selection. It adds unselected objects
-     * to the selection and Removes selected objects from the selection. Like the
-     * normal OS Desktop selection.
-     *
-     * @param objects
-     */
-    public void toggleSelectedObjects(Collection<AbstractCanvasObject> objects) {
-        Set<AbstractCanvasObject> intersection = new HashSet<>(objects);
-        intersection.retainAll(GuiSettings.getSelectedObjects());
-        GuiSettings.getSelectedObjects().addAll(objects);
-        GuiSettings.getSelectedObjects().removeAll(intersection);
-        OnSelectionChanged.broadcast();
-    }
-
-    /* Operations for Canvas */
-
-    /**
-     * Add a new Object.
-     *
-     * @param object the Object
-     */
     public void addObjectOnCanvas(GroupNode node, AbstractCanvasObject object) {
         canvasController.addObject(node, object);
         calculateStateAndVisualForTimeStep(model.getCurrentIteration());
     }
 
+
+    public void addCopyOnCanvas(GroupNode node, AbstractCanvasObject object) {
+        addObjectOnCanvas(node, object.copy());
+    }
+
     /**
      * Deletes an CpsObject on the Canvas and its connections.
      *
-     * @param obj  AbstractCpsObject
+     * @param obj AbstractCpsObject
      */
     public void deleteCanvasObject(AbstractCanvasObject obj) {
         canvasController.deleteObject(obj);
@@ -262,7 +255,6 @@ public class Control {
     }
 
 
-
     public void replaceObject(AbstractCanvasObject toBeReplaced, AbstractCanvasObject by) {
         canvasController.replaceObject(toBeReplaced, by);
     }
@@ -270,26 +262,32 @@ public class Control {
     /**
      * Copy all Selected Objects.
      */
-    public void copy(GroupNode upperNode) {
-        //TODO(Tom2022-01-27):
+    public void copy() {
+        GuiSettings.getClipboardObjects().clear();
+        GuiSettings.getClipboardObjects().addAll(GuiSettings.getSelectedObjects());
     }
 
-    public void paste(GroupNode upperNode, Vec2i point) {
-        //TODO(Tom2022-01-27):
+    public void paste(GroupNode groupNode, Vec2i offset) {
+        if (GuiSettings.getClipboardObjects().isEmpty()) {
+            return;
+        }
+        Set<AbstractCanvasObject> copies = GuiSettings.getClipboardObjects().stream().map(AbstractCanvasObject::copy).collect(Collectors.toSet());
+        copies.forEach(obj -> {
+            obj.getPosition().addAssign(offset);
+            obj.getPosition().clampX(0, GuiSettings.canvasSize.getX());
+            obj.getPosition().clampY(0, GuiSettings.canvasSize.getY());
+        });
+        groupNode.addAll(copies);
+        calculateStateAndVisualForTimeStep(model.getCurrentIteration());
         OnSelectionChanged.broadcast();
     }
 
-    public void cut(GroupNode upperNode) {
-        //TODO(Tom2022-01-27):
-        OnSelectionChanged.broadcast();
+    public void cut() {
+        copy();
+        deleteCanvasObjects(GuiSettings.getSelectedObjects());
+        clearSelection();
     }
 
-    /**
-     * creates a new Template for the given cps Object
-     *
-     * @param cps         Object, which should become a template
-     * @param parentFrame
-     */
     public void createTemplate(HolonObject cps, JFrame parentFrame) {
         CreateTemplatePopUp t = new CreateTemplatePopUp(cps, model, parentFrame, this);
         t.setVisible(true);
@@ -302,8 +300,6 @@ public class Control {
     }
 
 
-
-
     /**
      * init default category and objects.
      */
@@ -338,12 +334,12 @@ public class Control {
 
     public Category createCategoryWithName(String categoryName) {
         Optional<Category> category = findCategoryWithName(categoryName);
-        if(category.isEmpty()) {
+        if (category.isEmpty()) {
             Category cat = new Category(categoryName);
             GuiSettings.getCategories().add(cat);
             OnCategoryChanged.broadcast();
             return cat;
-        }else {
+        } else {
             return category.get();
         }
 
@@ -353,8 +349,7 @@ public class Control {
     /**
      * remove a Category from Model.
      *
-     * @param c
-     *            Category
+     * @param c Category
      */
     public void removeCategory(Category c) {
         GuiSettings.getCategories().remove(c);
@@ -368,10 +363,8 @@ public class Control {
     /**
      * Add Object into a Category.
      *
-     * @param category
-     *            Category
-     * @param object
-     *            Object
+     * @param category Category
+     * @param object   Object
      */
     public void addObject(Category category, AbstractCanvasObject object) {
         int i = 0;
@@ -388,14 +381,10 @@ public class Control {
     /**
      * Add new Holon Object to a Category.
      *
-     * @param category
-     *            Category
-     * @param object
-     *            New Object Name
-     * @param list
-     *            Array of Elements
-     * @param image
-     *            the image Path
+     * @param category Category
+     * @param object   New Object Name
+     * @param list     Array of Elements
+     * @param image    the image Path
      */
     public HolonObject addNewHolonObject(Category category, String object, List<HolonElement> list, String image) {
         HolonObject obj = new HolonObject(object);
@@ -414,11 +403,11 @@ public class Control {
     }
 
 
-
     /**
      * Removes an Object from a Category.
+     *
      * @param category Category
-     * @param cps the Object
+     * @param cps      the Object
      */
     public void removeObject(Category category, AbstractCanvasObject cps) {
         category.getObjects().remove(cps);
@@ -426,7 +415,6 @@ public class Control {
     }
 
 
-
     public Optional<Category> findCategoryWithName(String categoryName) {
         return GuiSettings.getCategories().stream().filter(cat -> cat.getName().equals(categoryName)).findAny();
     }
@@ -439,6 +427,7 @@ public class Control {
             reader.close();
             this.model = model;
             calculateStateAndVisualForCurrentTimeStep();
+            OnModelChanged.broadcast();
         } catch (IOException e) {
             log.warning(e.getLocalizedMessage());
         }
@@ -447,10 +436,19 @@ public class Control {
     }
 
 
+    public void toggleSelectedObjects(Collection<AbstractCanvasObject> objects) {
+        Set<AbstractCanvasObject> intersection = new HashSet<>(objects);
+        intersection.retainAll(GuiSettings.getSelectedObjects());
+        GuiSettings.getSelectedObjects().addAll(objects);
+        GuiSettings.getSelectedObjects().removeAll(intersection);
+        OnSelectionChanged.broadcast();
+    }
+
+
     public void saveFile(File file) {
         log.info("save" + file);
         try {
-            FileWriter  writer = new FileWriter(file);
+            FileWriter writer = new FileWriter(file);
             gson.toJson(model, writer);
             writer.close();
         } catch (IOException e) {
@@ -459,8 +457,17 @@ public class Control {
         GuiSettings.setActualSaveFile(file);
     }
 
+    public void group() {
+        canvasController.group(GuiSettings.getSelectedObjects());
+        clearSelection();
+        OnCanvasUpdate.broadcast();
+    }
 
-
+    public void ungroup() {
+        canvasController.ungroup(GuiSettings.getSelectedObjects());
+        clearSelection();
+        OnCanvasUpdate.broadcast();
+    }
 
     public void clearModel() {
         model.clear();
@@ -468,6 +475,12 @@ public class Control {
         model.setCurrentIteration(0);
         IdCounter.reset();
         calculateStateAndVisualForCurrentTimeStep();
+        OnModelChanged.broadcast();
         GuiSettings.setActualSaveFile(null);
     }
+
+    public void showGroupNode(GroupNode groupNode) {
+        OnShowGroupNode.broadcast(groupNode);
+        clearSelection();
+    }
 }

+ 4 - 11
src/holeg/ui/model/GuiSettings.java

@@ -22,10 +22,10 @@ public class GuiSettings {
     public static float maxCapacityForNewCreatedEdges = 10000;
     
     
-    private static Set<Edge> selectedEdges = new HashSet<>();
-    private static Set<Category> categories = new HashSet<>();
-    private static Set<AbstractCanvasObject> clipboardObjects = new HashSet<>();
-	private static Set<AbstractCanvasObject> selectedObjects = new HashSet<>();
+    private static final Set<Edge> selectedEdges = new HashSet<>();
+    private static final Set<Category> categories = new HashSet<>();
+    private static final Set<AbstractCanvasObject> clipboardObjects = new HashSet<>();
+	private static final Set<AbstractCanvasObject> selectedObjects = new HashSet<>();
     
 	public static int autoSaveNr = -1;
     public static int numberOfSaves = 35;
@@ -63,14 +63,7 @@ public class GuiSettings {
 	public static Set<AbstractCanvasObject> getClipboardObjects() {
 		return clipboardObjects;
 	}
-	public static void setClipboardObjects(Set<AbstractCanvasObject> clipboardObjects) {
-		GuiSettings.clipboardObjects = clipboardObjects;
-	}
 	public static Set<AbstractCanvasObject> getSelectedObjects() {
 		return selectedObjects;
 	}
-	public static void setSelectedObjects(Set<AbstractCanvasObject> selectedObjects) {
-		GuiSettings.selectedObjects = selectedObjects;
-	}
-
 }

+ 7 - 5
src/holeg/ui/view/canvas/Canvas.java

@@ -15,6 +15,7 @@ import java.util.HashSet;
 import java.util.Optional;
 import java.util.Set;
 import java.util.logging.Logger;
+import java.util.stream.Collectors;
 
 public class Canvas extends JPanel {
     private static final Logger log = Logger.getLogger(Canvas.class.getName());
@@ -59,7 +60,6 @@ public class Canvas extends JPanel {
     @Override
     public void paintComponent(java.awt.Graphics g) {
         super.paintComponent(g);
-        log.info("Draw");
         Graphics2D g2d = Rendering.initGraphics2D(g);
         Rendering.drawSelection(g2d);
         control.getModel().getEdgesOnCanvas().forEach(edge -> {
@@ -113,7 +113,7 @@ public class Canvas extends JPanel {
             if (!enabled) {
                 return;
             }
-            log.info(state.toString());
+            log.finest(state.toString());
             if (!e.isControlDown()) {
                 GuiSettings.getSelectedObjects().clear();
             }
@@ -137,7 +137,7 @@ public class Canvas extends JPanel {
             if (!enabled) {
                 return;
             }
-            log.info(state.toString());
+            log.finest(state.toString());
             Vec2i actualPos = new Vec2i(e.getPoint());
             switch (state) {
                 case None, Selection -> {
@@ -178,7 +178,7 @@ public class Canvas extends JPanel {
             if (!enabled) {
                 return;
             }
-            log.info(state.toString());
+            log.finest(state.toString());
             switch (state) {
                 case None -> {
                     if (GuiSettings.getSelectedObjects().contains(selectedOnPressed)) {
@@ -209,12 +209,14 @@ public class Canvas extends JPanel {
         public void mouseClicked(MouseEvent e) {
             boolean doubleLeftClick = e.getClickCount() % 2 == 0 && SwingUtilities.isLeftMouseButton(e);
             if (doubleLeftClick) {
-                log.info(state.toString());
+                log.finest(state.toString());
                 getObjectAtPosition(new Vec2i(e.getPoint())).ifPresent(obj -> {
                     if (obj instanceof HolonSwitch sw) {
                         sw.setMode(HolonSwitch.SwitchMode.Manual);
                         sw.flipManualState();
                         control.calculateStateAndVisualForCurrentTimeStep();
+                    } else if(obj instanceof GroupNode gNode){
+                        control.showGroupNode(gNode);
                     }
                 });
             }

+ 71 - 18
src/holeg/ui/view/canvas/CanvasCollectionPanel.java

@@ -1,45 +1,98 @@
 package holeg.ui.view.canvas;
 
 import holeg.model.GroupNode;
+import holeg.preferences.ImagePreference;
 import holeg.ui.controller.Control;
+import holeg.ui.view.image.Import;
 
 import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import java.awt.*;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 
 public class CanvasCollectionPanel extends JTabbedPane {
     private final Control control;
-    private final Map<JScrollPane, Canvas> canvases= new HashMap<>();
-    private final Map<GroupNode, JScrollPane> canvases2= new HashMap<>();
+    private final Map<Component, Canvas> componentCanvasMap = new HashMap<>();
+    private final Map<GroupNode, Component> groupNodeComponentMap = new HashMap<>();
     private Canvas main;
+    private Canvas actual;
 
-    public CanvasCollectionPanel(Control control){
+    public CanvasCollectionPanel(Control control) {
         this.control = control;
+        control.OnShowGroupNode.addListener(this::showGroupNode);
+        control.OnRemoveGroupNode.addListener(this::removeGroupNode);
+        control.OnModelChanged.addListener(this::reset);
         createMainCanvas();
+        this.addChangeListener(this::tabChangeEvent);
     }
 
-    private void createMainCanvas(){
+    private void reset() {
+        removeAll();
+        componentCanvasMap.clear();
+        groupNodeComponentMap.clear();
+        createMainCanvas();
+    }
+
+    private void tabChangeEvent(ChangeEvent changeEvent) {
+        Optional.ofNullable(componentCanvasMap.get(this.getSelectedComponent())).ifPresentOrElse(canvas -> {
+            actual = canvas;
+        }, () -> actual = main);
+        control.clearSelection();
+    }
+
+    private void createMainCanvas() {
         GroupNode mainGroupNode = control.getModel().getCanvas();
         Canvas canvas = new Canvas(control, mainGroupNode);
-        main = canvas;
+        actual = main = canvas;
         final JScrollPane scrollPane = new JScrollPane(canvas);
-        //        scrollPane.addComponentListener(new ComponentAdapter() {
-//            @Override
-//            public void componentResized(ComponentEvent e) {
-//                GuiSettings.canvasSize.setX(Math.max(GuiSettings.canvasSize.getX(), canvasSP.getViewport().getWidth()));
-//                GuiSettings.canvasSize
-//                        .setY(Math.max(GuiSettings.canvasSize.getY(), canvasSP.getViewport().getHeight()));
-//                log.info("canvas.repaint11");
-//                canvas.repaint();
-//            }
-//        });
-        canvases.put(scrollPane, canvas);
-        canvases2.put(mainGroupNode, scrollPane);
+        groupNodeComponentMap.put(mainGroupNode, scrollPane);
+        componentCanvasMap.put(scrollPane, canvas);
         this.addTab("Main", scrollPane);
     }
 
+    private void showGroupNode(GroupNode groupNode) {
+        Optional.ofNullable(groupNodeComponentMap.get(groupNode))
+                .ifPresentOrElse(this::setSelectedComponent,
+                        () -> createNewCanvas(groupNode));
+    }
+
+    private void createNewCanvas(GroupNode groupNode) {
+        Canvas canvas = new Canvas(control, groupNode);
+        final JScrollPane scrollPane = new JScrollPane(canvas);
+        groupNodeComponentMap.put(groupNode, scrollPane);
+        componentCanvasMap.put(scrollPane, canvas);
+        addTab(groupNode.getName(), scrollPane);
+        setSelectedComponent(scrollPane);
+        setTabComponentAt(this.indexOfComponent(scrollPane), new GroupNodeHeader(groupNode));
+    }
+
+    private void removeGroupNode(GroupNode groupNode){
+        Optional.ofNullable(groupNodeComponentMap.get(groupNode))
+                .ifPresent(scrollPane -> {
+                    remove(scrollPane);
+                    groupNodeComponentMap.remove(groupNode);
+                });
+    }
 
     public Canvas getActualCanvas() {
-        return main;
+        return actual;
+    }
+
+    private class GroupNodeHeader extends JPanel {
+        public GroupNodeHeader(GroupNode groupNode) {
+            JLabel label = new JLabel(groupNode.getName());
+            add(label);
+            label.setBorder(null);
+            label.setPreferredSize(new Dimension(label.getPreferredSize().width,8));
+            this.setBorder(null);
+            this.setOpaque(false);
+            JButton button = new JButton(new ImageIcon(Import.loadImage(ImagePreference.Button.GroupNode.Close, 8,8)));
+            button.setPreferredSize(new Dimension(8, 8));
+            button.setBorder(null);
+            add(button);
+            button.addActionListener(clicked -> removeGroupNode(groupNode));
+        }
     }
 }

+ 2 - 1
src/holeg/ui/view/category/CategoryPanel.java

@@ -61,9 +61,10 @@ public class CategoryPanel extends JScrollPane {
         CategoryMouseListener mouseListener = new CategoryMouseListener();
         categoryTree.addMouseListener(mouseListener);
         categoryTree.addMouseMotionListener(mouseListener);
-
         setColumnHeaderView(buttonPanel);
         setViewportView(categoryTree);
+        categoryTree.setFocusable(false);
+        this.setFocusable(false);
     }
 
     private void initContextHandle() {

+ 82 - 19
src/holeg/ui/view/main/Gui.java

@@ -1,6 +1,8 @@
 package holeg.ui.view.main;
 
-import holeg.model.*;
+import holeg.model.AbstractCanvasObject;
+import holeg.model.GroupNode;
+import holeg.model.Model;
 import holeg.preferences.ImagePreference;
 import holeg.preferences.PreferenceKeys;
 import holeg.ui.controller.Control;
@@ -8,7 +10,9 @@ import holeg.ui.model.GuiSettings;
 import holeg.ui.view.canvas.Canvas;
 import holeg.ui.view.canvas.CanvasCollectionPanel;
 import holeg.ui.view.category.CategoryPanel;
-import holeg.ui.view.dialog.*;
+import holeg.ui.view.dialog.AboutUsPopUp;
+import holeg.ui.view.dialog.CanvasResizePopUp;
+import holeg.ui.view.dialog.EditEdgesPopUp;
 import holeg.ui.view.image.Import;
 import holeg.ui.view.information.HolonInformationPanel;
 import holeg.ui.view.inspector.Inspector;
@@ -16,18 +20,24 @@ import holeg.ui.view.window.AddOnWindow;
 import holeg.ui.view.window.FlexWindow;
 import holeg.ui.view.window.Outliner;
 import holeg.utility.listener.WindowClosingListener;
+import holeg.utility.math.vector.Vec2i;
 
 import javax.swing.*;
 import javax.swing.filechooser.FileNameExtensionFilter;
 import javax.swing.filechooser.FileSystemView;
 import java.awt.*;
-import java.awt.event.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
 import java.io.File;
 import java.net.URI;
+import java.util.Optional;
+import java.util.Set;
 import java.util.logging.Logger;
 import java.util.prefs.Preferences;
+import java.util.stream.Collectors;
 
-public class Gui extends JFrame{
+public class Gui extends JFrame {
     private static final Logger log = Logger.getLogger(Model.class.getName());
     private static final Preferences prefs = Preferences.userNodeForPackage(Gui.class);
     private final Control control;
@@ -40,6 +50,7 @@ public class Gui extends JFrame{
 
     /**
      * Create the application.
+     *
      * @param control the Controller
      */
     public Gui(Control control) {
@@ -61,9 +72,9 @@ public class Gui extends JFrame{
     private void initFrame() {
         this.setIconImage(Import.loadImage(ImagePreference.Logo, 30, 30));
         this.setBounds(new Rectangle(prefs.getInt(PreferenceKeys.Gui.Width, 1200), prefs.getInt(PreferenceKeys.Gui.Height, 800)));
-        if(prefs.get(PreferenceKeys.Gui.Width, null) != null){
+        if (prefs.get(PreferenceKeys.Gui.Width, null) != null) {
             this.setLocation(prefs.getInt(PreferenceKeys.Gui.XPos, 1200), prefs.getInt(PreferenceKeys.Gui.YPos, 800));
-        }else{
+        } else {
             this.setLocationRelativeTo(null);
         }
         this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
@@ -81,13 +92,15 @@ public class Gui extends JFrame{
     }
 
     private void initLayout() {
+        this.setJMenuBar(new GuiMenuBar());
+
         final JSplitPane categorySplit = new JSplitPane();
         final JSplitPane canvasSplit = new JSplitPane();
         final JSplitPane elementSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
         final JScrollPane informationPanelScrollPane = new JScrollPane();
-        this.setJMenuBar(new GuiMenuBar());
-        this.getContentPane().setLayout(new BorderLayout(0, 0));
-        this.getContentPane().add(categorySplit);
+        Container contentPanel = this.getContentPane();
+        contentPanel.setLayout(new BorderLayout(0, 0));
+        contentPanel.add(categorySplit);
         categorySplit.setLeftComponent(categoryPanel);
         categorySplit.setRightComponent(canvasSplit);
         categorySplit.setDividerLocation(200);
@@ -110,7 +123,7 @@ public class Gui extends JFrame{
         informationPanelScrollPane.getVerticalScrollBar().setUnitIncrement(16);
         informationPanelScrollPane.setBorder(null);
 
-        this.getContentPane().add(timePanel, BorderLayout.SOUTH);
+        contentPanel.add(timePanel, BorderLayout.SOUTH);
     }
 
     public Canvas getActualCanvas() {
@@ -141,6 +154,10 @@ public class Gui extends JFrame{
         private final JMenuItem selectAllButton = new JMenuItem("All");
         private final JMenuItem clearSelectionButton = new JMenuItem("Clear");
         private final JMenuItem invertSelectionButton = new JMenuItem("Invert");
+        private final JMenuItem groupButton = new JMenuItem("Group");
+        private final JMenuItem ungroupButton = new JMenuItem("Ungroup");
+        private final JMenuItem removeButton = new JMenuItem("Remove");
+
 
         private final JMenuItem edgePropertiesButton = new JMenuItem("Edge Properties");
         private final JMenuItem alignAllButton = new JMenuItem("Align All");
@@ -161,7 +178,7 @@ public class Gui extends JFrame{
         private final JCheckBoxMenuItem showSupplyBarsCheckBox = new JCheckBoxMenuItem("Show supply bars.", true);
         private final JFileChooser fileChooser = initFileChooser();
 
-        private final int IconSize = 15;
+        private final static int IconSize = 15;
         //WindowMenu
         JMenuItem algorithmButton = new JMenuItem("Algorithm Panel", new ImageIcon(Import
                 .loadImage(ImagePreference.Button.Menu.Algo).getScaledInstance(IconSize, IconSize, java.awt.Image.SCALE_SMOOTH)));
@@ -170,6 +187,8 @@ public class Gui extends JFrame{
         JMenuItem flexMenuButton = new JMenuItem("Flexibility Panel", new ImageIcon(Import
                 .loadImage(ImagePreference.Button.Menu.Algo).getScaledInstance(IconSize, IconSize, java.awt.Image.SCALE_SMOOTH)));
 
+        private final static Vec2i DefaultOffset = new Vec2i(20,20);
+
         GuiMenuBar() {
             initMenuLayout();
             initButtonActions();
@@ -209,6 +228,11 @@ public class Gui extends JFrame{
             selectionMenu.add(clearSelectionButton);
             selectionMenu.add(invertSelectionButton);
             editMenu.addSeparator();
+            editMenu.add(groupButton);
+            editMenu.add(ungroupButton);
+            editMenu.addSeparator();
+            editMenu.add(removeButton);
+            editMenu.addSeparator();
             editMenu.add(edgePropertiesButton);
             editMenu.add(alignAllButton);
             editMenu.add(resetMenu);
@@ -235,6 +259,9 @@ public class Gui extends JFrame{
             openMenuButton.addActionListener(clicked -> openFile());
             saveMenuButton.addActionListener(clicked -> saveFile());
             saveAsMenuButton.addActionListener(clicked -> saveNewFile());
+            groupButton.addActionListener(clicked -> control.group());
+            ungroupButton.addActionListener(clicked -> control.ungroup());
+
             edgePropertiesButton.addActionListener(actionEvent -> new EditEdgesPopUp(Gui.this, control));
             alignAllButton.addActionListener(clicked -> {
                 //TODO(Tom2022-01-14): recreateTryToAlignObjects
@@ -248,6 +275,15 @@ public class Gui extends JFrame{
             outlinerButton.addActionListener(clicked -> new Outliner(Gui.this, control));
             flexMenuButton.addActionListener(clicked -> new FlexWindow(Gui.this, control));
 
+            selectAllButton.addActionListener(clicked -> selectAll());
+            clearSelectionButton.addActionListener(clicked -> control.clearSelection());
+            invertSelectionButton.addActionListener(clicked -> invertSelection());
+
+            copyButton.addActionListener(clicked -> control.copy());
+            cutButton.addActionListener(clicked -> control.cut());
+            pasteButton.addActionListener(clicked -> paste());
+
+            removeButton.addActionListener(clicked -> removeSelectedObjects());
 
             String tkWikiWebpage = "https://git.tk.informatik.tu-darmstadt.de/carlos.garcia/praktikum-holons/wiki/";
             introductionButton.addActionListener(clicked -> openWebpage(tkWikiWebpage + "Introduction+V2.1"));
@@ -257,6 +293,7 @@ public class Gui extends JFrame{
             aboutUsButton.addActionListener(clicked -> new AboutUsPopUp(Gui.this));
         }
 
+
         private void openWebpage(String URL) {
             try {
                 java.awt.Desktop.getDesktop().browse(new URI(URL));
@@ -267,8 +304,9 @@ public class Gui extends JFrame{
 
         private void initButtonShortCuts() {
             int defaultModifier = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
+            int defaultShiftModifier = defaultModifier + InputEvent.SHIFT_DOWN_MASK;
             saveMenuButton.setAccelerator(KeyStroke.getKeyStroke('S', defaultModifier));
-            saveAsMenuButton.setAccelerator(KeyStroke.getKeyStroke('S', defaultModifier + InputEvent.SHIFT_DOWN_MASK));
+            saveAsMenuButton.setAccelerator(KeyStroke.getKeyStroke('S', defaultShiftModifier));
             openMenuButton.setAccelerator(KeyStroke.getKeyStroke('O', defaultModifier));
             newMenuButton.setAccelerator(KeyStroke.getKeyStroke('N', defaultModifier));
             undoButton.setAccelerator(KeyStroke.getKeyStroke('Z', defaultModifier));
@@ -277,21 +315,25 @@ public class Gui extends JFrame{
             pasteButton.setAccelerator(KeyStroke.getKeyStroke('V', defaultModifier));
             cutButton.setAccelerator(KeyStroke.getKeyStroke('X', defaultModifier));
             selectAllButton.setAccelerator(KeyStroke.getKeyStroke('A', defaultModifier));
-            clearSelectionButton.setAccelerator(KeyStroke.getKeyStroke('A', defaultModifier + InputEvent.SHIFT_DOWN_MASK));
+            clearSelectionButton.setAccelerator(KeyStroke.getKeyStroke('A', defaultShiftModifier));
             invertSelectionButton.setAccelerator(KeyStroke.getKeyStroke('I', defaultModifier));
+            groupButton.setAccelerator(KeyStroke.getKeyStroke('G', defaultModifier));
+            ungroupButton.setAccelerator(KeyStroke.getKeyStroke('U', defaultModifier));
+            removeButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
         }
 
+
+
         private void toggleSupplyBarAppearance() {
             GuiSettings.showSupplyBars = showSupplyBarsCheckBox.isSelected();
-            log.info("canvas.repaint4");
         }
 
-        private void saveFile(){
+        private void saveFile() {
             GuiSettings.getActualSaveFile().ifPresentOrElse(control::saveFile, this::saveNewFile);
         }
 
         private void saveNewFile() {
-            if (fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
+            if (fileChooser.showSaveDialog(Gui.this) == JFileChooser.APPROVE_OPTION) {
                 String path = fileChooser.getSelectedFile().getPath();
                 if (!path.endsWith(".json")) {
                     path += ".json";
@@ -302,16 +344,15 @@ public class Gui extends JFrame{
         }
 
         private void openFile() {
-            if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
+            if (fileChooser.showOpenDialog(Gui.this) == JFileChooser.APPROVE_OPTION) {
                 prefs.put(PreferenceKeys.Gui.DefaultFolder, fileChooser.getCurrentDirectory().getPath());
                 control.loadFile(fileChooser.getSelectedFile());
-                //TODO(Tom2022-01-27): make better
             }
         }
 
         private void newFile() {
             if (control.getModel().getCanvas().getObjectsInThisLayer().findAny().isPresent()) {
-                int selectedOption = JOptionPane.showConfirmDialog(this, "Do you want to save your current model?",
+                int selectedOption = JOptionPane.showConfirmDialog(Gui.this, "Do you want to save your current model?",
                         "Warning", JOptionPane.YES_NO_OPTION);
                 if (selectedOption == JOptionPane.YES_OPTION) {
                     saveNewFile();
@@ -319,5 +360,27 @@ public class Gui extends JFrame{
             }
             control.clearModel();
         }
+
+        private void selectAll(){
+            control.setSelection(getActualCanvas().getGroupNode()
+                    .getObjectsInThisLayer().collect(Collectors.toSet()));
+        }
+
+        private void invertSelection() {
+            control.toggleSelectedObjects(getActualCanvas().getGroupNode()
+                    .getObjectsInThisLayer().collect(Collectors.toSet()));
+        }
+        private void removeSelectedObjects() {
+            control.deleteCanvasObjects(GuiSettings.getSelectedObjects());
+            control.clearSelection();
+        }
+
+        private void paste() {
+            Vec2i middlePosition = GroupNode.calculateMiddlePosition(GuiSettings.getClipboardObjects());
+            Optional<Vec2i> offset = Optional.ofNullable(getActualCanvas().getMousePosition())
+                    .map(point -> new Vec2i(point).subtract(middlePosition));
+            control.paste(getActualCanvas().getGroupNode(), offset.orElse(DefaultOffset));
+        }
+
     }
 }

+ 19 - 4
src/holeg/utility/math/vector/Vec2f.java

@@ -84,6 +84,11 @@ public class Vec2f {
 		result.setY(y + other.getY());
 		return result;
 	}
+	public void addAssign(Vec2f other) {
+		x += other.getX();
+		y += other.getY();
+	}
+
 
 	public Vec2f subtract(Vec2f other)
 	{
@@ -92,6 +97,10 @@ public class Vec2f {
 		result.setY(y - other.getY());
 		return result;
 	}
+	public void subtractAssign(Vec2f other) {
+		x -= other.getX();
+		y -= other.getY();
+	}
 
 	public Vec2f multiply(float scaleFactor)
 	{
@@ -100,12 +109,18 @@ public class Vec2f {
 		result.setY(y * scaleFactor);
 		return result;
 	}
+	public void multiplyAssign(float scaleFactor)
+	{
+		x *= scaleFactor;
+		y *= scaleFactor;
+	}
 	public Vec2f divide(float divideFactor)
 	{
-		Vec2f result = new Vec2f();
-		result.setX(x / divideFactor);
-		result.setY(y / divideFactor);
-		return result;
+		return this.multiply(1.0f/divideFactor);
+	}
+	public void divideAssign(float divideFactor)
+	{
+		multiplyAssign(1.0f/divideFactor);
 	}
 
 	public Vec2f normalize()