Slight improvements in documentation.
[Mograsim.git] / net.mograsim.logic.ui / src / net / mograsim / logic / ui / model / components / GUIComponent.java
index 7254d8b..f961caf 100644 (file)
@@ -2,7 +2,10 @@ package net.mograsim.logic.ui.model.components;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
@@ -11,12 +14,27 @@ import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
 import net.mograsim.logic.ui.model.ViewModelModifiable;
 import net.mograsim.logic.ui.model.wires.Pin;
 
+/**
+ * The base class for all GUI components.<br>
+ * A <code>GUIComponent</code> has a position and size. The size can only be modified by subclasses.<br>
+ * 
+ * @author Daniel Kirschten
+ */
 public abstract class GUIComponent
 {
+       /**
+        * The model this component is a part of.
+        */
        protected final ViewModelModifiable model;
        private final Rectangle bounds;
-       private final List<Pin> pins;
-       protected final List<Pin> pinsUnmodifiable;
+       /**
+        * The list of all pins of this component by name.
+        */
+       private final Map<String, Pin> pinsByName;
+       /**
+        * An unmodifiable view of {@link #pinsByName}.
+        */
+       protected final Map<String, Pin> pinsUnmodifiable;
 
        private final List<Consumer<? super GUIComponent>> componentMovedListeners;
        private final List<Consumer<? super Pin>> pinAddedListeners;
@@ -25,114 +43,226 @@ public abstract class GUIComponent
 
        private final Runnable redrawListenerForSubcomponents;
        // Defines how the GUIComponent is referenced in SubmodelComponentParams
-       protected Supplier<String> identifierDelegate = () -> "class:".concat(getClass().getCanonicalName());
+       protected Supplier<String> identifierDelegate = () -> getClass().getSimpleName();
+
+       // creation and destruction
 
        public GUIComponent(ViewModelModifiable model)
        {
                this.model = model;
                this.bounds = new Rectangle(0, 0, 0, 0);
-               this.pins = new ArrayList<>();
-               this.pinsUnmodifiable = Collections.unmodifiableList(pins);
+               this.pinsByName = new HashMap<>();
+               this.pinsUnmodifiable = Collections.unmodifiableMap(pinsByName);
 
                this.componentMovedListeners = new ArrayList<>();
                this.pinAddedListeners = new ArrayList<>();
                this.pinRemovedListeners = new ArrayList<>();
                this.redrawListeners = new ArrayList<>();
 
-               redrawListenerForSubcomponents = this::callRedrawListeners;
+               redrawListenerForSubcomponents = this::requestRedraw;
 
                model.componentCreated(this);
        }
 
+       /**
+        * Destroys this component. This method implicitly calls {@link ViewModelModifiable#componentDestroyed(GUIComponent)
+        * componentDestroyed()} for the model this component is a part of.
+        * 
+        * @author Daniel Kirschten
+        */
        public void destroy()
        {
-               pins.forEach(p -> pinRemovedListeners.forEach(l -> l.accept(p)));
+               pinsByName.values().forEach(p -> pinRemovedListeners.forEach(l -> l.accept(p)));
                model.componentDestroyed(this);
        }
 
-       public void moveTo(double x, double y)
-       {
-               bounds.x = x;
-               bounds.y = y;
-               callComponentMovedListeners();
-       }
+       // pins
 
        /**
-        * Returns the bounds of this component. Used for calculating which component is clicked.
+        * Adds the given pin to this component and calls pinAddedListeners and redrawListeners.
+        * 
+        * @throws IllegalArgumentException if the pin doesn't belong to this component
+        * @throws IllegalArgumentException if there already is a pin with the given name
+        * 
+        * @author Daniel Kirschten
         */
-       public Rectangle getBounds()
+       protected void addPin(Pin pin)
        {
-               return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
+               if (pin.component != this)
+                       throw new IllegalArgumentException("Can't add a pin not belonging to this component!");
+               if (pinsByName.containsKey(pin.name))
+                       throw new IllegalArgumentException("Duplicate pin name: " + pin.name);
+               pinsByName.put(pin.name, pin);
+               callPinAddedListeners(pin);
+               pin.addRedrawListener(redrawListenerForSubcomponents);
+               requestRedraw();
        }
 
        /**
-        * Called when this component is clicked. Absolute coordinates of the click are given. Returns true if this component consumed this
-        * click.
+        * Removes the given pin from this component and calls pinAddedListeners and redrawListeners.
+        * 
+        * @throws NullPointerException if there was no pin with this name
+        * 
+        * @author Daniel Kirschten
         */
-       @SuppressWarnings({ "static-method", "unused" }) // this method is inteded to be overridden
-       public boolean clicked(double x, double y)
+       protected void removePin(String name)
        {
-               return false;
+               Pin pin = pinsByName.remove(name);
+               callPinRemovedListeners(pin);
+               pin.removeRedrawListener(redrawListenerForSubcomponents);
+               requestRedraw();
        }
 
        /**
-        * Returns a list of pins of this component.
+        * Returns a collection of pins of this component.
+        * 
+        * @author Daniel Kirschten
         */
-       public List<Pin> getPins()
+       public Map<String, Pin> getPins()
        {
                return pinsUnmodifiable;
        }
 
-       // @formatter:off
-       public void addComponentMovedListener   (Consumer<? super GUIComponent> listener) {componentMovedListeners.add   (listener);}
-       public void addPinAddedListener         (Consumer<? super Pin         > listener) {pinAddedListeners      .add   (listener);}
-       public void addPinRemovedListener       (Consumer<? super Pin         > listener) {pinRemovedListeners    .add   (listener);}
-       public void addRedrawListener           (Runnable                       listener) {redrawListeners        .add   (listener);}
+       /**
+        * Returns the pin with the given name of this component.
+        * 
+        * @throws IllegalArgumentException if there is no pin with the given name
+        * 
+        * @author Daniel Kirschten
+        */
+       public Pin getPin(String name)
+       {
+               Pin pin = pinsByName.get(name);
+               if (pin == null)
+                       throw new IllegalArgumentException("No pin with the name " + name);
+               return pin;
+       }
 
-       public void removeComponentMovedListener(Consumer<? super GUIComponent> listener) {componentMovedListeners .remove(listener);}
-       public void removePinAddedListener      (Consumer<? super Pin         > listener) {pinAddedListeners       .remove(listener);}
-       public void removePinRemovedListener    (Consumer<? super Pin         > listener) {pinRemovedListeners     .remove(listener);}
-       public void removeRedrawListener        (Runnable                       listener) {redrawListeners         .remove(listener);}
+       // high-level access
 
-       private void callComponentMovedListeners(     ) {componentMovedListeners.forEach(l -> l.accept(this));}
-       private void callPinAddedListeners      (Pin p) {pinAddedListeners      .forEach(l -> l.accept(p   ));}
-       private void callPinRemovedListeners    (Pin p) {pinRemovedListeners    .forEach(l -> l.accept(p   ));}
-       private void callRedrawListeners        (     ) {redrawListeners        .forEach(l -> l.run(       ));}
-       // @formatter:on
+       /**
+        * Sets the given high-level state to the given value. <br>
+        * TODO more documentation!
+        * 
+        * @author Daniel Kirschten
+        */
+       @SuppressWarnings({ "static-method", "unused" }) // this method is intended to be overridden
+       public void setHighLevelState(String stateID, Object newState)
+       {
+               throw new IllegalArgumentException("No high level state with ID " + stateID);
+       }
 
        /**
-        * Render this component to the given gc.
+        * Gets the current value of the given high-level state. <br>
+        * TODO more documentation!
+        * 
+        * @author Daniel Kirschten
         */
-       public abstract void render(GeneralGC gc, Rectangle visibleRegion);
+       @SuppressWarnings("static-method") // this method is intended to be overridden
+       public Object getHighLevelState(String stateID)
+       {
+               throw new IllegalArgumentException("No high level state with ID " + stateID);
+       }
 
-       protected void requestRedraw()
+       // "graphical" operations
+
+       /**
+        * Sets the position of this component and calls componentMovedListeners and redrawListeners.
+        * 
+        * @author Daniel Kirschten
+        */
+       public void moveTo(double x, double y)
        {
-               callRedrawListeners();
+               bounds.x = x;
+               bounds.y = y;
+               callComponentMovedListeners();
+               requestRedraw();
        }
 
+       /**
+        * Sets the size of this component and calls redrawListeners.
+        * 
+        * @author Daniel Kirschten
+        */
        protected void setSize(double width, double height)
        {
                bounds.width = width;
                bounds.height = height;
-               callRedrawListeners();
+               requestRedraw();
        }
 
-       protected void addPin(Pin pin)
+       /**
+        * Returns the bounds of this component. Is a bit slower than {@link #getPosX()}, {@link #getPosY()}, {@link #getWidth},
+        * {@link #getHeight}, because new objects are created.
+        * 
+        * @author Daniel Kirschten
+        */
+       public Rectangle getBounds()
        {
-               pins.add(pin);
-               callPinAddedListeners(pin);
-               pin.addRedrawListener(redrawListenerForSubcomponents);
-               callRedrawListeners();
+               return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
        }
 
-       protected void removePin(Pin pin)
+       /**
+        * Returns the x coordinate of the position of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
+        * 
+        * @author Daniel Kirschten
+        */
+       public double getPosX()
        {
-               pins.remove(pin);
-               callPinRemovedListeners(pin);
-               pin.removeRedrawListener(redrawListenerForSubcomponents);
-               callRedrawListeners();
+               return bounds.x;
+       }
+
+       /**
+        * Returns the y coordinate of the position of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
+        * 
+        * @author Daniel Kirschten
+        */
+       public double getPosY()
+       {
+               return bounds.y;
+       }
+
+       /**
+        * Returns the (graphical) width of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
+        * 
+        * @author Daniel Kirschten
+        */
+       public double getWidth()
+       {
+               return bounds.width;
+       }
+
+       /**
+        * Returns the height of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
+        * 
+        * @author Daniel Kirschten
+        */
+       public double getHeight()
+       {
+               return bounds.height;
+       }
+
+       /**
+        * Called when this component is clicked. Absolute coordinates of the click are given. Returns true if this component consumed this
+        * click.
+        * 
+        * @author Daniel Kirschten
+        */
+       @SuppressWarnings({ "static-method", "unused" }) // this method is inteded to be overridden
+       public boolean clicked(double x, double y)
+       {
+               return false;
        }
 
+       /**
+        * Render this component to the given gc, in absoulute coordinates.
+        * 
+        * @author Daniel Kirschten
+        */
+       public abstract void render(GeneralGC gc, Rectangle visibleRegion);
+
+       // serializing
+
        /**
         * @return an identifier used to reference this GUIComponent inside of {@link SubmodelComponentParams}
         */
@@ -140,4 +270,39 @@ public abstract class GUIComponent
        {
                return identifierDelegate.get();
        }
+
+       @SuppressWarnings("static-method")
+       public Map<String, Object> getInstantiationParameters()
+       {
+               return new TreeMap<>();
+       }
+
+       // listeners
+
+       /**
+        * Calls redraw listeners.
+        * 
+        * @author Daniel Kirschten
+        */
+       protected void requestRedraw()
+       {
+               callRedrawListeners();
+       }
+
+       // @formatter:off
+       public void addComponentMovedListener   (Consumer<? super GUIComponent> listener) {componentMovedListeners.add   (listener);}
+       public void addPinAddedListener         (Consumer<? super Pin         > listener) {pinAddedListeners      .add   (listener);}
+       public void addPinRemovedListener       (Consumer<? super Pin         > listener) {pinRemovedListeners    .add   (listener);}
+       public void addRedrawListener           (Runnable                       listener) {redrawListeners        .add   (listener);}
+
+       public void removeComponentMovedListener(Consumer<? super GUIComponent> listener) {componentMovedListeners .remove(listener);}
+       public void removePinAddedListener      (Consumer<? super Pin         > listener) {pinAddedListeners       .remove(listener);}
+       public void removePinRemovedListener    (Consumer<? super Pin         > listener) {pinRemovedListeners     .remove(listener);}
+       public void removeRedrawListener        (Runnable                       listener) {redrawListeners         .remove(listener);}
+
+       private void callComponentMovedListeners(     ) {componentMovedListeners.forEach(l -> l.accept(this));}
+       private void callPinAddedListeners      (Pin p) {pinAddedListeners      .forEach(l -> l.accept(p   ));}
+       private void callPinRemovedListeners    (Pin p) {pinRemovedListeners    .forEach(l -> l.accept(p   ));}
+       private void callRedrawListeners        (     ) {redrawListeners        .forEach(l -> l.run(       ));}
+       // @formatter:on
 }
\ No newline at end of file