package holeg.ui.controller; import static holeg.serialize.ModelDeserializer.gson; import holeg.model.AbstractCanvasObject; import holeg.model.Edge; import holeg.model.GroupNode; import holeg.model.HolonElement; import holeg.model.HolonObject; import holeg.model.HolonSwitch; import holeg.model.Model; import holeg.preferences.ImagePreference; import holeg.preferences.PreferenceKeys; import holeg.serialize.CategoryAdapter; import holeg.ui.model.GuiSettings; import holeg.ui.model.IdCounter; import holeg.ui.model.UndoHistory; import holeg.ui.view.category.Category; import holeg.utility.events.Action; import holeg.utility.events.Event; import holeg.utility.math.vector.Vec2i; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.logging.Logger; import java.util.prefs.Preferences; /** * The Class represents the controller. */ public class Control { private static final Logger log = Logger.getLogger(Control.class.getName()); private static final Preferences prefs = Preferences.userNodeForPackage(Control.class); private final CanvasController canvasController; private final SimulationManager simulationManager; public Event OnCategoryChanged = new Event(); public Event OnSelectionChanged = new Event(); public Event OnCanvasUpdate = new Event(); public Action OnGuiSetEnabled = new Action<>(); public Action OnShowGroupNode = new Action<>(); public Action OnRemoveGroupNode = new Action<>(); public Event OnModelChanged = new Event(); private Model model; public Control(Model model) { this.model = model; this.canvasController = new CanvasController(this); this.simulationManager = new SimulationManager(this); OnCanvasUpdate.addListener(() -> UndoHistory.addSave(saveModelToJsonString())); UndoHistory.addSave(saveModelToJsonString()); } /* Operations for Categories and Objects */ /** * init default category and objects. */ public void resetCategories() { GuiSettings.getCategories().clear(); initCategories(); saveCategories(); } /** * Add new Holon Object to a Category. */ public void addObject(Category cat, String obj, List list, String img) { addNewHolonObject(cat, obj, list, img); OnCategoryChanged.broadcast(); saveCategories(); } public void deleteCategory(Category category) { GuiSettings.getCategories().remove(category); OnCategoryChanged.broadcast(); saveCategories(); } /** * removes a selectedObject from selection. */ public void removeObjectsFromSelection(Collection objects) { if (GuiSettings.getSelectedObjects().removeAll(objects)) { OnSelectionChanged.broadcast(); } } /** * removes a selectedObject from selection. * * @param obj */ public void removeObjectFromSelection(AbstractCanvasObject obj) { if (GuiSettings.getSelectedObjects().remove(obj)) { OnSelectionChanged.broadcast(); } } public void addObjectToSelection(AbstractCanvasObject obj) { if (GuiSettings.getSelectedObjects().add(obj)) { OnSelectionChanged.broadcast(); } } public void addObjectsToSelection(Collection objects) { if (GuiSettings.getSelectedObjects().addAll(objects)) { OnSelectionChanged.broadcast(); } } public void setSelection(Collection objects) { GuiSettings.getSelectedObjects().clear(); addObjectsToSelection(objects); } public void clearSelection() { if (!GuiSettings.getSelectedObjects().isEmpty()) { GuiSettings.getSelectedObjects().clear(); OnSelectionChanged.broadcast(); } } public void addObjectOnCanvas(GroupNode node, AbstractCanvasObject object) { canvasController.addObject(node, object); updateStateForIteration(model.getCurrentIteration()); } /** * Deletes an CpsObject on the Canvas and its connections. * * @param obj AbstractCpsObject */ public void deleteObject(AbstractCanvasObject obj) { canvasController.deleteObject(obj); if (obj instanceof GroupNode groupnode) { canvasController.deleteAllObjectsInGroupNode(groupnode); } calculateStateForCurrentIteration(); OnCanvasUpdate.broadcast(); } public void deleteCanvasObjects(Collection objects) { canvasController.deleteObjects(objects); calculateStateForCurrentIteration(); OnCanvasUpdate.broadcast(); } /** * Replaces {@code toBeReplaced} by {@code by} on the canvas * * @param toBeReplaced the object that will be replaced * @param by the object that will replace it */ public void replaceCanvasObject(AbstractCanvasObject toBeReplaced, AbstractCanvasObject by) { canvasController.replaceObject(toBeReplaced, by); OnCanvasUpdate.broadcast(); } /** * Add an edge to the Canvas. * * @param edge the edge */ public boolean addEdgeOnCanvas(Edge edge) { if (doesEdgeExist(edge)) { return false; } canvasController.addEdgeOnCanvas(edge); OnCanvasUpdate.broadcast(); return true; } public boolean doesEdgeExist(Edge edge) { boolean connectsToSelf = edge.getA() == edge.getB(); return !connectsToSelf && model.getEdgesOnCanvas().stream() .anyMatch(e -> (e.getA() == edge.getA() && e.getB() == edge.getB()) || (e.getB() == edge.getA() && e.getA() == edge.getB())); } /** * Add an edge to the Canvas. * * @param edge the edge */ public void addEdgeOnCanvasOrRemoveExisting(Edge edge) { boolean connectsToSelf = edge.getA() == edge.getB(); if (connectsToSelf) { return; } Optional edgeToRemove = model.getEdgesOnCanvas().stream() .filter(e -> (e.getA() == edge.getA() && e.getB() == edge.getB()) || (e.getB() == edge.getA() && e.getA() == edge.getB())).findAny(); edgeToRemove.ifPresentOrElse(canvasController::removeEdgesOnCanvas, () -> canvasController.addEdgeOnCanvas(edge)); updateStateForCurrentIteration(); } public void calculateStateForCurrentIteration() { simulationManager.calculateStateForIteration(model.getCurrentIteration()); } public void updateStateForCurrentIteration() { simulationManager.calculateStateForIteration(model.getCurrentIteration()); OnCanvasUpdate.broadcast(); } /** * calculates the flow of the edges and the supply for objects. * * @param x current Iteration */ public void updateStateForIteration(int x) { simulationManager.calculateStateForIteration(x); OnCanvasUpdate.broadcast(); } /** * resets the whole State of the simulation including a reset of all Edges to the default "is * working" state */ public void resetSimulation() { model.reset(); } /** * Getter for Model. * * @return the Model */ public Model getModel() { return model; } /** * Copy all Selected Objects. */ public void copy() { GuiSettings.getClipboardObjects().clear(); GuiSettings.getClipboardObjects().addAll(GuiSettings.getSelectedObjects()); GuiSettings.getClipboardEdges().clear(); GuiSettings.getClipboardEdges() .addAll(getEdgesBetweenObjects(GuiSettings.getSelectedObjects())); } private List getEdgesBetweenObjects(Collection selectedObjects) { return model.getEdgesOnCanvas().stream() .filter(edge -> selectedObjects.containsAll(Arrays.asList(edge.getA(), edge.getB()))) .toList(); } public void paste(GroupNode groupNode, Vec2i offset) { if (GuiSettings.getClipboardObjects().isEmpty()) { return; } HashMap oldToNew = new HashMap<>(); Collection copies = GuiSettings.getClipboardObjects().stream() .map(obj -> { AbstractCanvasObject copy = obj.copy(); oldToNew.put(obj, copy); return copy; }).toList(); copies.forEach(obj -> { obj.getPosition().addAssign(offset); obj.getPosition().clampX(0, GuiSettings.canvasSize.getX()); obj.getPosition().clampY(0, GuiSettings.canvasSize.getY()); }); groupNode.addAll(copies); Collection copyEdges = GuiSettings.getClipboardEdges().stream().map( edge -> new Edge(oldToNew.get(edge.getA()), oldToNew.get(edge.getB()), edge.maxCapacity)) .toList(); model.getEdgesOnCanvas().addAll(copyEdges); updateStateForCurrentIteration(); OnSelectionChanged.broadcast(); } public void cut() { copy(); deleteCanvasObjects(GuiSettings.getSelectedObjects()); clearSelection(); } public void guiSetEnabled(boolean state) { OnGuiSetEnabled.broadcast(state); } /** * init default category and objects. */ private void initCategories() { Category energy = createCategoryWithName("Energy"); Category building = createCategoryWithName("Building"); Category component = createCategoryWithName("Component"); HolonObject powerPlant = addNewHolonObject(energy, "Power Plant", new ArrayList<>(), ImagePreference.Canvas.DefaultObject.PowerPlant); HolonObject house = addNewHolonObject(building, "House", new ArrayList<>(), ImagePreference.Canvas.DefaultObject.House); HolonSwitch sw = new HolonSwitch("Switch"); component.getObjects().add(sw); powerPlant.add(new HolonElement(null, "Power", 10000)); energy.getObjects().add(powerPlant); house.add(new HolonElement(null, "TV", -250)); house.add(new HolonElement(null, "TV", -250)); house.add(new HolonElement(null, "Fridge", -500)); house.add(new HolonElement(null, "Radio", -100)); house.add(new HolonElement(null, "PC", -250)); house.add(new HolonElement(null, "PC", -250)); house.add(new HolonElement(null, "PC", -250)); house.add(new HolonElement(null, "Light", -50)); house.add(new HolonElement(null, "Light", -50)); house.add(new HolonElement(null, "Light", -50)); house.add(new HolonElement(null, "Light", -50)); house.add(new HolonElement(null, "Light", -50)); house.add(new HolonElement(null, "Solar Panel", 300)); building.getObjects().add(house); OnCategoryChanged.broadcast(); } public Category createCategoryWithName(String categoryName) { Optional category = findCategoryWithName(categoryName); if (category.isEmpty()) { Category cat = new Category(categoryName); GuiSettings.getCategories().add(cat); OnCategoryChanged.broadcast(); return cat; } else { return category.get(); } } /** * Add Object into a Category. * * @param category Category * @param object Object */ public void addObject(Category category, AbstractCanvasObject object) { int i = 0; while (category.findObjectWithName(object.getName()).isPresent()) { if (object.getName().contains("_")) { object.setName(object.getName().substring(0, object.getName().indexOf('_'))); } object.setName(object.getName() + "_" + i); i++; } category.getObjects().add(object); } /** * 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 */ public HolonObject addNewHolonObject(Category category, String object, List list, String image) { HolonObject obj = new HolonObject(object); obj.setImagePath(image); obj.clearElements(); obj.add(list); addObject(category, obj); return obj; } public Optional findCategoryWithName(String categoryName) { return GuiSettings.getCategories().stream().filter(cat -> cat.getName().equals(categoryName)) .findAny(); } public void loadFile(File file) { log.info("load" + file); try { FileReader reader = new FileReader(file); Model model = gson.fromJson(reader, Model.class); reader.close(); this.model = model; calculateStateForCurrentIteration(); OnModelChanged.broadcast(); } catch (IOException e) { log.warning(e.getLocalizedMessage()); } clearSelection(); GuiSettings.setActualSaveFile(file); } public void loadModelFromJsonString(String json) { this.model = gson.fromJson(json, Model.class); calculateStateForCurrentIteration(); OnModelChanged.broadcast(); } public String saveModelToJsonString() { return gson.toJson(model); } public void toggleSelectedObjects(Collection objects) { Set 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); gson.toJson(model, writer); writer.close(); } catch (IOException e) { log.warning(e.getLocalizedMessage()); } 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 undo() { UndoHistory.undo().ifPresent(this::loadModelFromJsonString); } public void redo() { UndoHistory.redo().ifPresent(this::loadModelFromJsonString); } public void clearModel() { model.clear(); clearSelection(); model.setCurrentIteration(0); IdCounter.reset(); calculateStateForCurrentIteration(); OnModelChanged.broadcast(); GuiSettings.setActualSaveFile(null); } public void showGroupNode(GroupNode groupNode) { OnShowGroupNode.broadcast(groupNode); clearSelection(); } public void loadCategory() { String json = prefs.get(PreferenceKeys.Category.DefaultCategory, "Init"); if (json.equals("Init")) { initCategories(); return; } GuiSettings.getCategories().clear(); GuiSettings.getCategories() .addAll(CategoryAdapter.Gson.fromJson(json, CategoryAdapter.CategorySet)); OnCategoryChanged.broadcast(); } public void saveCategories() { String json = CategoryAdapter.Gson.toJson(GuiSettings.getCategories()); prefs.put(PreferenceKeys.Category.DefaultCategory, json); } }