diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/.classpath b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/.classpath
new file mode 100644
index 00000000..2f309b0f
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/.classpath
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/.project b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/.project
new file mode 100644
index 00000000..c76f779e
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/.project
@@ -0,0 +1,17 @@
+
+
+ diylc-core
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/.settings/org.eclipse.jdt.ui.prefs b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 00000000..b00bc72a
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,4 @@
+#Sun Nov 08 15:11:18 CET 2009
+eclipse.preferences.version=1
+org.eclipse.jdt.ui.javadoc=false
+org.eclipse.jdt.ui.text.custom_code_templates=/**\r\n * @return the ${bare_field_name}\r\n *//**\r\n * @param ${param} the ${bare_field_name} to set\r\n *//**\r\n * ${tags}\r\n *//**\r\n * \r\n *//**\r\n * @author ${user}\r\n *\r\n * ${tags}\r\n *//**\r\n * \r\n *//**\r\n * ${tags}\r\n *//**&\#13;\r\n*{@inheritDoc}&\#13;\r\n**//**\r\n * ${tags}\r\n * ${see_to_target}\r\n */${filecomment}\r\n${package_declaration}\r\n\r\n${typecomment}\r\n${type_declaration}\r\n\r\n\r\n\r\n// ${todo} Auto-generated catch block\r\n${exception_var}.printStackTrace();// ${todo} Auto-generated method stub\r\n${body_statement}${body_statement}\r\n// ${todo} Auto-generated constructor stubreturn ${field};${field} \= ${param};
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/build.xml b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/build.xml
new file mode 100644
index 00000000..659dac8a
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/build.xml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Compile with JDK ${java.runtime.version}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/config.properties b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/config.properties
new file mode 100644
index 00000000..c6abd215
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/config.properties
@@ -0,0 +1,2 @@
+#org.diylc.utils.Constants.CANVAS_COLOR=#005D96
+#org.diylc.utils.Constants.GRID_COLOR=#FFFFFF
\ No newline at end of file
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/lib/app-framework.jar b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/lib/app-framework.jar
new file mode 100644
index 00000000..f7ae5f54
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/lib/app-framework.jar differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/lib/jettison-1.0.1.jar b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/lib/jettison-1.0.1.jar
new file mode 100644
index 00000000..fed929fd
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/lib/jettison-1.0.1.jar differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/lib/junit-4.7.jar b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/lib/junit-4.7.jar
new file mode 100644
index 00000000..700ad695
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/lib/junit-4.7.jar differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/lib/log4j-1.2.15.jar b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/lib/log4j-1.2.15.jar
new file mode 100644
index 00000000..c930a6ab
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/lib/log4j-1.2.15.jar differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/lib/xstream-1.3.1.jar b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/lib/xstream-1.3.1.jar
new file mode 100644
index 00000000..4ef4219c
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/lib/xstream-1.3.1.jar differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/library/main.jar b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/library/main.jar
new file mode 100644
index 00000000..ad37f99a
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/library/main.jar differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/BadPositionException.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/BadPositionException.java
new file mode 100644
index 00000000..52dcfe95
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/BadPositionException.java
@@ -0,0 +1,7 @@
+package org.diylc.common;
+
+public class BadPositionException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/ComponentType.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/ComponentType.java
new file mode 100644
index 00000000..a20fd58c
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/ComponentType.java
@@ -0,0 +1,114 @@
+package org.diylc.common;
+
+import javax.swing.Icon;
+
+import org.diylc.core.CreationMethod;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.annotations.BomPolicy;
+
+/**
+ * Entity class used to describe a component type.
+ *
+ * @author Branislav Stojkovic
+ *
+ * @see IDIYComponent
+ */
+public class ComponentType {
+
+ private String name;
+ private String description;
+ private CreationMethod creationMethod;
+ private String category;
+ private String namePrefix;
+ private String author;
+ private Icon icon;
+ private Class extends IDIYComponent>> instanceClass;
+ private double zOrder;
+ private boolean flexibleZOrder;
+ private boolean stretchable;
+ private BomPolicy bomPolicy;
+ private boolean autoEdit;
+ private boolean rotatable;
+
+ public ComponentType(String name, String description, CreationMethod creationMethod,
+ String category, String namePrefix, String author, Icon icon,
+ Class extends IDIYComponent>> instanceClass, double zOrder, boolean flexibleZOrder,
+ boolean stretchable, BomPolicy bomPolicy, boolean autoEdit, boolean rotatable) {
+ super();
+ this.name = name;
+ this.description = description;
+ this.creationMethod = creationMethod;
+ this.category = category;
+ this.namePrefix = namePrefix;
+ this.author = author;
+ this.icon = icon;
+ this.instanceClass = instanceClass;
+ this.zOrder = zOrder;
+ this.flexibleZOrder = flexibleZOrder;
+ this.stretchable = stretchable;
+ this.bomPolicy = bomPolicy;
+ this.autoEdit = autoEdit;
+ this.rotatable = rotatable;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public CreationMethod getCreationMethod() {
+ return creationMethod;
+ }
+
+ public String getCategory() {
+ return category;
+ }
+
+ public String getNamePrefix() {
+ return namePrefix;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public Icon getIcon() {
+ return icon;
+ }
+
+ public Class extends IDIYComponent>> getInstanceClass() {
+ return instanceClass;
+ }
+
+ public double getZOrder() {
+ return zOrder;
+ }
+
+ public boolean isFlexibleZOrder() {
+ return flexibleZOrder;
+ }
+
+ public boolean isStretchable() {
+ return stretchable;
+ }
+
+ public BomPolicy getBomPolicy() {
+ return bomPolicy;
+ }
+
+ public boolean isAutoEdit() {
+ return autoEdit;
+ }
+
+ public boolean isRotatable() {
+ return rotatable;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/Display.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/Display.java
new file mode 100644
index 00000000..ed3404ac
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/Display.java
@@ -0,0 +1,11 @@
+package org.diylc.common;
+
+public enum Display {
+
+ NAME, VALUE;
+
+ @Override
+ public String toString() {
+ return name().substring(0, 1) + name().substring(1).toLowerCase();
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/DrawOption.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/DrawOption.java
new file mode 100644
index 00000000..b36ac993
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/DrawOption.java
@@ -0,0 +1,40 @@
+package org.diylc.common;
+
+import java.awt.Graphics;
+
+/**
+ * Enumerates all options that can be used when drawing a project.
+ *
+ * @see IPlugInPort#draw(java.awt.Graphics2D, java.util.Set, IComponentFiler)
+ *
+ * @author Branislav Stojkovic
+ */
+public enum DrawOption {
+
+ /**
+ * Selection rectangle will be drawn when needed and selected components may
+ * be rendered differently.
+ */
+ SELECTION,
+ /**
+ * Selected zoom level will be applied to scale the {@link Graphics} before
+ * drawing.
+ */
+ ZOOM,
+ /**
+ * Grid lines are drawn.
+ */
+ GRID,
+ /**
+ * Control points are drawn.
+ */
+ CONTROL_POINTS,
+ /**
+ * Anti-aliasing is used when drawing.
+ */
+ ANTIALIASING,
+ /**
+ * Draw components in outline mode.
+ */
+ OUTLINE_MODE;
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/EventType.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/EventType.java
new file mode 100644
index 00000000..fd1d8838
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/EventType.java
@@ -0,0 +1,83 @@
+package org.diylc.common;
+
+import java.awt.Rectangle;
+import java.awt.geom.Point2D;
+
+import org.diylc.core.Project;
+
+/**
+ * Enumerates all possible events between {@link IPlugInPort} and
+ * {@link IPlugIn}. Some events overlap so be careful which ones you subscribe
+ * for and how you process them.
+ *
+ * @author Branislav Stojkovic
+ */
+public enum EventType {
+
+ /**
+ * Called when zoom level changes. Typically only one parameter of type
+ * {@link Double} is passed with new zoom level.
+ */
+ ZOOM_CHANGED,
+ /**
+ * Called when a new project is loaded. New {@link Project} is the first
+ * parameter. Boolean flag is the second parameter and it's true when new
+ * project is loaded, false when the same project has been either reloaded
+ * or loaded with undo/redo operations.
+ */
+ PROJECT_LOADED,
+ /**
+ * Called when either current file name or modified flag are changed. The
+ * first parameter is the current file name, the second one is a boolean
+ * modified flag.
+ */
+ FILE_STATUS_CHANGED,
+ /**
+ * Called when selection rectangle is changed. Object of type
+ * {@link Rectangle} is the only parameter and it contains the new selection
+ * rectangle.
+ */
+ SELECTION_RECT_CHANGED,
+ /**
+ * Called when component selection is changed. New
+ * {@link ComponentSelection} is attached as a parameter.
+ */
+ SELECTION_CHANGED,
+ /**
+ * Called when selection size changes, together with a {@link Point2D}
+ * object containing selection size (x = width, y = height). Size is
+ * expressed in the default measure (cm for metric, inches for imperial).
+ */
+ REPAINT,
+ /**
+ * Called when new component slot has been changed. The only parameter is
+ * {@link ComponentType} and may be null.
+ */
+ SLOT_CHANGED,
+ /**
+ * Called when the current project has been modified. Two instances of
+ * {@link Project} are passed as parameters, one before and one after the
+ * change. The third parameter is a string containing change description.
+ */
+ PROJECT_MODIFIED,
+ /**
+ * Called when control points under the cursor are changed. A single
+ * parameter is passed, an instance of
+ * Map, Set> containing all the
+ * components and indices of their control points that are under the cursor.
+ */
+ AVAILABLE_CTRL_POINTS_CHANGED,
+ /**
+ * Called when mouse cursor is moved. Point object is passed, containing the
+ * current cursor location (not taking zoom into account).
+ */
+ MOUSE_MOVED,
+ /**
+ * Called when a layer status is changed. The first parameter is a set of indices of locked layers. All layers not included are considered unlocked.
+ */
+ LAYER_STATE_CHANGED,
+ /**
+ * Called to update the status message. New status message is passed as a first parameter.
+ */
+ STATUS_MESSAGE_CHANGED;
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/GridType.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/GridType.java
new file mode 100644
index 00000000..5d960ce3
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/GridType.java
@@ -0,0 +1,6 @@
+package org.diylc.common;
+
+public enum GridType {
+
+ LINES, CROSSHAIR, DOT, NONE;
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/HorizontalAlignment.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/HorizontalAlignment.java
new file mode 100644
index 00000000..e8ef10f4
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/HorizontalAlignment.java
@@ -0,0 +1,9 @@
+package org.diylc.common;
+
+public enum HorizontalAlignment {
+ LEFT, CENTER, RIGHT;
+
+ public String toString() {
+ return name().substring(0, 1) + name().substring(1).toLowerCase();
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/IComponentFiler.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/IComponentFiler.java
new file mode 100644
index 00000000..c1647cdb
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/IComponentFiler.java
@@ -0,0 +1,8 @@
+package org.diylc.common;
+
+import org.diylc.core.IDIYComponent;
+
+public interface IComponentFiler {
+
+ boolean testComponent(IDIYComponent> component);
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/IKeyProcessor.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/IKeyProcessor.java
new file mode 100644
index 00000000..86cf6a5d
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/IKeyProcessor.java
@@ -0,0 +1,22 @@
+package org.diylc.common;
+
+public interface IKeyProcessor {
+
+ public static final int VK_LEFT = 0x25;
+ public static final int VK_UP = 0x26;
+ public static final int VK_RIGHT = 0x27;
+ public static final int VK_DOWN = 0x28;
+
+ /**
+ * Notifies the presenter that a key hes been pressed on the canvas.
+ *
+ * @param key
+ * @param ctrlDown
+ * @param shiftDown
+ * @param altDown
+ *
+ * @return true if the event should be consumed
+ */
+ boolean keyPressed(int key, boolean ctrlDown, boolean shiftDown,
+ boolean altDown);
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/IMouseProcessor.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/IMouseProcessor.java
new file mode 100644
index 00000000..95963440
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/IMouseProcessor.java
@@ -0,0 +1,69 @@
+package org.diylc.common;
+
+import java.awt.Point;
+
+public interface IMouseProcessor {
+
+ /**
+ * Notifies the presenter that mouse is clicked.
+ *
+ * Note: point coordinates are display based, i.e. scaled for zoom factor.
+ *
+ * @param point
+ * @param button
+ * @param ctrlDown
+ * @param shiftDown
+ * @param altDown
+ * @param clickCount
+ */
+ void mouseClicked(Point point, int button, boolean ctrlDown, boolean shiftDown, boolean altDown,
+ int clickCount);
+
+ /**
+ * Notifies the presenter that mouse is moved.
+ *
+ * Note: point coordinates are display based, i.e. scaled for zoom factor.
+ *
+ * @param point
+ * @param ctrlDown
+ * @param shiftDown
+ * @param altDown
+ */
+ void mouseMoved(Point point, boolean ctrlDown, boolean shiftDown, boolean altDown);
+
+ /**
+ * Notification that drag has been started from the specified point.
+ *
+ * Note: point coordinates are scaled for zoom factor.
+ *
+ * @param point
+ * @param dragAction
+ */
+ void dragStarted(Point point, int dragAction);
+
+ /**
+ * Checks if it's possible to drop over the specified point.
+ *
+ * Note: point coordinates are scaled for zoom factor.
+ *
+ * @param point
+ * @return
+ */
+ boolean dragOver(Point point);
+
+ /**
+ * Changes the current drag action during the dragging.
+ *
+ * @param dragAction
+ */
+ void dragActionChanged(int dragAction);
+
+ /**
+ * Notification that drag has been ended in the specified point.
+ *
+ * Note: point coordinates are scaled for zoom factor.
+ *
+ * @param point
+ */
+ void dragEnded(Point point);
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/IPlugIn.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/IPlugIn.java
new file mode 100644
index 00000000..cbfee46a
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/IPlugIn.java
@@ -0,0 +1,19 @@
+package org.diylc.common;
+
+import org.diylc.appframework.simplemq.IMessageListener;
+
+/**
+ * Interface for plug-ins.
+ *
+ * @author Branislav Stojkovic
+ */
+public interface IPlugIn extends IMessageListener {
+
+ /**
+ * Method that connects the plug-in with {@link IPlugInPort}. Called by the
+ * application when plug-in is installed.
+ *
+ * @param plugInPort
+ */
+ void connect(IPlugInPort plugInPort);
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/IPlugInPort.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/IPlugInPort.java
new file mode 100644
index 00000000..8624cbc7
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/IPlugInPort.java
@@ -0,0 +1,320 @@
+package org.diylc.common;
+
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.geom.Point2D;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.diylc.appframework.simplemq.IMessageListener;
+import org.diylc.appframework.simplemq.MessageDispatcher;
+import org.diylc.appframework.update.VersionNumber;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.Project;
+import org.diylc.core.Template;
+import org.diylc.core.Theme;
+
+/**
+ * Interface for communication between plug-ins and the application. Plug-ins
+ * may acquire information or make changes through this interface. Application
+ * events are dispatched to plug-ins through {@link MessageDispatcher}
+ *
+ * @author Branislav Stojkovic
+ *
+ * @see IPlugIn
+ * @see MessageDispatcher
+ * @see IMessageListener
+ * @see EventType
+ */
+public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyProcessor, ITemplateProcessor {
+
+ public static final String ANTI_ALIASING_KEY = "antiAliasing";
+ public static final String HI_QUALITY_RENDER_KEY = "hiQualityRender";
+ public static final String EXPORT_GRID_KEY = "exportGrid";
+ public static final String STICKY_POINTS_KEY = "stickyPoints";
+ public static final String METRIC_KEY = "metric";
+ public static final String SNAP_TO_GRID_KEY = "snapToGrid";
+ public static final String AUTO_PADS_KEY = "autoCreatePads";
+ public static final String CONTINUOUS_CREATION_KEY = "continuousCreation";
+ public static final String AUTO_EDIT_KEY = "autoEdit";
+ public static final String ABNORMAL_EXIT_KEY = "abnormalExit";
+ public static final String WHEEL_ZOOM_KEY = "wheelZoom";
+ public static final String OUTLINE_KEY = "outline";
+ public static final String THEME_KEY = "theme";
+ public static final String RECENT_COMPONENTS_KEY = "recentComponents";
+
+ public static final int DND_TOGGLE_STICKY = 0x1;
+ public static final int DND_TOGGLE_SNAP = 0x40000000;
+
+ /**
+ * Mouse button constants.
+ */
+ public static final int NOBUTTON = 0;
+ public static final int BUTTON1 = 1;
+ public static final int BUTTON2 = 2;
+ public static final int BUTTON3 = 3;
+
+ /**
+ * Returns size of the canvas that takes project dimensions into account as
+ * well as zoom level. Each dimension is calculated as the product of the
+ * actual size and the number of pixels per unit. If useZoom is
+ * set to true, the result is scaled by zoom factor.
+ *
+ * @param useZoom
+ *
+ * @return canvas dimensions
+ */
+ Dimension getCanvasDimensions(boolean useZoom);
+
+ /**
+ * Returns an instance of {@link Cursor} that should be used at the
+ * specified location.
+ *
+ * Note: point coordinates are scaled for zoom factor.
+ *
+ * @param point
+ * @return cursor
+ */
+ Cursor getCursorAt(Point point);
+
+ /**
+ * Returns an instance of currently loaded project.
+ *
+ * @return
+ */
+ Project getCurrentProject();
+
+ /**
+ * Loads specified {@link Project}.
+ *
+ * @param project
+ * @param freshStart
+ */
+ void loadProject(Project project, boolean freshStart);
+
+ /**
+ * Creates a new project.
+ */
+ void createNewProject();
+
+ /**
+ * Loads a project from the specified file.
+ *
+ * @param fileName
+ */
+ void loadProjectFromFile(String fileName);
+
+ /**
+ * Saves the current project into the specified file.
+ *
+ * @param fileName
+ * @param isBackup
+ */
+ void saveProjectToFile(String fileName, boolean isBackup);
+
+ /**
+ * @return the current file name.
+ */
+ String getCurrentFileName();
+
+ /**
+ * @return true if the current project is modified.
+ */
+ boolean isProjectModified();
+
+ /**
+ * Shows a user dialog if there are changes to confirm that it's safe to
+ * proceed.
+ *
+ * @return true, if file actions (new, open, close) can be taken
+ */
+ boolean allowFileAction();
+
+ /**
+ * Returns all available {@link ComponentType}s classified by category.
+ * Result is a {@link Map} between category name to a {@link List} of all
+ * {@link ComponentType}s that share that category name.
+ *
+ * @return
+ */
+ Map> getComponentTypes();
+
+ /**
+ * Draws project on the provided {@link Graphics2D}. If the provided filter
+ * is not null, it will be used to filter the components that are shown.
+ *
+ * @param g2d
+ * @param drawOptions
+ * specific drawing options
+ * @param filter
+ */
+ void draw(Graphics2D g2d, Set drawOptions, IComponentFiler filter);
+
+ Double[] getAvailableZoomLevels();
+
+ /**
+ * Returns current zoom level where zoomLevel = 1.0d means
+ * 100%.
+ *
+ * @return current zoom level
+ */
+ double getZoomLevel();
+
+ /**
+ * Changes current zoom level where zoomLevel = 1.0d means 100%
+ *
+ * @param zoomLevel
+ * new zoom leve
+ */
+ void setZoomLevel(double zoomLevel);
+
+ /**
+ * Returns current version number.
+ *
+ * @return
+ */
+ VersionNumber getCurrentVersionNumber();
+
+ /**
+ * Adds a list of components to the project.
+ *
+ * @param components
+ */
+ void pasteComponents(List> components);
+
+ /**
+ * Deletes all the selected components from the project.
+ */
+ void deleteSelectedComponents();
+
+ /**
+ * Sets default value for the specified property name for currently selected
+ * component types.
+ *
+ * @param propertyName
+ * display name for property
+ * @param value
+ * new default value, must not be null
+ */
+ void setSelectionDefaultPropertyValue(String propertyName, Object value);
+
+ /**
+ * Sets default value for the specified property name for projects.
+ *
+ * @param propertyName
+ * display name for property
+ * @param value
+ * new default value, must not be null
+ */
+ void setProjectDefaultPropertyValue(String propertyName, Object value);
+
+ /**
+ * @return a list of properties that are mutual for all the selected
+ * components. Resulting list may be empty if selected components do
+ * not have mutual properties or can be null if the selection is
+ * empty.
+ */
+ List getMutualSelectionProperties();
+
+ void editSelection();
+
+ /**
+ * @return a list of editable properties of the current project.
+ */
+ List getProjectProperties();
+
+ /**
+ * Applies specified properties to the current project.
+ *
+ * @param properties
+ */
+ void applyPropertiesToProject(List properties);
+
+ /**
+ * Gets the current new component slot.
+ */
+ ComponentType getNewComponentTypeSlot();
+
+ /**
+ * Sets the new component slot. Specified component type will be used to
+ * instantiate new component.
+ *
+ * @param componentType
+ * @param template
+ */
+ void setNewComponentTypeSlot(ComponentType componentType, Template template);
+
+ /**
+ * Changes default size notation, true for metric, false for imperial.
+ *
+ * @param isMetric
+ */
+ 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.
+ *
+ * @param layerZOrder
+ * @param locked
+ */
+ void setLayerLocked(int layerZOrder, boolean locked);
+
+ /**
+ * @return selection size expressed in either inches or centimeters, based
+ * on the user preference.
+ */
+ Point2D calculateSelectionDimension();
+
+// /**
+// * @return the smallest rectangle that encloses the selection
+// */
+// Rectangle2D getSelectedAreaRect();
+
+ /**
+ * Sends each of the selected components one step back.
+ */
+ void sendSelectionToBack();
+
+ /**
+ * Brings each of the selected components one step to front.
+ */
+ void bringSelectionToFront();
+
+ /**
+ * Causes the display to refresh.
+ */
+ void refresh();
+
+ /**
+ * @return currently selected theme.
+ */
+ Theme getSelectedTheme();
+
+ /**
+ * Changes the current theme.
+ *
+ * @param theme
+ */
+ void setSelectedTheme(Theme theme);
+
+ /**
+ * Renumbers all the selected components.
+ *
+ * @param xAxisFirst
+ */
+ void renumberSelectedComponents(boolean xAxisFirst);
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/ISelectionProcessor.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/ISelectionProcessor.java
new file mode 100644
index 00000000..1659e873
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/ISelectionProcessor.java
@@ -0,0 +1,59 @@
+package org.diylc.common;
+
+import java.awt.Point;
+import java.util.List;
+
+import org.diylc.core.ExpansionMode;
+import org.diylc.core.IDIYComponent;
+
+public interface ISelectionProcessor {
+
+ /**
+ * Returns the current {@link ComponentTransferable}.
+ *
+ * @return
+ */
+ List> getSelectedComponents();
+
+ /**
+ * Updates the selection with the specified list of component. Also, updates
+ * control point map with all components that are stuck to the newly
+ * selected components.
+ *
+ * @param newSelection
+ */
+ void updateSelection(List> newSelection);
+
+ /**
+ * Finds all components at the specified location, sorted by z-index from
+ * top to bottom.
+ *
+ * @param point
+ * @return
+ */
+ List> findComponentsAt(Point point);
+
+ /**
+ * Expands the current selection to include surrounding components. Options
+ * are controlled with expansionMode flag.
+ *
+ * @param expansionMode
+ */
+ void expandSelection(ExpansionMode expansionMode);
+
+ /**
+ * Selects all components in the project.
+ *
+ * @param int layer if > 0, designates which layer to select. If <= 0 we
+ * should select all regardless of layer
+ */
+ void selectAll(int layer);
+
+ /**
+ * Rotates selection for 90 degrees.
+ *
+ * @param direction
+ * 1 for clockwise, -1 for counter-clockwise
+ */
+ void rotateSelection(int direction);
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/ITask.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/ITask.java
new file mode 100644
index 00000000..eb7ad07a
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/ITask.java
@@ -0,0 +1,34 @@
+package org.diylc.common;
+
+/**
+ * Abstract layer between the app and platform worker thread implementation.
+ *
+ * @author Branislav Stojkovic
+ *
+ * @param
+ */
+public interface ITask {
+
+ /**
+ * Runs in background thread.
+ *
+ * @return
+ * @throws Exception
+ */
+ T doInBackground() throws Exception;
+
+ /**
+ * Called if background thread fails.
+ *
+ * @param e
+ */
+ void failed(Exception e);
+
+ /**
+ * Called if background thread is executed correctly, the result is passed
+ * as a parameter.
+ *
+ * @param result
+ */
+ void complete(T result);
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/ITemplateProcessor.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/ITemplateProcessor.java
new file mode 100644
index 00000000..1cf825a6
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/ITemplateProcessor.java
@@ -0,0 +1,26 @@
+package org.diylc.common;
+
+import java.util.List;
+
+import org.diylc.core.Template;
+
+public interface ITemplateProcessor {
+
+ public static final String TEMPLATES_KEY = "templates";
+
+ void saveSelectedComponentAsTemplate(String templateName);
+
+ List getTemplatesFor(String categoryName, String componentTypeName);
+
+ List getTemplatesForSelection();
+
+ void applyTemplateToSelection(Template template);
+
+ void deleteTemplate(String categoryName, String componentTypeName, String templateName);
+
+ public class TemplateAlreadyExistsException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/LabelPosition.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/LabelPosition.java
new file mode 100644
index 00000000..7038824e
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/LabelPosition.java
@@ -0,0 +1,11 @@
+package org.diylc.common;
+
+public enum LabelPosition {
+
+ ABOVE, BELOW;
+
+ @Override
+ public String toString() {
+ return name().substring(0, 1) + name().substring(1).toLowerCase();
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/ObjectCache.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/ObjectCache.java
new file mode 100644
index 00000000..09db7f8c
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/ObjectCache.java
@@ -0,0 +1,39 @@
+package org.diylc.common;
+
+import java.awt.AlphaComposite;
+import java.awt.BasicStroke;
+import java.awt.Stroke;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Repository for object that may be reused, such as {@link Stroke} or
+ * {@link AlphaComposite}.
+ *
+ * @author Branislav Stojkovic
+ */
+public class ObjectCache {
+
+ private static ObjectCache instance;
+
+ public static ObjectCache getInstance() {
+ if (instance == null) {
+ instance = new ObjectCache();
+ }
+ return instance;
+ }
+
+ private ObjectCache() {
+ }
+
+ private Map basicStrokeMap = new HashMap();
+
+ public Stroke fetchBasicStroke(int width) {
+ if (basicStrokeMap.containsKey(width)) {
+ return basicStrokeMap.get(width);
+ }
+ Stroke stroke = new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
+ basicStrokeMap.put(width, stroke);
+ return stroke;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/Orientation.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/Orientation.java
new file mode 100644
index 00000000..5d2d4f22
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/Orientation.java
@@ -0,0 +1,15 @@
+package org.diylc.common;
+
+public enum Orientation {
+
+ DEFAULT, _90, _180, _270;
+
+ @Override
+ public String toString() {
+ if (this == DEFAULT) {
+ return "Default";
+ } else {
+ return name().replace("_", "") + " degrees clockwise";
+ }
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/OrientationHV.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/OrientationHV.java
new file mode 100644
index 00000000..656f1016
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/OrientationHV.java
@@ -0,0 +1,11 @@
+package org.diylc.common;
+
+public enum OrientationHV {
+
+ VERTICAL, HORIZONTAL;
+
+ @Override
+ public String toString() {
+ return name().substring(0, 1) + name().substring(1).toLowerCase();
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/PCBLayer.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/PCBLayer.java
new file mode 100644
index 00000000..63add607
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/PCBLayer.java
@@ -0,0 +1,10 @@
+package org.diylc.common;
+
+public enum PCBLayer {
+
+ _1, _2, _3, _4, _5;
+
+ public String toString() {
+ return name().substring(1);
+ };
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/PropertyWrapper.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/PropertyWrapper.java
new file mode 100644
index 00000000..31e5384d
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/PropertyWrapper.java
@@ -0,0 +1,167 @@
+package org.diylc.common;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.diylc.core.IPropertyValidator;
+
+/**
+ * Entity class for editable properties extracted from component objects.
+ * Represents a single editable property together with it's current value.
+ *
+ * @author Branislav Stojkovic
+ */
+public class PropertyWrapper implements Cloneable {
+
+ private String name;
+ private Class> type;
+ private Object value;
+ private String setter;
+ private String getter;
+ private boolean defaultable;
+ private IPropertyValidator validator;
+ private boolean unique = true;
+ private boolean changed = false;
+
+ public PropertyWrapper(String name, Class> type, String getter,
+ String setter, boolean defaultable, IPropertyValidator validator) {
+ super();
+ this.name = name;
+ this.type = type;
+ this.getter = getter;
+ this.setter = setter;
+ this.defaultable = defaultable;
+ this.validator = validator;
+ }
+
+ public void readFrom(Object object) throws IllegalArgumentException,
+ IllegalAccessException, InvocationTargetException,
+ SecurityException, NoSuchMethodException {
+ this.value = object.getClass().getMethod(getter).invoke(object);
+ }
+
+ // public void readUniqueFrom(IDIYComponent component)
+ // throws IllegalArgumentException, IllegalAccessException,
+ // InvocationTargetException {
+ // Object newValue = getter.invoke(component);
+ // if (!newValue.equals(value)) {
+ // this.value = null;
+ // }
+ // }
+
+ public void writeTo(Object object) throws IllegalArgumentException,
+ IllegalAccessException, InvocationTargetException,
+ SecurityException, NoSuchMethodException {
+ object.getClass().getMethod(setter, type).invoke(object, this.value);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Class> getType() {
+ return type;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public void setValue(Object value) {
+ this.value = value;
+ }
+
+ public boolean isDefaultable() {
+ return defaultable;
+ }
+
+ public IPropertyValidator getValidator() {
+ return validator;
+ }
+
+ public boolean isUnique() {
+ return unique;
+ }
+
+ public void setUnique(boolean unique) {
+ this.unique = unique;
+ }
+
+ public boolean isChanged() {
+ return changed;
+ }
+
+ public void setChanged(boolean changed) {
+ this.changed = changed;
+ }
+
+ // @Override
+ // public Object clone() throws CloneNotSupportedException {
+ // // Try to invoke clone method on value if possible.
+ // try {
+ // Method cloneMethod = value.getClass().getMethod("clone");
+ // return new Property(name, type, cloneMethod.invoke(value));
+ // } catch (Exception e) {
+ // }
+ // return new Property(name, type, value);
+ // }
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ PropertyWrapper clone = new PropertyWrapper(this.name, this.type, this.getter,
+ this.setter, this.defaultable, this.validator);
+ clone.value = this.value;
+ clone.changed = this.changed;
+ clone.unique = this.unique;
+ return clone;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (defaultable ? 1231 : 1237);
+ result = prime * result + ((getter == null) ? 0 : getter.hashCode());
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ result = prime * result + ((setter == null) ? 0 : setter.hashCode());
+ result = prime * result + ((type == null) ? 0 : type.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ PropertyWrapper other = (PropertyWrapper) obj;
+ if (defaultable != other.defaultable)
+ return false;
+ if (getter == null) {
+ if (other.getter != null)
+ return false;
+ } else if (!getter.equals(other.getter))
+ return false;
+ if (name == null) {
+ if (other.name != null)
+ return false;
+ } else if (!name.equals(other.name))
+ return false;
+ if (setter == null) {
+ if (other.setter != null)
+ return false;
+ } else if (!setter.equals(other.setter))
+ return false;
+ if (type == null) {
+ if (other.type != null)
+ return false;
+ } else if (!type.equals(other.type))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return name + " = " + value;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/ResistorColorCode.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/ResistorColorCode.java
new file mode 100644
index 00000000..f1b75ffb
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/ResistorColorCode.java
@@ -0,0 +1,14 @@
+package org.diylc.common;
+
+public enum ResistorColorCode {
+
+ NONE, _4_BAND, _5_BAND;
+
+ @Override
+ public String toString() {
+ if (name().startsWith("_")) {
+ return name().substring(1).replace("_", " ").toLowerCase();
+ }
+ return name().substring(0, 1).toUpperCase() + name().substring(1).toLowerCase();
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/VerticalAlignment.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/VerticalAlignment.java
new file mode 100644
index 00000000..7bfc188d
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/common/VerticalAlignment.java
@@ -0,0 +1,9 @@
+package org.diylc.common;
+
+public enum VerticalAlignment {
+ TOP, CENTER, BOTTOM;
+
+ public String toString() {
+ return name().substring(0, 1) + name().substring(1).toLowerCase();
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/components/AbstractComponent.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/components/AbstractComponent.java
new file mode 100644
index 00000000..52c8435b
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/components/AbstractComponent.java
@@ -0,0 +1,248 @@
+package org.diylc.components;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.font.FontRenderContext;
+import java.awt.font.GlyphVector;
+import java.awt.geom.Rectangle2D;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+
+import org.diylc.common.HorizontalAlignment;
+import org.diylc.common.VerticalAlignment;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.annotations.EditableProperty;
+
+/**
+ * Abstract implementation of {@link IDIYComponent} that contains component name
+ * and toString.
+ *
+ * IMPORTANT: to improve performance, all fields except for Point
+ * and Point arrays should be immutable. Failing to comply with
+ * this can result in annoying and hard to trace bugs.
+ *
+ * @author Branislav Stojkovic
+ *
+ * @param
+ */
+public abstract class AbstractComponent implements IDIYComponent {
+
+ private static final long serialVersionUID = 1L;
+
+ protected String name = "";
+
+ public static Color SELECTION_COLOR = Color.red;
+ public static Color LABEL_COLOR = Color.black;
+ public static Color LABEL_COLOR_SELECTED = Color.red;
+ public static Font LABEL_FONT = new Font("Tahoma", Font.PLAIN, 14);
+ public static Color METAL_COLOR = Color.decode("#236B8E");
+
+ @EditableProperty(defaultable = false)
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public boolean canControlPointOverlap(int index) {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ @Override
+ public String getValueForDisplay() {
+ return getValue() == null ? "" : getValue().toString();
+ }
+
+ /**
+ * Returns the closest odd number, i.e. x when x is odd, or x + 1 when x is
+ * even.
+ *
+ * @param x
+ * @return
+ */
+ protected int getClosestOdd(double x) {
+ return ((int) x / 2) * 2 + 1;
+ }
+
+ /**
+ * @param clip
+ * @return true if none of the control points lie in the clip rectangle.
+ */
+ protected boolean checkPointsClipped(Shape clip) {
+ for (int i = 0; i < getControlPointCount(); i++) {
+ if (clip.contains(getControlPoint(i))) {
+ return false;
+ }
+ }
+ int minX = Integer.MAX_VALUE;
+ int minY = Integer.MAX_VALUE;
+ int maxX = Integer.MIN_VALUE;
+ int maxY = Integer.MIN_VALUE;
+ for (int i = 0; i < getControlPointCount(); i++) {
+ Point p = getControlPoint(i);
+ if (minX > p.x) {
+ minX = p.x;
+ }
+ if (maxX < p.x) {
+ maxX = p.x;
+ }
+ if (minY > p.y) {
+ minY = p.y;
+ }
+ if (maxY < p.y) {
+ maxY = p.y;
+ }
+ }
+ Rectangle2D rect = new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
+ return !clip.intersects(rect);
+ }
+
+ protected void drawCenteredText(Graphics2D g2d, String text, int x, int y,
+ HorizontalAlignment horizontalAlignment, VerticalAlignment verticalAlignment) {
+ FontMetrics fontMetrics = g2d.getFontMetrics();
+ Rectangle stringBounds = fontMetrics.getStringBounds(text, g2d).getBounds();
+
+ Font font = g2d.getFont();
+ FontRenderContext renderContext = g2d.getFontRenderContext();
+ GlyphVector glyphVector = font.createGlyphVector(renderContext, text);
+ Rectangle visualBounds = glyphVector.getVisualBounds().getBounds();
+
+ int textX = 0;
+ switch (horizontalAlignment) {
+ case CENTER:
+ textX = x - stringBounds.width / 2;
+ break;
+ case LEFT:
+ textX = x;
+ break;
+ case RIGHT:
+ textX = x - stringBounds.width;
+ break;
+ }
+
+ int textY = 0;
+ switch (verticalAlignment) {
+ case TOP:
+ textY = y + stringBounds.height;
+ break;
+ case CENTER:
+ textY = y - visualBounds.height / 2 - visualBounds.y;
+ break;
+ case BOTTOM:
+ textY = y - visualBounds.y;
+ break;
+ }
+
+ g2d.drawString(text, textX, textY);
+ }
+
+ @SuppressWarnings("unchecked")
+ public IDIYComponent clone() throws CloneNotSupportedException {
+ try {
+ // Instantiate object of the same type
+ AbstractComponent newInstance = (AbstractComponent) this.getClass()
+ .getConstructors()[0].newInstance();
+ Class> clazz = this.getClass();
+ while (AbstractComponent.class.isAssignableFrom(clazz)) {
+ Field[] fields = clazz.getDeclaredFields();
+ clazz = clazz.getSuperclass();
+ // fields = this.getClass().getDeclaredFields();
+ // Copy over all non-static, non-final fields that are declared
+ // in AbstractComponent or one of it's child classes
+ for (Field field : fields) {
+ if (!Modifier.isStatic(field.getModifiers())
+ && !Modifier.isFinal(field.getModifiers())) {
+ field.setAccessible(true);
+ Object value = field.get(this);
+
+ // Deep copy point arrays.
+ // TODO: something nicer
+ if (value != null
+ && value.getClass().isArray()
+ && value.getClass().getComponentType()
+ .isAssignableFrom(Point.class)) {
+ Object newArray = Array.newInstance(
+ value.getClass().getComponentType(), Array.getLength(value));
+ for (int i = 0; i < Array.getLength(value); i++) {
+ Point p = (Point) Array.get(value, i);
+ Array.set(newArray, i, new Point(p));
+ }
+ value = newArray;
+ }
+ // Deep copy points.
+ // TODO: something nicer
+ if (value != null && value instanceof Point) {
+ value = new Point((Point) value);
+ }
+
+ field.set(newInstance, value);
+ }
+ }
+ }
+ return newInstance;
+ } catch (Exception e) {
+ throw new CloneNotSupportedException("Could not clone the component. Reason: "
+ + e.getMessage());
+ }
+ }
+
+ @Override
+ public boolean equalsTo(IDIYComponent> other) {
+ if (other == null)
+ return false;
+ if (!other.getClass().equals(this.getClass()))
+ return false;
+ Class> clazz = this.getClass();
+ while (AbstractComponent.class.isAssignableFrom(clazz)) {
+ Field[] fields = clazz.getDeclaredFields();
+ clazz = clazz.getSuperclass();
+ // fields = this.getClass().getDeclaredFields();
+ // Copy over all non-static, non-final fields that are declared
+ // in
+ // AbstractComponent or one of it's child classes
+ for (Field field : fields) {
+ if (!Modifier.isStatic(field.getModifiers())
+ && !Modifier.isFinal(field.getModifiers())) {
+ field.setAccessible(true);
+ try {
+ Object value = field.get(this);
+ Object otherValue = field.get(other);
+ if (!compareObjects(value, otherValue))
+ return false;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ private boolean compareObjects(Object o1, Object o2) {
+ if (o1 == null && o2 == null)
+ return true;
+ if (o1 == null || o2 == null)
+ return false;
+ if (o1.getClass().isArray()) {
+ return Arrays.equals((Object[])o1, (Object[])o2);
+ }
+ return o1.equals(o2);
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/components/AbstractCurvedComponent.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/components/AbstractCurvedComponent.java
new file mode 100644
index 00000000..b0164317
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/components/AbstractCurvedComponent.java
@@ -0,0 +1,249 @@
+package org.diylc.components;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.geom.CubicCurve2D;
+
+import org.diylc.common.ObjectCache;
+import org.diylc.core.ComponentState;
+import org.diylc.core.IDrawingObserver;
+import org.diylc.core.Project;
+import org.diylc.core.VisibilityPolicy;
+import org.diylc.core.annotations.EditableProperty;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.utils.Constants;
+
+public abstract class AbstractCurvedComponent extends AbstractTransparentComponent {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Color GUIDELINE_COLOR = Color.blue;
+ public static Size DEFAULT_SIZE = new Size(1d, SizeUnit.in);
+
+ protected Point[] controlPoints = new Point[] {
+ new Point(0, 0),
+ new Point((int) (DEFAULT_SIZE.convertToPixels() / 2), 0),
+ new Point((int) (DEFAULT_SIZE.convertToPixels() / 2), (int) (DEFAULT_SIZE
+ .convertToPixels())),
+ new Point((int) DEFAULT_SIZE.convertToPixels(), (int) DEFAULT_SIZE.convertToPixels()) };
+
+ protected Color color = getDefaultColor();
+ protected PointCount pointCount = PointCount.FOUR;
+
+ /**
+ * Draws the specified curve onto graphics.
+ *
+ * @param curve
+ * @param g2d
+ * @param componentState
+ * @param theme
+ */
+ protected abstract void drawCurve(CubicCurve2D curve, Graphics2D g2d,
+ ComponentState componentState);
+
+ /**
+ * @return default color.
+ */
+ protected abstract Color getDefaultColor();
+
+ @Override
+ public void drawIcon(Graphics2D g2d, int width, int height) {
+ g2d.setColor(getDefaultColor().darker());
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(3));
+ CubicCurve2D curve = new CubicCurve2D.Double(1, height - 1, width / 4, height / 3,
+ 3 * width / 4, 2 * height / 3, width - 1, 1);
+ g2d.draw(curve);
+ g2d.setColor(getDefaultColor());
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1));
+ g2d.draw(curve);
+ }
+
+ @Override
+ public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode,
+ Project project, IDrawingObserver drawingObserver) {
+ if (checkPointsClipped(g2d.getClip())) {
+ return;
+ }
+ if (componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING) {
+ // Do not track guidelines.
+ drawingObserver.stopTracking();
+ g2d.setStroke(Constants.DASHED_STROKE);
+ g2d.setColor(GUIDELINE_COLOR);
+ g2d.drawLine(controlPoints[0].x, controlPoints[0].y, controlPoints[1].x,
+ controlPoints[1].y);
+ g2d.drawLine(controlPoints[1].x, controlPoints[1].y, controlPoints[2].x,
+ controlPoints[2].y);
+ g2d.drawLine(controlPoints[2].x, controlPoints[2].y, controlPoints[3].x,
+ controlPoints[3].y);
+ drawingObserver.startTracking();
+ }
+ CubicCurve2D curve = new CubicCurve2D.Double(controlPoints[0].x, controlPoints[0].y,
+ controlPoints[1].x, controlPoints[1].y, controlPoints[2].x, controlPoints[2].y,
+ controlPoints[3].x, controlPoints[3].y);
+
+ Composite oldComposite = g2d.getComposite();
+ if (alpha < MAX_ALPHA) {
+ g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f * alpha
+ / MAX_ALPHA));
+ }
+ // g2d.setColor(color.darker());
+ // g2d.setStroke(new BasicStroke(thickness));
+ // g2d.draw(path);
+ // g2d.setColor(color);
+ // g2d.setStroke(new BasicStroke(thickness - 2));
+ // g2d.draw(path);
+ drawCurve(curve, g2d, componentState);
+ g2d.setComposite(oldComposite);
+ }
+
+ @EditableProperty(name = "Point Count")
+ public PointCount getPointCount() {
+ if (pointCount == null) {
+ pointCount = PointCount.FOUR;
+ }
+ return pointCount;
+ }
+
+ public void setPointCount(PointCount pointCount) {
+ this.pointCount = pointCount;
+ // Reset control points.
+ for (int i = 0; i < getControlPointCount(); i++) {
+ setControlPoint(getControlPoint(i), i);
+ }
+ }
+
+ @Override
+ public int getControlPointCount() {
+ switch (getPointCount()) {
+ case TWO:
+ return 2;
+ case THREE:
+ return 3;
+ case FOUR:
+ return 4;
+ }
+ return 0;
+ }
+
+ @Override
+ public boolean isControlPointSticky(int index) {
+ return index == 0 || index == getControlPointCount() - 1;
+ }
+
+ @Override
+ public VisibilityPolicy getControlPointVisibilityPolicy(int index) {
+ return VisibilityPolicy.WHEN_SELECTED;
+ }
+
+ @Override
+ public boolean canControlPointOverlap(int index) {
+ // Only shape control points may overlap.
+ switch (getPointCount()) {
+ case TWO:
+ return false;
+ case THREE:
+ return index == 1;
+ case FOUR:
+ return index == 1 || index == 2;
+ }
+ return false;
+ }
+
+ @Override
+ public Point getControlPoint(int index) {
+ if (index == 0) {
+ return controlPoints[0];
+ }
+ switch (getPointCount()) {
+ case TWO:
+ switch (index) {
+ case 1:
+ return controlPoints[3];
+ }
+ case THREE:
+ switch (index) {
+ case 1:
+ return controlPoints[1];
+ case 2:
+ return controlPoints[3];
+ }
+ case FOUR:
+ switch (index) {
+ case 1:
+ return controlPoints[1];
+ case 2:
+ return controlPoints[2];
+ case 3:
+ return controlPoints[3];
+ }
+ }
+ return controlPoints[index];
+ }
+
+ @Override
+ public void setControlPoint(Point point, int index) {
+ if (index == 0) {
+ controlPoints[0].setLocation(point);
+ } else {
+ switch (getPointCount()) {
+ case TWO:
+ switch (index) {
+ case 1:
+ Point center = new Point((point.x + controlPoints[0].x) / 2,
+ (point.y + controlPoints[0].y) / 2);
+ controlPoints[1].setLocation(center);
+ controlPoints[2].setLocation(center);
+ controlPoints[3].setLocation(point);
+ break;
+ }
+ break;
+ case THREE:
+ switch (index) {
+ case 1:
+ controlPoints[1].setLocation(point);
+ controlPoints[2].setLocation(point);
+ break;
+ case 2:
+ controlPoints[3].setLocation(point);
+ break;
+ }
+ break;
+ case FOUR:
+ switch (index) {
+ case 1:
+ controlPoints[1].setLocation(point);
+ break;
+ case 2:
+ controlPoints[2].setLocation(point);
+ break;
+ case 3:
+ controlPoints[3].setLocation(point);
+ break;
+ }
+ break;
+ }
+ }
+ }
+
+ @EditableProperty
+ public Color getColor() {
+ return color;
+ }
+
+ public void setColor(Color color) {
+ this.color = color;
+ }
+
+ enum PointCount {
+ TWO, THREE, FOUR;
+
+ @Override
+ public String toString() {
+ return name().substring(0, 1) + name().substring(1).toLowerCase();
+ }
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/components/AbstractLeadedComponent.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/components/AbstractLeadedComponent.java
new file mode 100644
index 00000000..df01ae57
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/components/AbstractLeadedComponent.java
@@ -0,0 +1,492 @@
+package org.diylc.components;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Rectangle2D;
+
+import org.diylc.appframework.miscutils.ConfigurationManager;
+import org.diylc.common.Display;
+import org.diylc.common.IPlugInPort;
+import org.diylc.common.ObjectCache;
+import org.diylc.core.ComponentState;
+import org.diylc.core.IDrawingObserver;
+import org.diylc.core.Project;
+import org.diylc.core.Theme;
+import org.diylc.core.VisibilityPolicy;
+import org.diylc.core.annotations.EditableProperty;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.utils.Constants;
+
+/**
+ * Base class for all leaded components such as resistors or capacitors. Has two
+ * control points and draws leads between them. Also, it positions and draws the
+ * shape of the component as specified by a child class.
+ *
+ * @author Branislav Stojkovic
+ */
+public abstract class AbstractLeadedComponent extends
+ AbstractTransparentComponent {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Color LEAD_COLOR = Color.decode("#CCCCCC");
+ public static Color LEAD_COLOR_ICON = LEAD_COLOR.darker().darker();
+ public static Size LEAD_THICKNESS = new Size(0.6d, SizeUnit.mm);
+ public static Size DEFAULT_SIZE = new Size(1d, SizeUnit.in);
+
+ protected Size length;
+ protected Size width;
+ protected Point[] points = new Point[] {
+ new Point((int) (-DEFAULT_SIZE.convertToPixels() / 2), 0),
+ new Point((int) (DEFAULT_SIZE.convertToPixels() / 2), 0) };
+ protected Color bodyColor = Color.white;
+ protected Color borderColor = Color.black;
+ protected Color labelColor = LABEL_COLOR;
+ protected Color leadColor = LEAD_COLOR;
+ protected Display display = Display.NAME;
+ private boolean flipStanding = false;
+
+ protected AbstractLeadedComponent() {
+ super();
+ try {
+ this.length = getDefaultLength().clone();
+ this.width = getDefaultWidth().clone();
+ } catch (CloneNotSupportedException e) {
+ // This should never happen because Size supports cloning.
+ } catch (NullPointerException e) {
+ // This will happen if components do not have any shape.
+ }
+ }
+
+ @Override
+ public void draw(Graphics2D g2d, ComponentState componentState,
+ boolean outlineMode, Project project,
+ IDrawingObserver drawingObserver) {
+ double distance = points[0].distance(points[1]);
+ Shape shape = getBodyShape();
+ // If there's no body, just draw the line connecting the ending points.
+ if (shape == null) {
+ drawLead(g2d, componentState);
+ } else if (supportsStandingMode()
+ && length.convertToPixels() > points[0].distance(points[1])) {
+ // When ending points are too close draw the component in standing
+ // mode.
+ int width = getClosestOdd(this.width.convertToPixels());
+ Shape body = new Ellipse2D.Double((getFlipStanding() ? points[1]
+ : points[0]).x
+ - width / 2, (getFlipStanding() ? points[1] : points[0]).y
+ - width / 2, width, width);
+ Composite oldComposite = g2d.getComposite();
+ if (alpha < MAX_ALPHA) {
+ g2d.setComposite(AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, 1f * alpha / MAX_ALPHA));
+ }
+ g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR
+ : getStandingBodyColor());
+ g2d.fill(body);
+ g2d.setComposite(oldComposite);
+ Color finalBorderColor;
+ if (outlineMode) {
+ Theme theme = (Theme) ConfigurationManager.getInstance()
+ .readObject(IPlugInPort.THEME_KEY,
+ Constants.DEFAULT_THEME);
+ finalBorderColor = componentState == ComponentState.SELECTED
+ || componentState == ComponentState.DRAGGING ? SELECTION_COLOR
+ : theme.getOutlineColor();
+ } else {
+ finalBorderColor = componentState == ComponentState.SELECTED
+ || componentState == ComponentState.DRAGGING ? SELECTION_COLOR
+ : borderColor;
+ }
+
+ g2d.setColor(finalBorderColor);
+ g2d.draw(body);
+ if (!outlineMode) {
+ drawLead(g2d, componentState);
+ }
+ } else {
+ // Normal mode with component body in the center and two lead parts.
+ Rectangle shapeRect = shape.getBounds();
+ Double theta = Math.atan2(points[1].y - points[0].y, points[1].x
+ - points[0].x);
+ // Go back to the original transformation to draw leads.
+ if (!outlineMode) {
+ AffineTransform textTransform = g2d.getTransform();
+ // if (length.convertToPixels() > points[0].distance(points[1]))
+ // {
+ // g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
+ // 0.5f));
+ // }
+ int leadThickness = getClosestOdd(getLeadThickness());
+ double leadLength = (distance - calculatePinSpacing(shapeRect))
+ / 2 - leadThickness / 2;
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(
+ leadThickness));
+ Color leadColor = shouldShadeLeads() ? getLeadColorForPainting(
+ componentState).darker()
+ : getLeadColorForPainting(componentState);
+ g2d.setColor(leadColor);
+ int endX = (int) (points[0].x + Math.cos(theta) * leadLength);
+ int endY = (int) Math.round(points[0].y + Math.sin(theta)
+ * leadLength);
+ g2d.drawLine(points[0].x, points[0].y, endX, endY);
+ endX = (int) (points[1].x + Math.cos(theta - Math.PI)
+ * leadLength);
+ endY = (int) Math.round(points[1].y + Math.sin(theta - Math.PI)
+ * leadLength);
+ g2d.drawLine(points[1].x, points[1].y, endX, endY);
+ if (shouldShadeLeads()) {
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(
+ leadThickness - 2));
+ leadColor = getLeadColorForPainting(componentState);
+ g2d.setColor(leadColor);
+ g2d.drawLine(points[0].x, points[0].y,
+ (int) (points[0].x + Math.cos(theta) * leadLength),
+ (int) (points[0].y + Math.sin(theta) * leadLength));
+ g2d.drawLine(points[1].x, points[1].y,
+ (int) (points[1].x + Math.cos(theta - Math.PI)
+ * leadLength), (int) (points[1].y + Math
+ .sin(theta - Math.PI)
+ * leadLength));
+ }
+ g2d.setTransform(textTransform);
+ }
+ // Transform graphics to draw the body in the right place and at the
+ // right angle.
+ AffineTransform oldTransform = null;
+ double width;
+ double length;
+ if (useShapeRectAsPosition()) {
+ width = shapeRect.getHeight();
+ length = shapeRect.getWidth();
+ } else {
+ width = getWidth().convertToPixels();
+ length = getLength().convertToPixels();
+ }
+ oldTransform = g2d.getTransform();
+ g2d.translate((points[0].x + points[1].x - length) / 2,
+ (points[0].y + points[1].y - width) / 2);
+ g2d.rotate(theta, length / 2, width / 2);
+ // Draw body.
+ Composite oldComposite = g2d.getComposite();
+ if (alpha < MAX_ALPHA) {
+ g2d.setComposite(AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, 1f * alpha / MAX_ALPHA));
+ }
+ if (bodyColor != null) {
+ if (bodyColor != null) {
+ g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR
+ : bodyColor);
+ g2d.fill(shape);
+ }
+ }
+ decorateComponentBody(g2d, outlineMode);
+ g2d.setComposite(oldComposite);
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1));
+ Color finalBorderColor;
+ if (outlineMode) {
+ Theme theme = (Theme) ConfigurationManager.getInstance()
+ .readObject(IPlugInPort.THEME_KEY,
+ Constants.DEFAULT_THEME);
+ finalBorderColor = componentState == ComponentState.SELECTED
+ || componentState == ComponentState.DRAGGING ? SELECTION_COLOR
+ : theme.getOutlineColor();
+ } else {
+ finalBorderColor = componentState == ComponentState.SELECTED
+ || componentState == ComponentState.DRAGGING ? SELECTION_COLOR
+ : borderColor;
+ }
+ g2d.setColor(finalBorderColor);
+ g2d.draw(shape);
+
+ // // Go back to the original transformation to draw leads.
+ // if (!outlineMode) {
+ // AffineTransform textTransform = g2d.getTransform();
+ // g2d.setTransform(oldTransform);
+ // if (length.convertToPixels() > points[0].distance(points[1])) {
+ // g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
+ // 0.5f));
+ // }
+ // int leadThickness = getClosestOdd(getLeadThickness());
+ // double leadLength = (distance - calculatePinSpacing(shapeRect)) /
+ // 2 - leadThickness / 2;
+ // g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(leadThickness));
+ // Color leadColor = shouldShadeLeads() ?
+ // getLeadColor(componentState).darker()
+ // : getLeadColor(componentState);
+ // g2d.setColor(leadColor);
+ // int endX = (int) (points[0].x + Math.cos(theta) * leadLength);
+ // int endY = (int) Math.round(points[0].y + Math.sin(theta) *
+ // leadLength);
+ // g2d.drawLine(points[0].x, points[0].y, endX, endY);
+ // endX = (int) (points[1].x + Math.cos(theta - Math.PI) *
+ // leadLength);
+ // endY = (int) Math.round(points[1].y + Math.sin(theta - Math.PI) *
+ // leadLength);
+ // g2d.drawLine(points[1].x, points[1].y, endX, endY);
+ // if (shouldShadeLeads()) {
+ // g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(leadThickness
+ // - 2));
+ // leadColor = getLeadColor(componentState);
+ // g2d.setColor(leadColor);
+ // g2d.drawLine(points[0].x, points[0].y, (int) (points[0].x +
+ // Math.cos(theta)
+ // * leadLength), (int) (points[0].y + Math.sin(theta) *
+ // leadLength));
+ // g2d.drawLine(points[1].x, points[1].y, (int) (points[1].x +
+ // Math.cos(theta
+ // - Math.PI)
+ // * leadLength), (int) (points[1].y + Math.sin(theta - Math.PI)
+ // * leadLength));
+ // }
+ // g2d.setComposite(oldComposite);
+ // g2d.setTransform(textTransform);
+ // }
+
+ // Draw label.
+ g2d.setFont(LABEL_FONT);
+ if (useShapeRectAsPosition()) {
+ g2d.translate(shapeRect.x, shapeRect.y);
+ }
+ Color finalLabelColor;
+ if (outlineMode) {
+ Theme theme = (Theme) ConfigurationManager.getInstance()
+ .readObject(IPlugInPort.THEME_KEY,
+ Constants.DEFAULT_THEME);
+ finalLabelColor = componentState == ComponentState.SELECTED
+ || componentState == ComponentState.DRAGGING ? LABEL_COLOR_SELECTED
+ : theme.getOutlineColor();
+ } else {
+ finalLabelColor = componentState == ComponentState.SELECTED
+ || componentState == ComponentState.DRAGGING ? LABEL_COLOR_SELECTED
+ : labelColor;
+ }
+ g2d.setColor(finalLabelColor);
+ FontMetrics fontMetrics = g2d.getFontMetrics();
+ String label = display == Display.NAME ? getName()
+ : (getValue() == null ? "" : getValue().toString());
+ Rectangle2D textRect = fontMetrics.getStringBounds(label, g2d);
+ // Don't offset in outline mode.
+ int offset = outlineMode ? 0 : getLabelOffset((int) length,
+ (int) width);
+ // Adjust label angle if needed to make sure that it's readable.
+ if ((theta >= Math.PI / 2 && theta <= Math.PI)
+ || (theta < -Math.PI / 2 && theta > -Math.PI)) {
+ g2d.rotate(Math.PI, length / 2, width / 2);
+ offset = -offset;
+ }
+ g2d.drawString(label, (int) (length - textRect.getWidth()) / 2
+ + offset, calculateLabelYCoordinate(shapeRect, textRect,
+ fontMetrics));
+ g2d.setTransform(oldTransform);
+ }
+ }
+
+ private void drawLead(Graphics2D g2d, ComponentState componentState) {
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(
+ getLeadThickness()));
+ Color leadColor = shouldShadeLeads() ? getLeadColorForPainting(
+ componentState).darker()
+ : getLeadColorForPainting(componentState);
+ g2d.setColor(leadColor);
+ g2d.drawLine(points[0].x, points[0].y, points[1].x, points[1].y);
+ if (shouldShadeLeads()) {
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(
+ getLeadThickness() - 2));
+ leadColor = getLeadColorForPainting(componentState);
+ g2d.setColor(leadColor);
+ g2d.drawLine(points[0].x, points[0].y, points[1].x, points[1].y);
+ }
+ }
+
+ protected void decorateComponentBody(Graphics2D g2d, boolean outlineMode) {
+ // Do nothing.
+ }
+
+ protected int calculateLabelYCoordinate(Rectangle2D shapeRect,
+ Rectangle2D textRect, FontMetrics fontMetrics) {
+ return (int) (shapeRect.getHeight() - textRect.getHeight()) / 2
+ + fontMetrics.getAscent();
+ }
+
+ protected boolean shouldShadeLeads() {
+ return true;
+ }
+
+ protected boolean supportsStandingMode() {
+ return false;
+ }
+
+ protected int getLabelOffset(int bodyWidth, int labelWidth) {
+ return 0;
+ }
+
+ /**
+ * @return default component length.
+ */
+ protected abstract Size getDefaultLength();
+
+ /**
+ * Returns default component width.
+ *
+ * @return
+ */
+ protected abstract Size getDefaultWidth();
+
+ /**
+ * @return shape that represents component body. Shape should not be
+ * transformed and should be referenced to (0, 0).
+ */
+ protected abstract Shape getBodyShape();
+
+ /**
+ * Controls how component shape should be placed relative to start and end
+ * point.
+ *
+ * @return
+ * true if shape rect should be used to center the component or false
+ * to place the component relative to length and
+ * width values.
+ */
+ protected boolean useShapeRectAsPosition() {
+ return true;
+ }
+
+ /**
+ * @return default lead thickness. Override this method to change it.
+ */
+ protected int getLeadThickness() {
+ return (int) LEAD_THICKNESS.convertToPixels();
+ }
+
+ /**
+ * @return default lead color. Override this method to change it.
+ */
+ protected Color getLeadColorForPainting(ComponentState componentState) {
+ return componentState == ComponentState.SELECTED
+ || componentState == ComponentState.DRAGGING ? SELECTION_COLOR
+ : getLeadColor();
+ }
+
+ @EditableProperty(name = "Lead color")
+ public Color getLeadColor() {
+ if (leadColor == null) {
+ leadColor = LEAD_COLOR_ICON;
+ }
+ return leadColor;
+ }
+
+ public void setLeadColor(Color leadColor) {
+ this.leadColor = leadColor;
+ }
+
+ protected int calculatePinSpacing(Rectangle shapeRect) {
+ return shapeRect.width;
+ }
+
+ @Override
+ public int getControlPointCount() {
+ return points.length;
+ }
+
+ @Override
+ public Point getControlPoint(int index) {
+ return (Point) points[index];
+ }
+
+ @Override
+ public boolean isControlPointSticky(int index) {
+ return true;
+ }
+
+ @Override
+ public VisibilityPolicy getControlPointVisibilityPolicy(int index) {
+ return VisibilityPolicy.ALWAYS;
+ }
+
+ @Override
+ public void setControlPoint(Point point, int index) {
+ points[index].setLocation(point);
+ }
+
+ @EditableProperty(name = "Color")
+ public Color getBodyColor() {
+ return bodyColor;
+ }
+
+ public void setBodyColor(Color bodyColor) {
+ this.bodyColor = bodyColor;
+ }
+
+ public Color getStandingBodyColor() {
+ return bodyColor;
+ }
+
+ @EditableProperty(name = "Border")
+ public Color getBorderColor() {
+ return borderColor;
+ }
+
+ public void setBorderColor(Color borderColor) {
+ this.borderColor = borderColor;
+ }
+
+ @EditableProperty(name = "Length", defaultable = true)
+ public Size getLength() {
+ return length;
+ }
+
+ public void setLength(Size length) {
+ this.length = length;
+ }
+
+ @EditableProperty(name = "Width", defaultable = true)
+ public Size getWidth() {
+ return width;
+ }
+
+ public void setWidth(Size width) {
+ this.width = width;
+ }
+
+ @EditableProperty
+ public Display getDisplay() {
+ return display;
+ }
+
+ public void setDisplay(Display display) {
+ this.display = display;
+ }
+
+ @EditableProperty(name = "Label color")
+ public Color getLabelColor() {
+ return labelColor;
+ }
+
+ public void setLabelColor(Color labelColor) {
+ this.labelColor = labelColor;
+ }
+
+ /**
+ * Override this method with @EditableProperty annotation in child classes
+ * where standing mode is supported
+ *
+ * @return
+ */
+ public boolean getFlipStanding() {
+ return flipStanding;
+ }
+
+ public void setFlipStanding(boolean flipStanding) {
+ this.flipStanding = flipStanding;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/components/AbstractRadialComponent.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/components/AbstractRadialComponent.java
new file mode 100644
index 00000000..c5bc9688
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/components/AbstractRadialComponent.java
@@ -0,0 +1,43 @@
+package org.diylc.components;
+
+import java.awt.Rectangle;
+
+import org.diylc.core.annotations.EditableProperty;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+
+/**
+ * Base class for radial components. The same as {@link AbstractRadialComponent}
+ * but with added pin spacing.
+ *
+ * @author bancika
+ *
+ * @param
+ */
+public abstract class AbstractRadialComponent extends
+ AbstractLeadedComponent {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Size PIN_SPACING = new Size(0.1d, SizeUnit.in);
+
+ private Size pinSpacing = PIN_SPACING;
+
+ @Override
+ protected int calculatePinSpacing(Rectangle shapeRect) {
+ return getClosestOdd(getPinSpacing().convertToPixels());
+ }
+
+ @EditableProperty(name = "Pin spacing")
+ public Size getPinSpacing() {
+ if (pinSpacing == null) {
+ pinSpacing = PIN_SPACING;
+ }
+ return pinSpacing;
+ }
+
+ public void setPinSpacing(Size pinSpacing) {
+ this.pinSpacing = pinSpacing;
+ }
+
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/components/AbstractSchematicLeadedSymbol.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/components/AbstractSchematicLeadedSymbol.java
new file mode 100644
index 00000000..2eaa652e
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/components/AbstractSchematicLeadedSymbol.java
@@ -0,0 +1,73 @@
+package org.diylc.components;
+
+import java.awt.Color;
+import java.awt.FontMetrics;
+import java.awt.geom.Rectangle2D;
+
+import org.diylc.common.LabelPosition;
+import org.diylc.core.ComponentState;
+import org.diylc.core.annotations.EditableProperty;
+
+public abstract class AbstractSchematicLeadedSymbol extends AbstractLeadedComponent {
+
+ private static final long serialVersionUID = 1L;
+
+ public static Color COLOR = Color.blue;
+ public static Color LEAD_COLOR = Color.black;
+
+ protected LabelPosition labelPosition = LabelPosition.ABOVE;
+
+ public AbstractSchematicLeadedSymbol() {
+ super();
+ // We don't want to fill the body, so use null.
+ this.bodyColor = null;
+ this.leadColor = LEAD_COLOR;
+ this.borderColor = COLOR;
+ }
+
+ @Override
+ public Color getBodyColor() {
+ return super.getBodyColor();
+ }
+
+ @Override
+ protected boolean shouldShadeLeads() {
+ return false;
+ }
+
+ @Override
+ protected int getLeadThickness() {
+ return 1;
+ }
+
+ @Override
+ protected int calculateLabelYCoordinate(Rectangle2D shapeRect, Rectangle2D textRect,
+ FontMetrics fontMetrics) {
+ if (labelPosition == LabelPosition.ABOVE) {
+ return -1;
+ } else {
+ return (int) (shapeRect.getHeight() + textRect.getHeight() - 1);
+ }
+ }
+
+ @EditableProperty(name = "Label position")
+ public LabelPosition getLabelPosition() {
+ return labelPosition;
+ }
+
+ @Override
+ @EditableProperty(name = "Color")
+ public Color getBorderColor() {
+ return super.getBorderColor();
+ }
+
+ public void setLabelPosition(LabelPosition labelPosition) {
+ this.labelPosition = labelPosition;
+ }
+
+ @Override
+ protected Color getLeadColorForPainting(ComponentState componentState) {
+ return componentState == ComponentState.SELECTED
+ || componentState == ComponentState.DRAGGING ? SELECTION_COLOR : getLeadColor();
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/components/AbstractTransparentComponent.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/components/AbstractTransparentComponent.java
new file mode 100644
index 00000000..f13da970
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/components/AbstractTransparentComponent.java
@@ -0,0 +1,21 @@
+package org.diylc.components;
+
+import org.diylc.core.annotations.EditableProperty;
+
+public abstract class AbstractTransparentComponent extends AbstractComponent {
+
+ private static final long serialVersionUID = 1L;
+
+ public static byte MAX_ALPHA = 127;
+
+ protected byte alpha = MAX_ALPHA;
+
+ @EditableProperty
+ public Byte getAlpha() {
+ return alpha;
+ }
+
+ public void setAlpha(Byte alpha) {
+ this.alpha = alpha;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/AllowAllValidator.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/AllowAllValidator.java
new file mode 100644
index 00000000..0b194256
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/AllowAllValidator.java
@@ -0,0 +1,13 @@
+package org.diylc.core;
+
+/**
+ * Validator that allows all values.
+ *
+ * @author Branislav Stojkovic
+ */
+public class AllowAllValidator implements IPropertyValidator {
+
+ @Override
+ public void validate(Object value) throws ValidationException {
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/ComponentState.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/ComponentState.java
new file mode 100644
index 00000000..ec33071b
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/ComponentState.java
@@ -0,0 +1,11 @@
+package org.diylc.core;
+
+/**
+ * Enumerates all possible states that a component can be in.
+ *
+ * @author Branislav Stojkovic
+ */
+public enum ComponentState {
+
+ NORMAL, SELECTED, LOCKED, DRAGGING
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/CreationMethod.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/CreationMethod.java
new file mode 100644
index 00000000..e8c44864
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/CreationMethod.java
@@ -0,0 +1,6 @@
+package org.diylc.core;
+
+public enum CreationMethod {
+
+ SINGLE_CLICK, POINT_BY_POINT;
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/ExpansionMode.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/ExpansionMode.java
new file mode 100644
index 00000000..1de715af
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/ExpansionMode.java
@@ -0,0 +1,8 @@
+package org.diylc.core;
+
+public enum ExpansionMode {
+
+ ALL,
+ SAME_TYPE,
+ IMMEDIATE,
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/IDIYComponent.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/IDIYComponent.java
new file mode 100644
index 00000000..a5c963eb
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/IDIYComponent.java
@@ -0,0 +1,143 @@
+package org.diylc.core;
+
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.io.Serializable;
+
+import org.diylc.core.annotations.ComponentDescriptor;
+import org.diylc.core.annotations.EditableProperty;
+
+/**
+ * Interface for component instance. Implementation classes of this interface
+ * will be instantiated by the application when component is added to the
+ * canvas.
+ *
+ * Implementing classes should meet the following:
+ *
+ * - Must have an empty constructor.
+ * - Class should be annotated with {@link ComponentDescriptor}.
+ * - Getters for properties editable by users should be annotated with
+ * {@link EditableProperty}.
+ * - Component configuration should be stored int
public static
+ * fields so they can be set through config file.
+ *
+ *
+ * @author Branislav Stojkovic
+ *
+ * @param
+ * type of component values, e.g. Resistance for resistors or String
+ * for transistors.
+ */
+public interface IDIYComponent extends Serializable {
+
+ public static final int CHASSIS = 1;
+ public static final int BOARD = 2;
+ public static final int TRACE = 3;
+ public static final int COMPONENT = 4;
+ public static final int TEXT = 5;
+
+ /**
+ * @return component instance name.
+ */
+ String getName();
+
+ /**
+ * Updates component instance name.
+ *
+ * @param name
+ */
+ void setName(String name);
+
+ /**
+ * @return component value.
+ */
+ T getValue();
+
+ /**
+ * Updates component value.
+ *
+ * @param value
+ */
+ void setValue(T value);
+
+ /**
+ * @return number of control points for this component instance. May vary
+ * between two instances of the same type, e.g. DIL IC with 8 and 16
+ * pins will have 8 or 16 pins although they are of the same type.
+ */
+ int getControlPointCount();
+
+ /**
+ * @param index
+ * @return control point at the specified index.
+ */
+ Point getControlPoint(int index);
+
+ /**
+ * Updates the control point at the specified index.
+ *
+ * @param point
+ * @param index
+ */
+ void setControlPoint(Point point, int index);
+
+ /**
+ * @param index
+ * @return true, if the specified control point may stick to control points
+ * of other components.
+ */
+ boolean isControlPointSticky(int index);
+
+ /**
+ * @param index
+ * @return true, if the specified control point may overlap with other
+ * control points of the same component. The other control
+ * point must be able to overlap too.
+ */
+ boolean canControlPointOverlap(int index);
+
+ VisibilityPolicy getControlPointVisibilityPolicy(int index);
+
+ /**
+ * Draws the component onto the {@link Graphics2D}.
+ *
+ * @param g2d
+ * @param componentState
+ * @param outlineMode
+ * @param project
+ * @param drawingObserver
+ */
+ void draw(Graphics2D g2d, ComponentState componentState,
+ boolean outlineMode, Project project,
+ IDrawingObserver drawingObserver);
+
+ /**
+ * Draws icon representation of the component. This should not depend on
+ * component state, i.e. it should be treated as a static method.
+ *
+ * @param g2d
+ * @param width
+ * @param height
+ */
+ void drawIcon(Graphics2D g2d, int width, int height);
+
+ /**
+ * Clones the component.
+ *
+ * @return
+ */
+ IDIYComponent clone() throws CloneNotSupportedException;
+
+ /**
+ * @return full value for BOM.
+ */
+ String getValueForDisplay();
+
+ /**
+ * Checks if two components are equal.
+ *
+ * @param other
+ * @return
+ */
+ boolean equalsTo(IDIYComponent> other);
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/IDrawingObserver.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/IDrawingObserver.java
new file mode 100644
index 00000000..8db76e21
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/IDrawingObserver.java
@@ -0,0 +1,15 @@
+package org.diylc.core;
+
+/**
+ * Interface for tracking component drawing process. Components may use it to
+ * stop or restart tracking. Anything drawn while tracking is stopped will not
+ * be added to the mouse hot-spot area.
+ *
+ * @author Branislav Stojkovic
+ */
+public interface IDrawingObserver {
+
+ void stopTracking();
+
+ void startTracking();
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/IPropertyValidator.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/IPropertyValidator.java
new file mode 100644
index 00000000..9dbdee24
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/IPropertyValidator.java
@@ -0,0 +1,16 @@
+package org.diylc.core;
+
+/**
+ * Interface for validating property value changes.
+ *
+ * @author Branislav Stojkovic
+ */
+public interface IPropertyValidator {
+
+ /**
+ * @param value
+ * @throws ValidationException
+ * if validation fails. Message will contain the reason.
+ */
+ void validate(Object value) throws ValidationException;
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/IView.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/IView.java
new file mode 100644
index 00000000..97e0274d
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/IView.java
@@ -0,0 +1,41 @@
+package org.diylc.core;
+
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+
+import org.diylc.common.PropertyWrapper;
+
+
+/**
+ * Base interface for the main GUI component.
+ *
+ * @author Branislav Stojkovic
+ */
+public interface IView {
+
+ public static final int ERROR_MESSAGE = 0;
+ public static final int INFORMATION_MESSAGE = 1;
+ public static final int WARNING_MESSAGE = 2;
+ public static final int QUESTION_MESSAGE = 3;
+ public static final int PLAIN_MESSAGE = -1;
+ public static final int DEFAULT_OPTION = -1;
+ public static final int YES_NO_OPTION = 0;
+ public static final int YES_NO_CANCEL_OPTION = 1;
+ public static final int OK_CANCEL_OPTION = 2;
+ public static final int YES_OPTION = 0;
+ public static final int NO_OPTION = 1;
+ public static final int CANCEL_OPTION = 2;
+ public static final int OK_OPTION = 0;
+
+ public static final String CHECK_BOX_MENU_ITEM = "org.diylc.checkBoxMenuItem";
+ public static final String RADIO_BUTTON_GROUP_KEY = "org.diylc.radioButtonGroup";
+
+ void showMessage(String message, String title, int messageType);
+
+ int showConfirmDialog(String message, String title, int optionType, int messageType);
+
+ boolean editProperties(List properties, Set defaultedProperties);
+
+ File promptFileSave();
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/Project.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/Project.java
new file mode 100644
index 00000000..3cb15555
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/Project.java
@@ -0,0 +1,259 @@
+package org.diylc.core;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.diylc.appframework.update.VersionNumber;
+import org.diylc.core.annotations.EditableProperty;
+import org.diylc.core.annotations.PositiveMeasureValidator;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+
+/**
+ * Entity class that defines a project. Contains project properties and a
+ * collection of components.This class is serialized to file. Some filed getters
+ * are tagged with {@link EditableProperty} to enable for user to edit them.
+ *
+ * @author Branislav Stojkovic
+ */
+public class Project implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ public static String DEFAULT_TITLE = "New Project";
+ public static Size DEFAULT_WIDTH = new Size(29d, SizeUnit.cm);
+ public static Size DEFAULT_HEIGHT = new Size(21d, SizeUnit.cm);
+ public static Size DEFAULT_GRID_SPACING = new Size(0.1d, SizeUnit.in);
+
+ private VersionNumber fileVersion;
+
+ private String title;
+ private String author;
+ private String description;
+ private Size width;
+ private Size height;
+ private Size gridSpacing;
+ private List> components;
+ private Set>> groups;
+ private Set lockedLayers;
+
+ public Project() {
+ components = new ArrayList>();
+ groups = new HashSet>>();
+ lockedLayers = new HashSet();
+ title = DEFAULT_TITLE;
+ author = System.getProperty("user.name");
+ width = DEFAULT_WIDTH;
+ height = DEFAULT_HEIGHT;
+ gridSpacing = DEFAULT_GRID_SPACING;
+ }
+
+ @EditableProperty(defaultable = false)
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ @EditableProperty
+ public String getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(String author) {
+ this.author = author;
+ }
+
+ @EditableProperty(defaultable = false)
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @EditableProperty
+ public Size getWidth() {
+ return width;
+ }
+
+ public void setWidth(Size width) {
+ this.width = width;
+ }
+
+ @EditableProperty
+ public Size getHeight() {
+ return height;
+ }
+
+ public void setHeight(Size height) {
+ this.height = height;
+ }
+
+ @EditableProperty(name = "Grid spacing", validatorClass = SpacingValidator.class)
+ public Size getGridSpacing() {
+ return gridSpacing;
+ }
+
+ public void setGridSpacing(Size gridSpacing) {
+ this.gridSpacing = gridSpacing;
+ }
+
+ /**
+ * List of components sorted by z-order ascending.
+ *
+ * @return
+ */
+ public List> getComponents() {
+ return components;
+ }
+
+ /**
+ * Set of grouped components.
+ *
+ * @return
+ */
+ public Set>> getGroups() {
+ return groups;
+ }
+
+ public Set getLockedLayers() {
+ return lockedLayers;
+ }
+
+ public VersionNumber getFileVersion() {
+ return fileVersion;
+ }
+
+ public void setFileVersion(VersionNumber fileVersion) {
+ this.fileVersion = fileVersion;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((author == null) ? 0 : author.hashCode());
+ result = prime * result + ((components == null) ? 0 : components.hashCode());
+ result = prime * result + ((description == null) ? 0 : description.hashCode());
+ result = prime * result + ((height == null) ? 0 : height.hashCode());
+ result = prime * result + ((title == null) ? 0 : title.hashCode());
+ result = prime * result + ((width == null) ? 0 : width.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Project other = (Project) obj;
+ if (author == null) {
+ if (other.author != null)
+ return false;
+ } else if (!author.equals(other.author))
+ return false;
+ if (components == null) {
+ if (other.components != null)
+ return false;
+ } else if (components.size() != other.components.size()) {
+ return false;
+ } else {
+ Iterator> i1 = components.iterator();
+ Iterator> i2 = other.components.iterator();
+ while (i1.hasNext()) {
+ IDIYComponent> c1 = i1.next();
+ IDIYComponent> c2 = i2.next();
+ if (!c1.equalsTo(c2))
+ return false;
+ }
+ }
+ if (description == null) {
+ if (other.description != null)
+ return false;
+ } else if (!description.equals(other.description))
+ return false;
+ if (height == null) {
+ if (other.height != null)
+ return false;
+ } else if (!height.equals(other.height))
+ return false;
+ if (title == null) {
+ if (other.title != null)
+ return false;
+ } else if (!title.equals(other.title))
+ return false;
+ if (width == null) {
+ if (other.width != null)
+ return false;
+ } else if (!width.equals(other.width))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return title;
+ }
+
+ public static class SpacingValidator extends PositiveMeasureValidator {
+
+ @Override
+ public void validate(Object value) throws ValidationException {
+ super.validate(value);
+ Size size = (Size) value;
+ if (size.compareTo(new Size(0.1d, SizeUnit.mm)) < 0) {
+ throw new ValidationException("must be at least 0.1mm");
+ }
+ if (size.compareTo(new Size(1d, SizeUnit.in)) > 0) {
+ throw new ValidationException("must be less than 1in");
+ }
+ }
+ }
+
+ @Override
+ public Project clone() {
+ Project project = new Project();
+ project.setTitle(this.getTitle());
+ project.setAuthor(this.getAuthor());
+ project.setDescription(this.getDescription());
+ project.setFileVersion(this.getFileVersion());
+ project.setGridSpacing(this.getGridSpacing());
+ project.setHeight(this.getHeight());
+ project.setWidth(this.getWidth());
+ project.getLockedLayers().addAll(this.getLockedLayers());
+
+ Map, IDIYComponent>> cloneMap = new HashMap, IDIYComponent>>();
+
+ for (IDIYComponent> component : this.components) {
+ try {
+ IDIYComponent> clone = component.clone();
+ project.getComponents().add(clone);
+ cloneMap.put(component, clone);
+ } catch (CloneNotSupportedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ for (Set> group : this.groups) {
+ Set> cloneGroup = new HashSet>();
+ for (IDIYComponent> component : group) {
+ cloneGroup.add(cloneMap.get(component));
+ }
+ project.groups.add(cloneGroup);
+ }
+ return project;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/Template.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/Template.java
new file mode 100644
index 00000000..eaee3557
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/Template.java
@@ -0,0 +1,80 @@
+package org.diylc.core;
+
+import java.awt.Point;
+import java.util.List;
+import java.util.Map;
+
+public class Template {
+
+ private String name;
+ private Map values;
+ private List points;
+
+ public Template(String name, Map values, List points) {
+ super();
+ this.name = name;
+ this.values = values;
+ this.points = points;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Map getValues() {
+ return values;
+ }
+
+ public void setValues(Map values) {
+ this.values = values;
+ }
+
+ public List getPoints() {
+ return points;
+ }
+
+ public void setPoints(List points) {
+ this.points = points;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ result = prime * result + ((points == null) ? 0 : points.hashCode());
+ result = prime * result + ((values == null) ? 0 : values.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Template other = (Template) obj;
+ if (name == null) {
+ if (other.name != null)
+ return false;
+ } else if (!name.equals(other.name))
+ return false;
+ if (points == null) {
+ if (other.points != null)
+ return false;
+ } else if (!points.equals(other.points))
+ return false;
+ if (values == null) {
+ if (other.values != null)
+ return false;
+ } else if (!values.equals(other.values))
+ return false;
+ return true;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/Theme.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/Theme.java
new file mode 100644
index 00000000..722f02dc
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/Theme.java
@@ -0,0 +1,86 @@
+package org.diylc.core;
+
+import java.awt.Color;
+import java.io.Serializable;
+
+public class Theme implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private String name;
+ private Color bgColor;
+ private Color gridColor;
+ private Color outlineColor;
+
+ public Theme(String name, Color bgColor, Color gridColor, Color outlineColor) {
+ super();
+ this.name = name;
+ this.bgColor = bgColor;
+ this.gridColor = gridColor;
+ this.outlineColor = outlineColor;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Color getBgColor() {
+ return bgColor;
+ }
+
+ public Color getGridColor() {
+ return gridColor;
+ }
+
+ public Color getOutlineColor() {
+ return outlineColor;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((bgColor == null) ? 0 : bgColor.hashCode());
+ result = prime * result + ((gridColor == null) ? 0 : gridColor.hashCode());
+ result = prime * result + ((outlineColor == null) ? 0 : outlineColor.hashCode());
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Theme other = (Theme) obj;
+ if (bgColor == null) {
+ if (other.bgColor != null)
+ return false;
+ } else if (!bgColor.equals(other.bgColor))
+ return false;
+ if (gridColor == null) {
+ if (other.gridColor != null)
+ return false;
+ } else if (!gridColor.equals(other.gridColor))
+ return false;
+ if (outlineColor == null) {
+ if (other.outlineColor != null)
+ return false;
+ } else if (!outlineColor.equals(other.outlineColor))
+ return false;
+ if (name == null) {
+ if (other.name != null)
+ return false;
+ } else if (!name.equals(other.name))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/ValidationException.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/ValidationException.java
new file mode 100644
index 00000000..f78cc348
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/ValidationException.java
@@ -0,0 +1,11 @@
+
+package org.diylc.core;
+
+public class ValidationException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public ValidationException(String message) {
+ super(message);
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/VisibilityPolicy.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/VisibilityPolicy.java
new file mode 100644
index 00000000..040d8730
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/VisibilityPolicy.java
@@ -0,0 +1,22 @@
+package org.diylc.core;
+
+/**
+ * Enumerates control point visibility policies.
+ *
+ * @author Branislav Stojkovic
+ */
+public enum VisibilityPolicy {
+
+ /**
+ * Control point should be rendered all the time.
+ */
+ ALWAYS,
+ /**
+ * Control point should be rendered only when the component is selected.
+ */
+ WHEN_SELECTED,
+ /**
+ * Control point should not be rendered regardless of component selection.
+ */
+ NEVER;
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/annotations/BomPolicy.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/annotations/BomPolicy.java
new file mode 100644
index 00000000..86231f1d
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/annotations/BomPolicy.java
@@ -0,0 +1,7 @@
+package org.diylc.core.annotations;
+
+public enum BomPolicy {
+ SHOW_ALL_NAMES,
+ SHOW_ONLY_TYPE_NAME,
+ NEVER_SHOW,
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/annotations/ComponentDescriptor.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/annotations/ComponentDescriptor.java
new file mode 100644
index 00000000..c2927ec0
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/annotations/ComponentDescriptor.java
@@ -0,0 +1,86 @@
+package org.diylc.core.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import org.diylc.core.CreationMethod;
+import org.diylc.core.IDIYComponent;
+
+/**
+ * Annotation for {@link IDIYComponent} implementation. Describes component
+ * properties.
+ *
+ * @author Branislav Stojkovic
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ComponentDescriptor {
+
+ /**
+ * @return component type name.
+ */
+ String name();
+
+ /**
+ * @return component type description.
+ */
+ String description();
+
+ /**
+ * @return method that should be used to create a component. If
+ * CreationMethod.POINT_BY_POINT is used, user will
+ * have to select ending points before the component is created.
+ */
+ CreationMethod creationMethod() default CreationMethod.SINGLE_CLICK;
+
+ /**
+ * @return component category, e.g. "Passive", "Semiconductors", etc.
+ */
+ String category();
+
+ /**
+ * @return component author name.
+ */
+ String author();
+
+ /**
+ * @return prefix that will be used to generate component instance names,
+ * e.g. "R" for resistors or "Q" for transistors.
+ */
+ String instanceNamePrefix();
+
+ /**
+ * @return Z-order of the component.
+ */
+ double zOrder();
+
+ /**
+ * @return true if the component may go beyond it's predefined layer. In
+ * that case, zOrder is used as initial z order of the
+ * component.
+ */
+ boolean flexibleZOrder() default false;
+
+ /**
+ * @return when false, moving one control point will cause all the others to
+ * move together with it.
+ */
+ boolean stretchable() default true;
+
+ /**
+ * @return controls what should be shown the BOM
+ */
+ BomPolicy bomPolicy() default BomPolicy.SHOW_ALL_NAMES;
+
+ /**
+ * @return when true, component editor dialog should be shown in Auto-Edit
+ * mode.
+ */
+ boolean autoEdit() default true;
+
+ /**
+ * @return true if component may be rotated, false otherwise
+ * @return
+ */
+ boolean rotatable() default true;
+
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/annotations/EditableProperty.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/annotations/EditableProperty.java
new file mode 100644
index 00000000..9642d074
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/annotations/EditableProperty.java
@@ -0,0 +1,42 @@
+package org.diylc.core.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import org.diylc.core.AllowAllValidator;
+import org.diylc.core.IPropertyValidator;
+
+/**
+ * Used to annotate editable component property. Editable property XYZ must have
+ * both getter and setter named getXYZ and setXYZ
+ * respectively and this annotation must be placed on the getter method.
+ *
+ * @author Branislav Stojkovic
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EditableProperty {
+
+ /**
+ * Returns property name. If left blank property name will be extracted from
+ * the getter name, e.g. getter getXYZ will induce property
+ * name XYZ.
+ * Default value is blank.
+ *
+ * @return
+ */
+ String name() default "";
+
+ /**
+ * If true, application may set default value for this property.
+ * Default value is true.
+ *
+ * @return
+ */
+ boolean defaultable() default true;
+
+ /**
+ * Class of validator to use to
+ * @return
+ */
+ Class extends IPropertyValidator> validatorClass() default AllowAllValidator.class;
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/annotations/PositiveMeasureValidator.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/annotations/PositiveMeasureValidator.java
new file mode 100644
index 00000000..a276b1a6
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/annotations/PositiveMeasureValidator.java
@@ -0,0 +1,22 @@
+package org.diylc.core.annotations;
+
+import org.diylc.core.IPropertyValidator;
+import org.diylc.core.ValidationException;
+import org.diylc.core.measures.AbstractMeasure;
+
+public class PositiveMeasureValidator implements IPropertyValidator {
+
+ @Override
+ public void validate(Object value) throws ValidationException {
+ if (value == null)
+ return;
+ if (value instanceof AbstractMeasure) {
+ AbstractMeasure measure = (AbstractMeasure) value;
+ if (measure.getValue() < 0) {
+ throw new ValidationException("must be greater than zero.");
+ }
+ } else {
+ throw new ValidationException("wrong data type, measure expected.");
+ }
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/AbstractMeasure.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/AbstractMeasure.java
new file mode 100644
index 00000000..ad2386ee
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/AbstractMeasure.java
@@ -0,0 +1,88 @@
+package org.diylc.core.measures;
+
+import java.io.Serializable;
+import java.text.DecimalFormat;
+import java.text.Format;
+
+/***
+ * Immutable measure class.
+ *
+ * @author bancika
+ *
+ * @param
+ */
+public class AbstractMeasure> implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 1L;
+
+ protected static final Format format = new DecimalFormat("0.####");
+
+ protected Double value;
+ protected T unit;
+
+// public AbstractMeasure() {
+// }
+
+ public AbstractMeasure(Double value, T unit) {
+ super();
+ this.value = value;
+ this.unit = unit;
+ }
+
+ public Double getValue() {
+ return value;
+ }
+
+// public void setValue(Double value) {
+// this.value = value;
+// }
+
+ public T getUnit() {
+ return unit;
+ }
+
+// public void setUnit(T multiplier) {
+// this.unit = multiplier;
+// }
+
+// @Override
+// public Object clone() throws CloneNotSupportedException {
+// return super.clone();
+// }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((unit == null) ? 0 : unit.hashCode());
+ result = prime * result + ((value == null) ? 0 : value.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ AbstractMeasure> other = (AbstractMeasure>) obj;
+ if (unit == null) {
+ if (other.unit != null)
+ return false;
+ } else if (!unit.equals(other.unit))
+ return false;
+ if (value == null) {
+ if (other.value != null)
+ return false;
+ } else if (!value.equals(other.value))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return format.format(value) + unit;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Capacitance.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Capacitance.java
new file mode 100644
index 00000000..b4d81742
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Capacitance.java
@@ -0,0 +1,31 @@
+package org.diylc.core.measures;
+
+public class Capacitance extends AbstractMeasure {
+
+ private static final long serialVersionUID = 1L;
+
+// public Capacitance() {
+// super();
+// // TODO Auto-generated constructor stub
+// }
+
+ public Capacitance(Double value, CapacitanceUnit unit) {
+ super(value, unit);
+ // TODO Auto-generated constructor stub
+ }
+
+ @Override
+ public Capacitance clone() throws CloneNotSupportedException {
+ return new Capacitance(value, unit);
+ }
+
+ public static Capacitance parseCapacitance(String value) {
+ for (CapacitanceUnit unit : CapacitanceUnit.values()) {
+ if (value.toLowerCase().endsWith(unit.toString().toLowerCase())) {
+ value = value.substring(0, value.length() - unit.toString().length() - 1).trim();
+ return new Capacitance(Double.parseDouble(value), unit);
+ }
+ }
+ throw new IllegalArgumentException("Could not parse capacitance: " + value);
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/CapacitanceUnit.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/CapacitanceUnit.java
new file mode 100644
index 00000000..83071499
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/CapacitanceUnit.java
@@ -0,0 +1,24 @@
+package org.diylc.core.measures;
+
+public enum CapacitanceUnit implements Unit {
+
+ pF(1, "pF"), nF(1e3, "nF"), uF(1e6, "uF"), mF(1e9, "mF"), F(1e12, "F");
+
+ double factor;
+ String display;
+
+ private CapacitanceUnit(double factor, String display) {
+ this.factor = factor;
+ this.display = display;
+ }
+
+ @Override
+ public double getFactor() {
+ return factor;
+ }
+
+ @Override
+ public String toString() {
+ return display;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Current.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Current.java
new file mode 100644
index 00000000..0ea01291
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Current.java
@@ -0,0 +1,16 @@
+package org.diylc.core.measures;
+
+public class Current extends AbstractMeasure {
+
+ private static final long serialVersionUID = 1L;
+
+ public Current(Double value, CurrentUnit unit) {
+ super(value, unit);
+ // TODO Auto-generated constructor stub
+ }
+
+ @Override
+ public Current clone() throws CloneNotSupportedException {
+ return new Current(value, unit);
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/CurrentUnit.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/CurrentUnit.java
new file mode 100644
index 00000000..c5c18ac4
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/CurrentUnit.java
@@ -0,0 +1,24 @@
+package org.diylc.core.measures;
+
+public enum CurrentUnit implements Unit {
+
+ uA(1e-6, "uA"), mA(1e-3, "mA"), A(1, "A");
+
+ double factor;
+ String display;
+
+ private CurrentUnit(double factor, String display) {
+ this.factor = factor;
+ this.display = display;
+ }
+
+ @Override
+ public double getFactor() {
+ return factor;
+ }
+
+ @Override
+ public String toString() {
+ return display;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Inductance.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Inductance.java
new file mode 100644
index 00000000..a63bfcae
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Inductance.java
@@ -0,0 +1,31 @@
+package org.diylc.core.measures;
+
+public class Inductance extends AbstractMeasure {
+
+ private static final long serialVersionUID = 1L;
+
+// public Inductance() {
+// super();
+// // TODO Auto-generated constructor stub
+// }
+
+ public Inductance(Double value, InductanceUnit unit) {
+ super(value, unit);
+ // TODO Auto-generated constructor stub
+ }
+
+ @Override
+ public Inductance clone() throws CloneNotSupportedException {
+ return new Inductance(value, unit);
+ }
+
+ public static Inductance parseCapacitance(String value) {
+ for (InductanceUnit unit : InductanceUnit.values()) {
+ if (value.toLowerCase().endsWith(unit.toString().toLowerCase())) {
+ value = value.substring(0, value.length() - unit.toString().length() - 1).trim();
+ return new Inductance(Double.parseDouble(value), unit);
+ }
+ }
+ throw new IllegalArgumentException("Could not parse inductance: " + value);
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/InductanceUnit.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/InductanceUnit.java
new file mode 100644
index 00000000..6c838315
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/InductanceUnit.java
@@ -0,0 +1,24 @@
+package org.diylc.core.measures;
+
+public enum InductanceUnit implements Unit {
+
+ pH(1, "pH"), nH(1e3, "nH"), uH(1e6, "uH"), mH(1e9, "mH"), H(1e12, "H");
+
+ double factor;
+ String display;
+
+ private InductanceUnit(double factor, String display) {
+ this.factor = factor;
+ this.display = display;
+ }
+
+ @Override
+ public double getFactor() {
+ return factor;
+ }
+
+ @Override
+ public String toString() {
+ return display;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Power.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Power.java
new file mode 100644
index 00000000..f7f6069a
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Power.java
@@ -0,0 +1,31 @@
+package org.diylc.core.measures;
+
+public class Power extends AbstractMeasure {
+
+ private static final long serialVersionUID = 1L;
+
+// public Power() {
+// super();
+// // TODO Auto-generated constructor stub
+// }
+
+ public Power(Double value, PowerUnit unit) {
+ super(value, unit);
+ // TODO Auto-generated constructor stub
+ }
+
+ @Override
+ public Power clone() throws CloneNotSupportedException {
+ return new Power(value, unit);
+ }
+
+ public static Power parseCapacitance(String value) {
+ for (PowerUnit unit : PowerUnit.values()) {
+ if (value.toLowerCase().endsWith(unit.toString().toLowerCase())) {
+ value = value.substring(0, value.length() - unit.toString().length() - 1).trim();
+ return new Power(Double.parseDouble(value), unit);
+ }
+ }
+ throw new IllegalArgumentException("Could not parse power: " + value);
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/PowerUnit.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/PowerUnit.java
new file mode 100644
index 00000000..ecd79c39
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/PowerUnit.java
@@ -0,0 +1,24 @@
+package org.diylc.core.measures;
+
+public enum PowerUnit implements Unit {
+
+ MW(1e-1, "mW"), W(1, "W"), KW(1e3, "KW");
+
+ double factor;
+ String display;
+
+ private PowerUnit(double factor, String display) {
+ this.factor = factor;
+ this.display = display;
+ }
+
+ @Override
+ public double getFactor() {
+ return factor;
+ }
+
+ @Override
+ public String toString() {
+ return display;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Resistance.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Resistance.java
new file mode 100644
index 00000000..bfe0ebbd
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Resistance.java
@@ -0,0 +1,98 @@
+package org.diylc.core.measures;
+
+import java.awt.Color;
+
+import org.diylc.common.ResistorColorCode;
+
+public class Resistance extends AbstractMeasure {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final Color[] COLOR_DIGITS = new Color[] { Color.black, Color.decode("#8B4513"),
+ Color.red, Color.orange, Color.yellow, Color.decode("#76EE00"), Color.blue,
+ Color.decode("#91219E"), Color.lightGray, Color.white };
+ private static final Color[] COLOR_MULTIPLIER = new Color[] { Color.lightGray.brighter(),
+ Color.decode("#FFB90F"), Color.black, Color.decode("#8B4513"), Color.red, Color.orange,
+ Color.yellow, Color.decode("#76EE00"), Color.blue };
+
+// public Resistance() {
+// super();
+// // TODO Auto-generated constructor stub
+// }
+
+ public Resistance(Double value, ResistanceUnit multiplier) {
+ super(value, multiplier);
+ // TODO Auto-generated constructor stub
+ }
+
+ @Override
+ public Resistance clone() throws CloneNotSupportedException {
+ return new Resistance(value, unit);
+ }
+
+ public Color[] getColorCode(ResistorColorCode resistorColorCode) {
+ Color[] bands;
+ if (getValue() == 0) {
+ switch (resistorColorCode) {
+ case _4_BAND:
+ bands = new Color[3];
+ bands[0] = COLOR_DIGITS[0];
+ bands[1] = COLOR_DIGITS[0];
+ bands[2] = COLOR_MULTIPLIER[2];
+ break;
+ case _5_BAND:
+ bands = new Color[4];
+ bands[0] = COLOR_DIGITS[0];
+ bands[1] = COLOR_DIGITS[0];
+ bands[2] = COLOR_DIGITS[0];
+ bands[3] = COLOR_MULTIPLIER[2];
+ break;
+ default:
+ bands = new Color[] {};
+ }
+ } else {
+ double base = getValue() * getUnit().getFactor();
+ int multiplier = 0;
+ while (base > (resistorColorCode == ResistorColorCode._4_BAND ? 99 : 999)) {
+ multiplier += 1;
+ base /= 10;
+ }
+ while (base < (resistorColorCode == ResistorColorCode._4_BAND ? 10 : 100)) {
+ multiplier -= 1;
+ base *= 10;
+ }
+ if (multiplier > 6 || multiplier < -2) {
+ // Out of range.
+ return new Color[] {};
+ }
+ switch (resistorColorCode) {
+ case _4_BAND:
+ bands = new Color[3];
+ bands[0] = COLOR_DIGITS[(int) (base / 10)];
+ bands[1] = COLOR_DIGITS[(int) (base % 10)];
+ bands[2] = COLOR_MULTIPLIER[multiplier + 2];
+ break;
+ case _5_BAND:
+ bands = new Color[4];
+ bands[0] = COLOR_DIGITS[(int) (base / 100)];
+ bands[1] = COLOR_DIGITS[(int) (base / 10 % 10)];
+ bands[2] = COLOR_DIGITS[(int) (base % 10)];
+ bands[3] = COLOR_MULTIPLIER[multiplier + 2];
+ break;
+ default:
+ bands = new Color[] {};
+ }
+ }
+ return bands;
+ }
+
+ public static Resistance parseResistance(String value) {
+ for (ResistanceUnit unit : ResistanceUnit.values()) {
+ if (value.toLowerCase().endsWith(unit.toString().toLowerCase())) {
+ value = value.substring(0, value.length() - unit.toString().length()).trim();
+ return new Resistance(Double.parseDouble(value), unit);
+ }
+ }
+ throw new IllegalArgumentException("Could not parse resistance: " + value);
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/ResistanceUnit.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/ResistanceUnit.java
new file mode 100644
index 00000000..9d70fdf2
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/ResistanceUnit.java
@@ -0,0 +1,24 @@
+package org.diylc.core.measures;
+
+public enum ResistanceUnit implements Unit {
+
+ R(1, "\u2126"), K(1e3, "K"), M(1e6, "M");
+
+ double factor;
+ String display;
+
+ private ResistanceUnit(double factor, String display) {
+ this.factor = factor;
+ this.display = display;
+ }
+
+ @Override
+ public double getFactor() {
+ return factor;
+ }
+
+ @Override
+ public String toString() {
+ return display;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Size.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Size.java
new file mode 100644
index 00000000..b5c7715c
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Size.java
@@ -0,0 +1,47 @@
+package org.diylc.core.measures;
+
+import org.diylc.utils.Constants;
+
+public class Size extends AbstractMeasure implements Comparable {
+
+ private static final long serialVersionUID = 1L;
+
+// public Size() {
+// super();
+// // TODO Auto-generated constructor stub
+// }
+
+ public Size(Double value, SizeUnit unit) {
+ super(value, unit);
+ // TODO Auto-generated constructor stub
+ }
+
+ public double convertToPixels() {
+// double factor = getUnit().getFactor() / SizeUnit.in.getFactor();
+// int grids = (int) (factor * getValue() * Constants.GRIDS_PER_INCH);
+// double remainder = (factor * getValue() * Constants.GRIDS_PER_INCH) - grids;
+// return (int) Math.round(Constants.PIXELS_PER_INCH / Constants.GRIDS_PER_INCH
+// * (grids + remainder));
+ return getValue() * getUnit().getFactor() / SizeUnit.in.getFactor() * Constants.PIXELS_PER_INCH;
+ }
+
+ @Override
+ public Size clone() throws CloneNotSupportedException {
+ return new Size(value, unit);
+ }
+
+ public static Size parseSize(String value) {
+ for (SizeUnit unit : SizeUnit.values()) {
+ if (value.toLowerCase().endsWith(unit.toString().toLowerCase())) {
+ value = value.substring(0, value.length() - unit.toString().length() - 1).trim();
+ return new Size(Double.parseDouble(value), unit);
+ }
+ }
+ throw new IllegalArgumentException("Could not parse size: " + value);
+ }
+
+ @Override
+ public int compareTo(Size o) {
+ return new Double(value * unit.getFactor()).compareTo(o.getValue() * o.getUnit().getFactor());
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/SizeUnit.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/SizeUnit.java
new file mode 100644
index 00000000..fe8afd35
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/SizeUnit.java
@@ -0,0 +1,17 @@
+package org.diylc.core.measures;
+
+public enum SizeUnit implements Unit {
+
+ mm(1d), cm(10d), m(1e4d), in(25.4d), ft(25.4d * 12), yd(9144d);
+
+ double factor;
+
+ private SizeUnit(double factor) {
+ this.factor = factor;
+ }
+
+ @Override
+ public double getFactor() {
+ return factor;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Unit.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Unit.java
new file mode 100644
index 00000000..90413341
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Unit.java
@@ -0,0 +1,6 @@
+package org.diylc.core.measures;
+
+public interface Unit {
+
+ double getFactor();
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Voltage.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Voltage.java
new file mode 100644
index 00000000..373b45cb
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/Voltage.java
@@ -0,0 +1,31 @@
+package org.diylc.core.measures;
+
+public class Voltage extends AbstractMeasure {
+
+ private static final long serialVersionUID = 1L;
+
+// public Voltage() {
+// super();
+// // TODO Auto-generated constructor stub
+// }
+
+ public Voltage(Double value, VoltageUnit unit) {
+ super(value, unit);
+ // TODO Auto-generated constructor stub
+ }
+
+ @Override
+ public Voltage clone() throws CloneNotSupportedException {
+ return new Voltage(value, unit);
+ }
+
+ public static Voltage parseCapacitance(String value) {
+ for (VoltageUnit unit : VoltageUnit.values()) {
+ if (value.toLowerCase().endsWith(unit.toString().toLowerCase())) {
+ value = value.substring(0, value.length() - unit.toString().length() - 1).trim();
+ return new Voltage(Double.parseDouble(value), unit);
+ }
+ }
+ throw new IllegalArgumentException("Could not parse voltage: " + value);
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/VoltageUnit.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/VoltageUnit.java
new file mode 100644
index 00000000..f20071b1
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/core/measures/VoltageUnit.java
@@ -0,0 +1,24 @@
+package org.diylc.core.measures;
+
+public enum VoltageUnit implements Unit {
+
+ MV(1e-3, "mV"), V(1, "V"), KV(1e3, "KV");
+
+ double factor;
+ String display;
+
+ private VoltageUnit(double factor, String display) {
+ this.factor = factor;
+ this.display = display;
+ }
+
+ @Override
+ public double getFactor() {
+ return factor;
+ }
+
+ @Override
+ public String toString() {
+ return display;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/IconLoader.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/IconLoader.java
new file mode 100644
index 00000000..75f1cede
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/IconLoader.java
@@ -0,0 +1,61 @@
+package org.diylc.images;
+
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+
+/**
+ * Loads image resources as Icons.
+ *
+ * @author Branislav Stojkovic
+ */
+public enum IconLoader {
+
+ Delete("delete.png"), Add("add.png"), FolderOut("folder_out.png"), Garbage("garbage.png"), DiskBlue(
+ "disk_blue.png"), SaveAs("save_as.png"), Exit("exit.png"), DocumentPlainYellow(
+ "document_plain_yellow.png"), PhotoScenery("photo_scenery.png"), LightBulbOn(
+ "lightbulb_on.png"), LightBulbOff("lightbulb_off.png"), NotebookAdd("notebook_add.png"), FormGreen(
+ "form_green.png"), Gears("gears.png"), About("about.png"), WindowColors(
+ "window_colors.png"), WindowGear("window_gear.png"), NavigateCheck("navigate_check.png"), Undo(
+ "undo.png"), Error("error.png"), Warning("warning.png"), ZoomSmall("zoom_small.png"), MoveSmall(
+ "move_small.png"), Print("print.png"), PDF("pdf.png"), Excel("excel.png"), CSV(
+ "csv.png"), HTML("html.png"), Image("image.png"), Cut("cut.png"), Copy("copy.png"), Paste(
+ "paste.png"), Selection("selection.png"), BOM("bom.png"), BlackBoard("blackboard.png"), IdCard(
+ "id_card.png"), IdCardAdd("id_card_add.png"), Chest("chest.png"), Upload("upload.png"), Wrench(
+ "wrench.png"), Group("group.png"), Ungroup("ungroup.png"), TraceMask("trace_mask.png"), Faq(
+ "faq.png"), Component("component.png"), Plugin("plugin.png"), Manual("manual.png"), Donate(
+ "donate.png"), Bug("bug.png"), IconLarge("icon_large.png"), IconMedium(
+ "icon_medium.png"), IconSmall("icon_small.png"), DocumentEdit("document_edit.png"), EditComponent(
+ "edit_component.png"), Size("size.png"), Front("front.png"), Back("back.png"), Pens(
+ "pens.png"), Sort("sort.png"), ElementsSelection("elements_selection.png");
+
+ protected String name;
+
+ private IconLoader(String name) {
+ this.name = name;
+ }
+
+ public Icon getIcon() {
+ java.net.URL imgURL = getClass().getResource(name);
+ if (imgURL != null) {
+ return new ImageIcon(imgURL, name);
+ } else {
+ System.err.println("Couldn't find file: " + name);
+ return null;
+ }
+ }
+
+ public Image getImage() {
+ BufferedImage img = null;
+ try {
+ img = ImageIO.read(getClass().getResourceAsStream(name));
+ } catch (IOException e) {
+ System.err.println("Couldn't find file: " + name);
+ }
+ return img;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/about.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/about.png
new file mode 100644
index 00000000..a8e64236
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/about.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/add.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/add.png
new file mode 100644
index 00000000..8a73c4f6
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/add.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/arrow_down_blue.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/arrow_down_blue.png
new file mode 100644
index 00000000..38b5739c
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/arrow_down_blue.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/arrow_left_blue.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/arrow_left_blue.png
new file mode 100644
index 00000000..ff99225c
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/arrow_left_blue.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/arrow_right_blue.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/arrow_right_blue.png
new file mode 100644
index 00000000..299f298e
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/arrow_right_blue.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/arrow_up_blue.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/arrow_up_blue.png
new file mode 100644
index 00000000..e491e547
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/arrow_up_blue.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/at.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/at.png
new file mode 100644
index 00000000..206cf23a
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/at.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/back.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/back.png
new file mode 100644
index 00000000..df446d87
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/back.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/blackboard.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/blackboard.png
new file mode 100644
index 00000000..6bcf8dd2
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/blackboard.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/bom.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/bom.png
new file mode 100644
index 00000000..233f3687
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/bom.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/bug.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/bug.png
new file mode 100644
index 00000000..9086622e
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/bug.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/check.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/check.png
new file mode 100644
index 00000000..ff611c8a
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/check.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/chest.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/chest.png
new file mode 100644
index 00000000..5f83ea57
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/chest.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/component.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/component.png
new file mode 100644
index 00000000..ce05570f
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/component.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/copy.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/copy.png
new file mode 100644
index 00000000..4757339f
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/copy.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/csv.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/csv.png
new file mode 100644
index 00000000..1ae75a5f
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/csv.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/cut.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/cut.png
new file mode 100644
index 00000000..e354a22c
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/cut.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/delete.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/delete.png
new file mode 100644
index 00000000..312bb3f5
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/delete.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/disk_blue.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/disk_blue.png
new file mode 100644
index 00000000..d2d0d6e1
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/disk_blue.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/disks.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/disks.png
new file mode 100644
index 00000000..532ccc6e
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/disks.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/document.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/document.png
new file mode 100644
index 00000000..af78223e
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/document.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/document_edit.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/document_edit.png
new file mode 100644
index 00000000..35d01513
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/document_edit.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/document_info.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/document_info.png
new file mode 100644
index 00000000..453f2290
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/document_info.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/document_plain_yellow.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/document_plain_yellow.png
new file mode 100644
index 00000000..3e88b651
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/document_plain_yellow.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/donate.gif b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/donate.gif
new file mode 100644
index 00000000..43cef691
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/donate.gif differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/donate.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/donate.png
new file mode 100644
index 00000000..0d2baa8d
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/donate.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/edit.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/edit.png
new file mode 100644
index 00000000..ae851759
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/edit.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/edit_component.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/edit_component.png
new file mode 100644
index 00000000..9f438c3e
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/edit_component.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/elements_selection.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/elements_selection.png
new file mode 100644
index 00000000..be9d2df1
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/elements_selection.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/error.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/error.png
new file mode 100644
index 00000000..f2bd64fd
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/error.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/excel.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/excel.png
new file mode 100644
index 00000000..3705304e
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/excel.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/exit.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/exit.png
new file mode 100644
index 00000000..65d3c548
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/exit.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/export.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/export.png
new file mode 100644
index 00000000..113a7cda
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/export.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/faq.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/faq.png
new file mode 100644
index 00000000..ee0dd88f
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/faq.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/folder_music.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/folder_music.png
new file mode 100644
index 00000000..dc3da81b
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/folder_music.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/folder_out.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/folder_out.png
new file mode 100644
index 00000000..80895073
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/folder_out.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/forbidden.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/forbidden.png
new file mode 100644
index 00000000..530433e6
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/forbidden.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/form_blue.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/form_blue.png
new file mode 100644
index 00000000..687cbf32
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/form_blue.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/form_blue_add.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/form_blue_add.png
new file mode 100644
index 00000000..15fb1f21
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/form_blue_add.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/front.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/front.png
new file mode 100644
index 00000000..302a04ca
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/front.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/garbage.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/garbage.png
new file mode 100644
index 00000000..a8fe2a3c
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/garbage.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/gear.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/gear.png
new file mode 100644
index 00000000..8e1411f2
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/gear.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/gears.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/gears.png
new file mode 100644
index 00000000..90ecf65c
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/gears.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/graph_edge.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/graph_edge.png
new file mode 100644
index 00000000..537388ca
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/graph_edge.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/graph_edge_curved.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/graph_edge_curved.png
new file mode 100644
index 00000000..dcb67b42
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/graph_edge_curved.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/graph_edge_directed.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/graph_edge_directed.png
new file mode 100644
index 00000000..09e881bc
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/graph_edge_directed.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/group.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/group.png
new file mode 100644
index 00000000..be9d2df1
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/group.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/html.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/html.png
new file mode 100644
index 00000000..292f755f
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/html.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/icon_large.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/icon_large.png
new file mode 100644
index 00000000..edd52f73
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/icon_large.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/icon_medium.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/icon_medium.png
new file mode 100644
index 00000000..d5559652
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/icon_medium.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/icon_small.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/icon_small.png
new file mode 100644
index 00000000..ca6af1c6
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/icon_small.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/id_card.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/id_card.png
new file mode 100644
index 00000000..b72bae75
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/id_card.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/id_card_add.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/id_card_add.png
new file mode 100644
index 00000000..212dcc26
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/id_card_add.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/image.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/image.png
new file mode 100644
index 00000000..b1d3ad27
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/image.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/lightbulb_off.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/lightbulb_off.png
new file mode 100644
index 00000000..d38e9e6c
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/lightbulb_off.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/lightbulb_on.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/lightbulb_on.png
new file mode 100644
index 00000000..032d1650
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/lightbulb_on.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/manual.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/manual.png
new file mode 100644
index 00000000..41c04a0b
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/manual.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/media_play_green.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/media_play_green.png
new file mode 100644
index 00000000..0a06be81
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/media_play_green.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/media_stop_red.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/media_stop_red.png
new file mode 100644
index 00000000..02fbdd17
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/media_stop_red.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/move_small.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/move_small.png
new file mode 100644
index 00000000..3d00db3c
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/move_small.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/navigate_check.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/navigate_check.png
new file mode 100644
index 00000000..8fc0a6a5
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/navigate_check.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/navigate_plus.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/navigate_plus.png
new file mode 100644
index 00000000..a751af18
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/navigate_plus.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/notebook_add.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/notebook_add.png
new file mode 100644
index 00000000..62bdeacb
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/notebook_add.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/paste.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/paste.png
new file mode 100644
index 00000000..fe5c0e94
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/paste.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/pdf.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/pdf.png
new file mode 100644
index 00000000..1dddc483
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/pdf.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/pens.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/pens.png
new file mode 100644
index 00000000..16d091b2
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/pens.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/photo_scenery.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/photo_scenery.png
new file mode 100644
index 00000000..b1d3ad27
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/photo_scenery.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/plugin.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/plugin.png
new file mode 100644
index 00000000..2bee22c4
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/plugin.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/print.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/print.png
new file mode 100644
index 00000000..ae807b13
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/print.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/redo.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/redo.png
new file mode 100644
index 00000000..092ff424
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/redo.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/refresh.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/refresh.png
new file mode 100644
index 00000000..1f23890d
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/refresh.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/save_as.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/save_as.png
new file mode 100644
index 00000000..a38d1fa2
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/save_as.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/selection.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/selection.png
new file mode 100644
index 00000000..dbb0a1a9
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/selection.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/size.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/size.png
new file mode 100644
index 00000000..16fd65eb
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/size.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/sort.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/sort.png
new file mode 100644
index 00000000..e7f43647
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/sort.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/trace_mask.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/trace_mask.png
new file mode 100644
index 00000000..38bf1602
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/trace_mask.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/undo.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/undo.png
new file mode 100644
index 00000000..f7e5f935
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/undo.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/ungroup.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/ungroup.png
new file mode 100644
index 00000000..383e6330
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/ungroup.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/upload.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/upload.png
new file mode 100644
index 00000000..113a7cda
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/upload.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/view.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/view.png
new file mode 100644
index 00000000..21aae811
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/view.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/warning.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/warning.png
new file mode 100644
index 00000000..a6cdac6e
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/warning.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/window_colors.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/window_colors.png
new file mode 100644
index 00000000..8f5f13ba
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/window_colors.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/wrench.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/wrench.png
new file mode 100644
index 00000000..e6b6a1b5
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/wrench.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/zoom_in.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/zoom_in.png
new file mode 100644
index 00000000..f575d423
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/zoom_in.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/zoom_out.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/zoom_out.png
new file mode 100644
index 00000000..4641b520
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/zoom_out.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/zoom_small.png b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/zoom_small.png
new file mode 100644
index 00000000..7020e117
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/images/zoom_small.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/log4j.properties b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/log4j.properties
new file mode 100644
index 00000000..df80fe66
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/log4j.properties
@@ -0,0 +1,9 @@
+log4j.rootLogger = DEBUG, fileout, stdout
+log4j.appender.fileout = com.diyfever.gui.miscutils.NewLogForEachRunFileAppender
+log4j.appender.fileout.layout.ConversionPattern = %d{ABSOLUTE} %5p [%t] %c{1} - %m%n
+log4j.appender.fileout.layout = org.apache.log4j.PatternLayout
+log4j.appender.fileout.File = log/log.log
+
+log4j.appender.stdout = org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout.ConversionPattern = %d{ABSOLUTE} %5p [%t] %c{1} - %m%n
+log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
\ No newline at end of file
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/CalcUtils.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/CalcUtils.java
new file mode 100644
index 00000000..62db2dfc
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/CalcUtils.java
@@ -0,0 +1,25 @@
+package org.diylc.presenter;
+
+import java.awt.Point;
+
+import org.diylc.core.measures.Size;
+
+public class CalcUtils {
+
+ /**
+ * Rounds the number to the closest grid line.
+ *
+ * @param x
+ * @return
+ */
+ public static int roundToGrid(int x, Size gridSpacing) {
+ double grid = gridSpacing.convertToPixels();
+ return (int) (Math.round(1f * x / grid) * grid);
+ }
+
+ public static void snapPointToGrid(Point point, Size gridSpacing) {
+ int x = roundToGrid(point.x, gridSpacing);
+ int y = roundToGrid(point.y, gridSpacing);
+ point.setLocation(x, y);
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/ComparatorFactory.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/ComparatorFactory.java
new file mode 100644
index 00000000..3e87145a
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/ComparatorFactory.java
@@ -0,0 +1,88 @@
+package org.diylc.presenter;
+
+import java.util.Comparator;
+
+import org.diylc.common.ComponentType;
+import org.diylc.common.PropertyWrapper;
+import org.diylc.core.IDIYComponent;
+
+public class ComparatorFactory {
+
+ private static ComparatorFactory instance;
+
+ private Comparator> componentNameComparator;
+ private Comparator componentTypeComparator;
+ private Comparator propertyNameComparator;
+ private Comparator> componentZOrderComparator;
+
+ public static ComparatorFactory getInstance() {
+ if (instance == null) {
+ instance = new ComparatorFactory();
+ }
+ return instance;
+ }
+
+ public Comparator> getComponentNameComparator() {
+ if (componentNameComparator == null) {
+ componentNameComparator = new Comparator>() {
+
+ @Override
+ public int compare(IDIYComponent> o1, IDIYComponent> o2) {
+ String name1 = o1.getName();
+ String name2 = o2.getName();
+ if (name1 == null || name2 == null) {
+ return 0;
+ }
+ return name1.compareToIgnoreCase(name2);
+ }
+ };
+ }
+ return componentNameComparator;
+ }
+
+ public Comparator getComponentTypeComparator() {
+ if (componentTypeComparator == null) {
+ componentTypeComparator = new Comparator() {
+
+ @Override
+ public int compare(ComponentType o1, ComponentType o2) {
+ return o1.getName().compareToIgnoreCase(o2.getName());
+ }
+ };
+ }
+ return componentTypeComparator;
+ }
+
+ public Comparator getPropertyNameComparator() {
+ if (propertyNameComparator == null) {
+ propertyNameComparator = new Comparator() {
+
+ @Override
+ public int compare(PropertyWrapper o1, PropertyWrapper o2) {
+ return o1.getName().compareToIgnoreCase(o2.getName());
+ }
+
+ };
+ }
+ return propertyNameComparator;
+ }
+
+ public Comparator> getComponentZOrderComparator() {
+ if (componentZOrderComparator == null) {
+ componentZOrderComparator = new Comparator>() {
+
+ @Override
+ public int compare(IDIYComponent> o1, IDIYComponent> o2) {
+ ComponentType type1 = ComponentProcessor.getInstance()
+ .extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) o1.getClass());
+ ComponentType type2 = ComponentProcessor.getInstance()
+ .extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) o2.getClass());
+ return new Double(type1.getZOrder()).compareTo(type2.getZOrder());
+ }
+ };
+ }
+ return componentZOrderComparator;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/ComponentProcessor.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/ComponentProcessor.java
new file mode 100644
index 00000000..28bbc0b9
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/ComponentProcessor.java
@@ -0,0 +1,251 @@
+package org.diylc.presenter;
+
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+
+import org.apache.log4j.Logger;
+import org.diylc.common.ComponentType;
+import org.diylc.common.PropertyWrapper;
+import org.diylc.core.CreationMethod;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IPropertyValidator;
+import org.diylc.core.annotations.BomPolicy;
+import org.diylc.core.annotations.ComponentDescriptor;
+import org.diylc.core.annotations.EditableProperty;
+
+/**
+ * Utility class with component processing methods.
+ *
+ * @author Branislav Stojkovic
+ */
+public class ComponentProcessor {
+
+ private static final Logger LOG = Logger.getLogger(ComponentProcessor.class);
+
+ private static ComponentProcessor instance;
+
+ private Map> propertyCache;
+ private Map propertyValidatorCache;
+ private Map componentTypeMap;
+
+ public static ComponentProcessor getInstance() {
+ if (instance == null) {
+ instance = new ComponentProcessor();
+ }
+ return instance;
+ }
+
+ private ComponentProcessor() {
+ super();
+ this.propertyCache = new HashMap>();
+ this.componentTypeMap = new HashMap();
+ this.propertyValidatorCache = new HashMap();
+ }
+
+ public ComponentType extractComponentTypeFrom(Class extends IDIYComponent>> clazz) {
+ if (componentTypeMap.containsKey(clazz.getName())) {
+ return componentTypeMap.get(clazz.getName());
+ }
+ String name;
+ String description;
+ CreationMethod creationMethod;
+ String category;
+ String namePrefix;
+ String author;
+ Icon icon;
+ double zOrder;
+ boolean flexibleZOrder;
+ boolean stretchable;
+ BomPolicy bomPolicy;
+ boolean autoEdit;
+ boolean rotatable;
+ if (clazz.isAnnotationPresent(ComponentDescriptor.class)) {
+ ComponentDescriptor annotation = clazz.getAnnotation(ComponentDescriptor.class);
+ name = annotation.name();
+ description = annotation.description();
+ creationMethod = annotation.creationMethod();
+ category = annotation.category();
+ namePrefix = annotation.instanceNamePrefix();
+ author = annotation.author();
+ zOrder = annotation.zOrder();
+ flexibleZOrder = annotation.flexibleZOrder();
+ stretchable = annotation.stretchable();
+ bomPolicy = annotation.bomPolicy();
+ autoEdit = annotation.autoEdit();
+ rotatable = annotation.rotatable();
+ } else {
+ name = clazz.getSimpleName();
+ description = "";
+ creationMethod = CreationMethod.SINGLE_CLICK;
+ category = "Uncategorized";
+ namePrefix = "Unknown";
+ author = "Unknown";
+ zOrder = IDIYComponent.COMPONENT;
+ flexibleZOrder = false;
+ stretchable = true;
+ bomPolicy = BomPolicy.SHOW_ALL_NAMES;
+ autoEdit = true;
+ rotatable = true;
+ }
+ icon = null;
+ // Draw component icon.
+ try {
+ IDIYComponent> componentInstance = (IDIYComponent>) clazz.newInstance();
+ Image image = new BufferedImage(Presenter.ICON_SIZE, Presenter.ICON_SIZE,
+ java.awt.image.BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g2d = (Graphics2D) image.getGraphics();
+ g2d
+ .setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ // g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+ // RenderingHints.VALUE_STROKE_PURE);
+ componentInstance.drawIcon(g2d, Presenter.ICON_SIZE, Presenter.ICON_SIZE);
+ icon = new ImageIcon(image);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ ComponentType componentType = new ComponentType(name, description, creationMethod,
+ category, namePrefix, author, icon, clazz, zOrder, flexibleZOrder, stretchable,
+ bomPolicy, autoEdit, rotatable);
+ componentTypeMap.put(clazz.getName(), componentType);
+ return componentType;
+ }
+
+ /**
+ * Extracts all editable properties from the component class.
+ *
+ * @param clazz
+ * @return
+ */
+ public List extractProperties(Class> clazz) {
+ if (propertyCache.containsKey(clazz.getName())) {
+ return cloneProperties(propertyCache.get(clazz.getName()));
+ }
+ List properties = new ArrayList();
+ for (Method method : clazz.getMethods()) {
+ if (method.getName().startsWith("get")) {
+ try {
+ if (method.isAnnotationPresent(EditableProperty.class)
+ && !method.isAnnotationPresent(Deprecated.class)) {
+ EditableProperty annotation = method.getAnnotation(EditableProperty.class);
+ String name;
+ if (annotation.name().equals("")) {
+ name = method.getName().substring(3);
+ } else {
+ name = annotation.name();
+ }
+ IPropertyValidator validator = getPropertyValidator(annotation
+ .validatorClass());
+ Method setter = clazz.getMethod("set" + method.getName().substring(3),
+ method.getReturnType());
+ PropertyWrapper property = new PropertyWrapper(name,
+ method.getReturnType(), method.getName(), setter.getName(),
+ annotation.defaultable(), validator);
+ properties.add(property);
+ }
+ } catch (NoSuchMethodException e) {
+ LOG.debug("No matching setter found for \"" + method.getName()
+ + "\". Skipping...");
+ }
+ }
+ }
+
+ propertyCache.put(clazz.getName(), properties);
+ return cloneProperties(properties);
+ }
+
+ private List cloneProperties(List properties) {
+ List result = new ArrayList(properties.size());
+ for (PropertyWrapper propertyWrapper : properties) {
+ try {
+ result.add((PropertyWrapper) propertyWrapper.clone());
+ } catch (CloneNotSupportedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns properties that have the same value for all the selected
+ * components.
+ *
+ * @param selectedComponents
+ * @return
+ */
+ public List getMutualSelectionProperties(
+ List> selectedComponents) throws Exception {
+ if (selectedComponents.isEmpty()) {
+ return null;
+ }
+ List properties = new ArrayList();
+ IDIYComponent> firstComponent = selectedComponents.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);
+ List newProperties = extractProperties(component.getClass());
+ for (PropertyWrapper property : newProperties) {
+ property.readFrom(component);
+ }
+ properties.retainAll(newProperties);
+ // Try to find matching properties in old and new lists and see if
+ // their values match.
+ for (PropertyWrapper oldProperty : properties) {
+ if (newProperties.contains(oldProperty)) {
+ PropertyWrapper newProperty = newProperties.get(newProperties
+ .indexOf(oldProperty));
+ if (newProperty.getValue() != null && newProperty.getValue() != null) {
+ if (!newProperty.getValue().equals(oldProperty.getValue()))
+ // Values don't match, so the property is not unique
+ // valued.
+ oldProperty.setUnique(false);
+ } else if ((newProperty.getValue() == null && oldProperty.getValue() != null)
+ || (newProperty.getValue() != null && oldProperty.getValue() == null)) {
+ oldProperty.setUnique(false);
+ }
+ }
+ }
+ // for (PropertyWrapper property : properties) {
+ // try {
+ // property.readUniqueFrom(component);
+ // } catch (Exception e) {
+ // // TODO Auto-generated catch block
+ // e.printStackTrace();
+ // }
+ // }
+ }
+ Collections.sort(properties, ComparatorFactory.getInstance().getPropertyNameComparator());
+ return properties;
+ }
+
+ private IPropertyValidator getPropertyValidator(Class extends IPropertyValidator> clazz) {
+ if (propertyValidatorCache.containsKey(clazz.getName())) {
+ return propertyValidatorCache.get(clazz.getName());
+ }
+ IPropertyValidator validator;
+ try {
+ validator = clazz.newInstance();
+ } catch (Exception e) {
+ LOG.error("Could not instantiate validator for " + clazz.getName(), e);
+ return null;
+ }
+ propertyValidatorCache.put(clazz.getName(), validator);
+ return validator;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/DrawingManager.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/DrawingManager.java
new file mode 100644
index 00000000..b0050e30
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/DrawingManager.java
@@ -0,0 +1,425 @@
+package org.diylc.presenter;
+
+import java.awt.AlphaComposite;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.geom.Area;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.log4j.Logger;
+import org.diylc.appframework.miscutils.ConfigurationManager;
+import org.diylc.appframework.simplemq.MessageDispatcher;
+import org.diylc.common.DrawOption;
+import org.diylc.common.EventType;
+import org.diylc.common.GridType;
+import org.diylc.common.IComponentFiler;
+import org.diylc.common.IPlugInPort;
+import org.diylc.common.ObjectCache;
+import org.diylc.core.ComponentState;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.Project;
+import org.diylc.core.Theme;
+import org.diylc.core.VisibilityPolicy;
+import org.diylc.utils.Constants;
+
+/**
+ * Utility that deals with painting {@link Project} on the {@link Graphics2D}
+ * and keeps areas taken by each drawn component.
+ *
+ * @author Branislav Stojkovic
+ */
+public class DrawingManager {
+
+ private static final Logger LOG = Logger.getLogger(DrawingManager.class);
+
+ public static int CONTROL_POINT_SIZE = 7;
+
+ public static final String ZOOM_KEY = "zoom";
+
+ public static String DEBUG_COMPONENT_AREAS = "org.diylc.debugComponentAreas";
+
+ public static Color CONTROL_POINT_COLOR = Color.blue;
+ public static Color SELECTED_CONTROL_POINT_COLOR = Color.green;
+
+ private Theme theme = (Theme) ConfigurationManager.getInstance()
+ .readObject(IPlugInPort.THEME_KEY, Constants.DEFAULT_THEME);
+
+ // Keeps Area object of each drawn component.
+ private Map, Area> componentAreaMap;
+ // Maps components to the last state they are drawn in. Also, used to
+ // determine which components are invalidated when they are not in the map.
+ private Map, ComponentState> lastDrawnStateMap;
+
+ private Composite slotComposite = AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, 0.3f);
+ private Composite lockedComposite = AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, 0.3f);
+ private List> failedComponents = new ArrayList>();
+
+ private double zoomLevel = 1d;// ConfigurationManager.getInstance().readDouble(ZOOM_KEY,
+ // 1d);
+
+ private MessageDispatcher messageDispatcher;
+
+ private boolean debugComponentAreas;
+
+ public DrawingManager(MessageDispatcher messageDispatcher) {
+ super();
+ this.messageDispatcher = messageDispatcher;
+ componentAreaMap = new HashMap, Area>();
+ lastDrawnStateMap = new HashMap, ComponentState>();
+ String debugComponentAreasStr = System
+ .getProperty(DEBUG_COMPONENT_AREAS);
+ debugComponentAreas = debugComponentAreasStr != null
+ && debugComponentAreasStr.equalsIgnoreCase("true");
+ }
+
+ /**
+ * Paints the project onto the canvas and returns the list of components
+ * that failed to draw.
+ *
+ * @param g2d
+ * @param project
+ * @param drawOptions
+ * @param filter
+ * @param selectionRect
+ * @param selectedComponents
+ * @param lockedComponents
+ * @param groupedComponents
+ * @param controlPointSlot
+ * @param componentSlot
+ * @param dragInProgress
+ * @return
+ */
+ public List> drawProject(Graphics2D g2d, Project project,
+ Set drawOptions, IComponentFiler filter,
+ Rectangle selectionRect, List> selectedComponents,
+ Set> lockedComponents,
+ Set> groupedComponents,
+ List controlPointSlot, List> componentSlot,
+ boolean dragInProgress) {
+ failedComponents.clear();
+ if (project == null) {
+ return failedComponents;
+ }
+ G2DWrapper g2dWrapper = new G2DWrapper(g2d);
+
+ if (drawOptions.contains(DrawOption.ANTIALIASING)) {
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ } else {
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_OFF);
+ g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
+ }
+ if (ConfigurationManager.getInstance().readBoolean(
+ IPlugInPort.HI_QUALITY_RENDER_KEY, false)) {
+ g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
+ RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
+ g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
+ RenderingHints.VALUE_COLOR_RENDER_QUALITY);
+ g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
+ RenderingHints.VALUE_RENDER_QUALITY);
+ g2d.setRenderingHint(RenderingHints.KEY_DITHERING,
+ RenderingHints.VALUE_DITHER_ENABLE);
+ g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ // g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+ // RenderingHints.VALUE_STROKE_PURE);
+ } else {
+ g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
+ RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
+ g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
+ RenderingHints.VALUE_COLOR_RENDER_SPEED);
+ g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
+ RenderingHints.VALUE_RENDER_SPEED);
+ g2d.setRenderingHint(RenderingHints.KEY_DITHERING,
+ RenderingHints.VALUE_DITHER_DISABLE);
+ g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
+ }
+
+ double zoom = 1d;
+ if (drawOptions.contains(DrawOption.ZOOM)) {
+ zoom = zoomLevel;
+ } else {
+ zoom = 1 / Constants.PIXEL_SIZE;
+ }
+
+ // AffineTransform initialTx = g2d.getTransform();
+ Dimension d = getCanvasDimensions(project, zoom, true);
+
+ g2dWrapper.setColor(theme.getBgColor());
+ g2dWrapper.fillRect(0, 0, d.width, d.height);
+ g2d.clip(new Rectangle(new Point(0, 0), d));
+
+ GridType gridType = GridType.LINES;
+ if (drawOptions.contains(DrawOption.GRID) && gridType != GridType.NONE) {
+ double zoomStep = project.getGridSpacing().convertToPixels() * zoom;
+ if (gridType == GridType.CROSSHAIR) {
+ g2d.setStroke(new BasicStroke(1f, BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER, 10f, new float[] {
+ (float) zoomStep / 2, (float) zoomStep / 2 },
+ (float) zoomStep / 4));
+ } else if (gridType == GridType.DOT) {
+ g2d.setStroke(new BasicStroke(1f, BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER, 10f, new float[] { 1f,
+ (float) zoomStep - 1 }, 0f));
+ }
+
+ g2dWrapper.setColor(theme.getGridColor());
+ for (double i = zoomStep; i < d.width; i += zoomStep) {
+ g2dWrapper.drawLine((int) i, 0, (int) i, d.height - 1);
+ }
+ for (double j = zoomStep; j < d.height; j += zoomStep) {
+ g2dWrapper.drawLine(0, (int) j, d.width - 1, (int) j);
+ }
+ }
+
+ if (Math.abs(1.0 - zoom) > 1e-4) {
+ g2dWrapper.scale(zoom, zoom);
+ }
+
+ // Composite mainComposite = g2d.getComposite();
+ // Composite alphaComposite =
+ // AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f);
+
+ // g2dWrapper.resetTx();
+
+ // componentAreaMap.clear();
+ for (IDIYComponent> component : project.getComponents()) {
+ // Do not draw the component if it's filtered out.
+ if (filter != null && !filter.testComponent(component)) {
+ continue;
+ }
+ ComponentState state = ComponentState.NORMAL;
+ if (drawOptions.contains(DrawOption.SELECTION)
+ && selectedComponents.contains(component)) {
+ if (dragInProgress) {
+ state = ComponentState.DRAGGING;
+ } else {
+ state = ComponentState.SELECTED;
+ }
+ }
+ // Do not track the area if component is not invalidated and was
+ // drawn in the same state.
+ boolean trackArea = lastDrawnStateMap.get(component) != state;
+
+ synchronized (g2d) {
+ g2dWrapper.startedDrawingComponent();
+ if (!trackArea) {
+ g2dWrapper.stopTracking();
+ }
+ // Draw locked components in a new composite.
+ if (lockedComponents.contains(component)) {
+ g2d.setComposite(lockedComposite);
+ }
+ // Draw the component through the g2dWrapper.
+ try {
+ component.draw(g2dWrapper, state, drawOptions
+ .contains(DrawOption.OUTLINE_MODE), project,
+ g2dWrapper);
+ } catch (Exception e) {
+ LOG.error("Error drawing " + component.getName(), e);
+ failedComponents.add(component);
+ }
+ Area area = g2dWrapper.finishedDrawingComponent();
+ if (trackArea && area != null && !area.isEmpty()) {
+ componentAreaMap.put(component, area);
+ lastDrawnStateMap.put(component, state);
+ }
+ }
+ }
+
+ // Draw control points.
+ if (drawOptions.contains(DrawOption.CONTROL_POINTS)) {
+ // Draw unselected points first to make sure they are below.
+ if (dragInProgress || drawOptions.contains(DrawOption.OUTLINE_MODE)) {
+ for (IDIYComponent> component : project.getComponents()) {
+ for (int i = 0; i < component.getControlPointCount(); i++) {
+ VisibilityPolicy visibilityPolicy = component
+ .getControlPointVisibilityPolicy(i);
+ if ((groupedComponents.contains(component)
+ && (visibilityPolicy == VisibilityPolicy.ALWAYS || (selectedComponents
+ .contains(component) && visibilityPolicy == VisibilityPolicy.WHEN_SELECTED)) || (!groupedComponents
+ .contains(component)
+ && !selectedComponents.contains(component) && component
+ .getControlPointVisibilityPolicy(i) == VisibilityPolicy.ALWAYS))) {
+ g2dWrapper.setColor(CONTROL_POINT_COLOR);
+ Point controlPoint = component.getControlPoint(i);
+ int pointSize = CONTROL_POINT_SIZE - 2;
+ g2dWrapper.fillOval(controlPoint.x - pointSize / 2,
+ controlPoint.y - pointSize / 2, pointSize,
+ pointSize);
+ }
+ }
+ }
+ }
+ // Then draw the selected ones.
+ for (IDIYComponent> component : selectedComponents) {
+ for (int i = 0; i < component.getControlPointCount(); i++) {
+ if (!groupedComponents.contains(component)
+ && (component.getControlPointVisibilityPolicy(i) == VisibilityPolicy.WHEN_SELECTED || component
+ .getControlPointVisibilityPolicy(i) == VisibilityPolicy.ALWAYS)) {
+
+ Point controlPoint = component.getControlPoint(i);
+ int pointSize = CONTROL_POINT_SIZE;
+
+ g2dWrapper.setColor(SELECTED_CONTROL_POINT_COLOR
+ .darker());
+ g2dWrapper.fillOval(controlPoint.x - pointSize / 2,
+ controlPoint.y - pointSize / 2, pointSize,
+ pointSize);
+ g2dWrapper.setColor(SELECTED_CONTROL_POINT_COLOR);
+ g2dWrapper.fillOval(controlPoint.x - CONTROL_POINT_SIZE
+ / 2 + 1, controlPoint.y - CONTROL_POINT_SIZE
+ / 2 + 1, CONTROL_POINT_SIZE - 2,
+ CONTROL_POINT_SIZE - 2);
+ }
+ }
+ }
+ }
+
+ // Draw component slot in a separate composite.
+ if (componentSlot != null) {
+ g2dWrapper.startedDrawingComponent();
+ g2dWrapper.setComposite(slotComposite);
+ for (IDIYComponent> component : componentSlot) {
+ try {
+
+ component.draw(g2dWrapper, ComponentState.NORMAL,
+ drawOptions.contains(DrawOption.OUTLINE_MODE),
+ project, g2dWrapper);
+
+ } catch (Exception e) {
+ LOG.error("Error drawing " + component.getName(), e);
+ failedComponents.add(component);
+ }
+ }
+ g2dWrapper.finishedDrawingComponent();
+ }
+
+ // Draw control points of the component in the slot.
+ if (controlPointSlot != null) {
+ for (Point point : controlPointSlot) {
+ if (point != null) {
+ g2dWrapper.setColor(SELECTED_CONTROL_POINT_COLOR.darker());
+ g2dWrapper.fillOval(point.x - CONTROL_POINT_SIZE / 2,
+ point.y - CONTROL_POINT_SIZE / 2,
+ CONTROL_POINT_SIZE, CONTROL_POINT_SIZE);
+ g2dWrapper.setColor(SELECTED_CONTROL_POINT_COLOR);
+ g2dWrapper.fillOval(point.x - CONTROL_POINT_SIZE / 2 + 1,
+ point.y - CONTROL_POINT_SIZE / 2 + 1,
+ CONTROL_POINT_SIZE - 2, CONTROL_POINT_SIZE - 2);
+ }
+ }
+ }
+
+ // Go back to the original transformation and zoom in to draw the
+ // selection rectangle and other similar elements.
+ // g2d.setTransform(initialTx);
+ // if ((drawOptions.contains(DrawOption.ZOOM)) && (Math.abs(1.0 -
+ // zoomLevel) > 1e-4)) {
+ // g2d.scale(zoomLevel, zoomLevel);
+ // }
+
+ // At the end draw selection rectangle if needed.
+ if (drawOptions.contains(DrawOption.SELECTION)
+ && (selectionRect != null)) {
+ g2d.setColor(Color.white);
+ g2d.draw(selectionRect);
+ g2d.setColor(Color.black);
+ g2d.setStroke(Constants.DASHED_STROKE);
+ g2d.draw(selectionRect);
+ }
+
+ // Draw component area for test
+ if (debugComponentAreas) {
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1));
+ g2d.setColor(Color.red);
+ for (Area area : componentAreaMap.values()) {
+ g2d.draw(area);
+ }
+ }
+
+ return failedComponents;
+ }
+
+ public double getZoomLevel() {
+ return zoomLevel;
+ }
+
+ public void setZoomLevel(double zoomLevel) {
+ this.zoomLevel = zoomLevel;
+ fireZoomChanged();
+ // ConfigurationManager.getInstance().writeValue(ZOOM_KEY, zoomLevel);
+ }
+
+ public void invalidateComponent(IDIYComponent> component) {
+ componentAreaMap.remove(component);
+ lastDrawnStateMap.remove(component);
+ }
+
+ public Area getComponentArea(IDIYComponent> component) {
+ return componentAreaMap.get(component);
+ }
+
+ public void clearComponentAreaMap() {
+ componentAreaMap.clear();
+ lastDrawnStateMap.clear();
+ }
+
+ public List> findComponentsAt(Point point, Project project) {
+ List> components = new ArrayList>();
+ for (int i = 0; i < project.getComponents().size(); i++) {
+ Area area = componentAreaMap.get(project.getComponents().get(i));
+ if (area != null && area.contains(point)) {
+ components.add(0, project.getComponents().get(i));
+ }
+ }
+ return components;
+ }
+
+ public Dimension getCanvasDimensions(Project project, Double zoomLevel,
+ boolean useZoom) {
+ double width = project.getWidth().convertToPixels();
+ double height = project.getHeight().convertToPixels();
+ if (useZoom) {
+ width *= zoomLevel;
+ height *= zoomLevel;
+ } else {
+ width /= Constants.PIXEL_SIZE;
+ height /= Constants.PIXEL_SIZE;
+ }
+ return new Dimension((int) width, (int) height);
+ }
+
+ public void fireZoomChanged() {
+ messageDispatcher.dispatchMessage(EventType.ZOOM_CHANGED, zoomLevel);
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ }
+
+ public Theme getTheme() {
+ return theme;
+ }
+
+ public void setTheme(Theme theme) {
+ this.theme = theme;
+ ConfigurationManager.getInstance().writeValue(IPlugInPort.THEME_KEY,
+ theme);
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/G2DWrapper.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/G2DWrapper.java
new file mode 100644
index 00000000..47c892b1
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/G2DWrapper.java
@@ -0,0 +1,691 @@
+package org.diylc.presenter;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.Image;
+import java.awt.Paint;
+import java.awt.Polygon;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.RenderingHints.Key;
+import java.awt.font.FontRenderContext;
+import java.awt.font.GlyphVector;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Arc2D;
+import java.awt.geom.Area;
+import java.awt.geom.CubicCurve2D;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Line2D;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+import java.awt.geom.QuadCurve2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
+import java.awt.image.ImageObserver;
+import java.awt.image.RenderedImage;
+import java.awt.image.renderable.RenderableImage;
+import java.text.AttributedCharacterIterator;
+import java.util.Map;
+
+import org.diylc.core.IDrawingObserver;
+
+/**
+ * {@link Graphics2D} wrapper that keeps track of all drawing actions and
+ * creates an {@link Area} that corresponds to drawn objects. Before each
+ * component is drawn, {@link #startedDrawingComponent()} should be called.
+ * After the component is drawn, area may be retrieved using
+ * {@link #finishedDrawingComponent()}. Graphics configuration (color, font,
+ * etc) is reset between each two components.
+ *
+ * @author Branislav Stojkovic
+ */
+class G2DWrapper extends Graphics2D implements IDrawingObserver {
+
+ public static int LINE_SENSITIVITY_MARGIN = 2;
+ public static int CURVE_SENSITIVITY = 6;
+
+ private boolean drawingComponent = false;
+ private boolean trackingAllowed = true;
+
+ private Graphics2D canvasGraphics;
+ private Stroke originalStroke;
+ private Color originalColor;
+ private Composite originalComposite;
+ private AffineTransform originalTx;
+ private Font originalFont;
+ private AffineTransform currentTx;
+ private AffineTransform initialTx;
+ private Area currentArea;
+ private Shape lastShape;
+
+ /**
+ * Creates a wrapper around specified {@link Graphics2D} object.
+ *
+ * @param canvasGraphics
+ */
+ public G2DWrapper(Graphics2D canvasGraphics) {
+ super();
+ this.canvasGraphics = canvasGraphics;
+ currentArea = new Area();
+ currentTx = new AffineTransform();
+ }
+
+ /**
+ * Clears out the current area and caches canvas settings.
+ */
+ public void startedDrawingComponent() {
+ drawingComponent = true;
+ currentArea = new Area();
+ originalStroke = canvasGraphics.getStroke();
+ originalColor = canvasGraphics.getColor();
+ originalTx = canvasGraphics.getTransform();
+ originalComposite = canvasGraphics.getComposite();
+ originalFont = canvasGraphics.getFont();
+ currentTx = new AffineTransform();
+ initialTx = canvasGraphics.getTransform();
+ lastShape = null;
+ startTracking();
+ }
+
+ /**
+ * Reverts {@link Graphics2D} settings and returns area drawn by component
+ * in the meantime.
+ *
+ * @return
+ */
+ public Area finishedDrawingComponent() {
+ drawingComponent = false;
+ canvasGraphics.setStroke(originalStroke);
+ canvasGraphics.setColor(originalColor);
+ canvasGraphics.setTransform(originalTx);
+ canvasGraphics.setComposite(originalComposite);
+ canvasGraphics.setFont(originalFont);
+ return currentArea;
+ }
+
+ @Override
+ public void startTracking() {
+ this.trackingAllowed = true;
+ }
+
+ @Override
+ public void stopTracking() {
+ this.trackingAllowed = false;
+ }
+
+ /**
+ * Appends shape interior to the current component area.
+ *
+ * @param s
+ */
+ private void appendShape(Shape s) {
+ if (!drawingComponent || !trackingAllowed) {
+ return;
+ }
+ Rectangle2D bounds = s.getBounds2D();
+ // Only cache the shape if it's not 1D.
+ if (bounds.getWidth() > 1 && bounds.getHeight() > 1) {
+ Area area = new Area(s);
+ area.transform(currentTx);
+ currentArea.add(area);
+ lastShape = s;
+ }
+ }
+
+ /**
+ * Appends shape outline to the current component area.
+ *
+ * @param s
+ */
+ private void appendShapeOutline(Shape s) {
+ // Do not add shape outline if the same shape has been filled recently.
+ if (!drawingComponent || !trackingAllowed || s.equals(lastShape)) {
+ return;
+ }
+ lastShape = s;
+ PathIterator pathIterator = s.getPathIterator(null);
+ double thickness;
+ if (getStroke() instanceof BasicStroke) {
+ thickness = ((BasicStroke) getStroke()).getLineWidth() + 2 * LINE_SENSITIVITY_MARGIN;
+ } else {
+ thickness = 2 * LINE_SENSITIVITY_MARGIN;
+ }
+ Point2D prevPoint = null;
+ while (!pathIterator.isDone()) {
+ double[] coord = new double[6];
+ int type = pathIterator.currentSegment(coord);
+ switch (type) {
+ case PathIterator.SEG_MOVETO:
+ prevPoint = new Point2D.Double(coord[0], coord[1]);
+ break;
+ case PathIterator.SEG_LINETO:
+ // Represent straight line with a rectangle.
+ Point2D nextPoint = new Point2D.Double(coord[0], coord[1]);
+ Double theta = Math.atan2(nextPoint.getY() - prevPoint.getY(), nextPoint.getX()
+ - prevPoint.getX());
+ double width = Math.sqrt(Math.pow(nextPoint.getX() - prevPoint.getX(), 2)
+ + Math.pow(nextPoint.getY() - prevPoint.getY(), 2));
+ double midX = (prevPoint.getX() + nextPoint.getX()) / 2;
+ double midY = (prevPoint.getY() + nextPoint.getY()) / 2;
+ Rectangle2D rect = new Rectangle2D.Double(midX - width / 2, midY - thickness / 2,
+ width, thickness);
+ Area area = new Area(rect);
+ area.transform(AffineTransform.getRotateInstance(theta, midX, midY));
+ // area.transform(currentTx);
+ appendShape(area);
+ // Set the prev point to line end
+ prevPoint = nextPoint;
+ break;
+ case PathIterator.SEG_CUBICTO:
+ CubicCurve2D cubicCurve = new CubicCurve2D.Double(prevPoint.getX(), prevPoint
+ .getY(), coord[0], coord[1], coord[2], coord[3], coord[4], coord[5]);
+ addCubicCurveArea(cubicCurve);
+ prevPoint = new Point2D.Double(coord[4], coord[5]);
+ break;
+ case PathIterator.SEG_QUADTO:
+ QuadCurve2D quadCurve = new QuadCurve2D.Double(prevPoint.getX(), prevPoint.getY(),
+ coord[0], coord[1], coord[2], coord[3]);
+ addQuadCurveArea(quadCurve);
+ prevPoint = new Point2D.Double(coord[3], coord[3]);
+ break;
+ }
+ pathIterator.next();
+ }
+ }
+
+ /**
+ * Adds a cubic curve to the component area. It uses subdivision to
+ * approximate the curve with a series of straight lines.
+ *
+ * @param curve
+ */
+ private void addCubicCurveArea(CubicCurve2D curve) {
+ // When end points are close enough, approximate the curve with a
+ // straight line.
+ if (curve.getFlatness() < CURVE_SENSITIVITY) {
+ appendShapeOutline(new Line2D.Double(curve.getP1(), curve.getP2()));
+ } else {
+ CubicCurve2D leftSubcurve = new CubicCurve2D.Double();
+ CubicCurve2D rightSubcurve = new CubicCurve2D.Double();
+ curve.subdivide(leftSubcurve, rightSubcurve);
+ addCubicCurveArea(leftSubcurve);
+ addCubicCurveArea(rightSubcurve);
+ }
+ }
+
+ /**
+ * Adds a quad curve to the component area. It uses subdivision to
+ * approximate the curve with a series of straight lines.
+ *
+ * @param curve
+ */
+ private void addQuadCurveArea(QuadCurve2D curve) {
+ // When end points are close enough, approximate the curve with a
+ // straight line.
+ if (curve.getFlatness() < CURVE_SENSITIVITY) {
+ appendShapeOutline(new Line2D.Double(curve.getP1(), curve.getP2()));
+ } else {
+ QuadCurve2D leftSubcurve = new QuadCurve2D.Double();
+ QuadCurve2D rightSubcurve = new QuadCurve2D.Double();
+ curve.subdivide(leftSubcurve, rightSubcurve);
+ addQuadCurveArea(leftSubcurve);
+ addQuadCurveArea(rightSubcurve);
+ }
+ }
+
+ @Override
+ public void addRenderingHints(Map, ?> hints) {
+ canvasGraphics.addRenderingHints(hints);
+ }
+
+ @Override
+ public void clip(Shape s) {
+ canvasGraphics.clip(s);
+ }
+
+ @Override
+ public void draw(Shape s) {
+ canvasGraphics.draw(s);
+ if (drawingComponent && trackingAllowed) {
+ appendShapeOutline(s);
+ }
+ }
+
+ @Override
+ public void drawGlyphVector(GlyphVector g, float x, float y) {
+ canvasGraphics.drawGlyphVector(g, x, y);
+ }
+
+ @Override
+ public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) {
+ // FIXME: process map
+ return canvasGraphics.drawImage(img, xform, obs);
+ }
+
+ @Override
+ public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) {
+ canvasGraphics.drawImage(img, op, x, y);
+ // FIXME: process map
+ }
+
+ @Override
+ public void drawRenderableImage(RenderableImage img, AffineTransform xform) {
+ canvasGraphics.drawRenderableImage(img, xform);
+ // FIXME: process map
+ }
+
+ @Override
+ public void drawRenderedImage(RenderedImage img, AffineTransform xform) {
+ canvasGraphics.drawRenderedImage(img, xform);
+ // FIXME: process map
+ }
+
+ @Override
+ public void drawString(String str, int x, int y) {
+ canvasGraphics.drawString(str, x, y);
+ if (drawingComponent && trackingAllowed) {
+ FontMetrics fontMetrics = canvasGraphics.getFontMetrics();
+ Rectangle2D rect = fontMetrics.getStringBounds(str, canvasGraphics);
+ Point2D point = new Point2D.Double(x, y);
+ //currentTx.transform(point, point);
+ Rectangle2D finalRec = new Rectangle2D.Double(rect.getX() + point.getX(), rect.getY() + point.getY(), rect
+ .getWidth(), rect.getHeight());
+ appendShape(finalRec);
+ }
+ }
+
+ @Override
+ public void drawString(String str, float x, float y) {
+ canvasGraphics.drawString(str, x, y);
+ if (drawingComponent && trackingAllowed) {
+ FontMetrics fontMetrics = canvasGraphics.getFontMetrics();
+ Rectangle2D rect = fontMetrics.getStringBounds(str, canvasGraphics);
+ appendShape(new Rectangle2D.Double(rect.getX() + x, rect.getY() + y, rect.getWidth(),
+ rect.getHeight()));
+ }
+ }
+
+ @Override
+ public void drawString(AttributedCharacterIterator iterator, int x, int y) {
+ canvasGraphics.drawString(iterator, x, y);
+ // FIXME: process map
+ }
+
+ @Override
+ public void drawString(AttributedCharacterIterator iterator, float x, float y) {
+ canvasGraphics.drawString(iterator, x, y);
+ // FIXME: process map
+ }
+
+ @Override
+ public void fill(Shape s) {
+ canvasGraphics.fill(s);
+ appendShape(s);
+ }
+
+ @Override
+ public Color getBackground() {
+ return canvasGraphics.getBackground();
+ }
+
+ @Override
+ public Composite getComposite() {
+ return canvasGraphics.getComposite();
+ }
+
+ @Override
+ public GraphicsConfiguration getDeviceConfiguration() {
+ return canvasGraphics.getDeviceConfiguration();
+ }
+
+ @Override
+ public FontRenderContext getFontRenderContext() {
+ return canvasGraphics.getFontRenderContext();
+ }
+
+ @Override
+ public Paint getPaint() {
+ return canvasGraphics.getPaint();
+ }
+
+ @Override
+ public Object getRenderingHint(Key hintKey) {
+ return canvasGraphics.getRenderingHint(hintKey);
+ }
+
+ @Override
+ public RenderingHints getRenderingHints() {
+ return canvasGraphics.getRenderingHints();
+ }
+
+ @Override
+ public Stroke getStroke() {
+ return canvasGraphics.getStroke();
+ }
+
+ @Override
+ public AffineTransform getTransform() {
+ return canvasGraphics.getTransform();
+ }
+
+ @Override
+ public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
+ return canvasGraphics.hit(rect, s, onStroke);
+ }
+
+ @Override
+ public void rotate(double theta) {
+ canvasGraphics.rotate(theta);
+ currentTx.rotate(theta);
+ }
+
+ @Override
+ public void rotate(double theta, double x, double y) {
+ canvasGraphics.rotate(theta, x, y);
+ currentTx.rotate(theta, x, y);
+ }
+
+ @Override
+ public void scale(double sx, double sy) {
+ canvasGraphics.scale(sx, sy);
+ currentTx.scale(sx, sy);
+ }
+
+ @Override
+ public void setBackground(Color color) {
+ canvasGraphics.setBackground(color);
+ // FIXME: fix map
+ }
+
+ @Override
+ public void setComposite(Composite comp) {
+ canvasGraphics.setComposite(comp);
+ // FIXME: check this.
+ }
+
+ @Override
+ public void setPaint(Paint paint) {
+ canvasGraphics.setPaint(paint);
+ // FIXME: check this
+ }
+
+ @Override
+ public void setRenderingHint(Key hintKey, Object hintValue) {
+ canvasGraphics.setRenderingHint(hintKey, hintValue);
+ // FIXME: check this
+ }
+
+ @Override
+ public void setRenderingHints(Map, ?> hints) {
+ canvasGraphics.setRenderingHints(hints);
+ // FIXME: check this
+ }
+
+ @Override
+ public void setStroke(Stroke s) {
+ canvasGraphics.setStroke(s);
+ }
+
+ @Override
+ public void setTransform(AffineTransform Tx) {
+ canvasGraphics.setTransform(Tx);
+ currentTx = new AffineTransform(Tx);
+ try {
+ // Invert the tx that was set before we started drawing the component.
+ // We're left only component tx.
+ AffineTransform inverseInitialTx = initialTx.createInverse();
+ Point2D p = new Point2D.Double(currentTx.getTranslateX(), currentTx.getTranslateY());
+ inverseInitialTx.transform(p, p);
+ currentTx.concatenate(inverseInitialTx);
+ double[] matrix = new double[6];
+ currentTx.getMatrix(matrix);
+ currentTx.setTransform(matrix[0], matrix[1], matrix[2], matrix[3], p.getX(), p.getY());
+ } catch (NoninvertibleTransformException e) {
+ }
+ }
+
+ @Override
+ public void shear(double shx, double shy) {
+ canvasGraphics.shear(shx, shy);
+ currentTx.shear(shx, shy);
+ }
+
+ @Override
+ public void transform(AffineTransform Tx) {
+ canvasGraphics.transform(Tx);
+ currentTx.concatenate(Tx);
+ }
+
+ @Override
+ public void translate(int x, int y) {
+ canvasGraphics.translate(x, y);
+ currentTx.translate(x, y);
+ }
+
+ @Override
+ public void translate(double tx, double ty) {
+ canvasGraphics.translate(tx, ty);
+ currentTx.translate(tx, ty);
+ }
+
+ @Override
+ public void clearRect(int x, int y, int width, int height) {
+ canvasGraphics.clearRect(x, y, width, height);
+ }
+
+ @Override
+ public void clipRect(int x, int y, int width, int height) {
+ canvasGraphics.clipRect(x, y, width, height);
+ }
+
+ @Override
+ public void copyArea(int x, int y, int width, int height, int dx, int dy) {
+ canvasGraphics.copyArea(x, y, width, height, dx, dy);
+ }
+
+ @Override
+ public Graphics create() {
+ return canvasGraphics.create();
+ }
+
+ @Override
+ public void dispose() {
+ canvasGraphics.dispose();
+ }
+
+ @Override
+ public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) {
+ canvasGraphics.drawArc(x, y, width, height, startAngle, arcAngle);
+ }
+
+ @Override
+ public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
+ boolean result = canvasGraphics.drawImage(img, x, y, observer);
+ appendShape(new Rectangle2D.Double(x, y, img.getWidth(observer), img.getHeight(observer)));
+ return result;
+ }
+
+ @Override
+ public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) {
+ // FIXME: map
+ return canvasGraphics.drawImage(img, x, y, bgcolor, observer);
+ }
+
+ @Override
+ public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) {
+ // FIXME: map
+ return canvasGraphics.drawImage(img, x, y, width, height, observer);
+ }
+
+ @Override
+ public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor,
+ ImageObserver observer) {
+ // FIXME: map
+ return canvasGraphics.drawImage(img, x, y, width, height, bgcolor, observer);
+ }
+
+ @Override
+ public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1,
+ int sx2, int sy2, ImageObserver observer) {
+ // FIXME: map
+ return canvasGraphics.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer);
+ }
+
+ @Override
+ public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1,
+ int sx2, int sy2, Color bgcolor, ImageObserver observer) {
+ // FIXME: map
+ return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer);
+ }
+
+ @Override
+ public void drawLine(int x1, int y1, int x2, int y2) {
+ canvasGraphics.drawLine(x1, y1, x2, y2);
+ if (drawingComponent && trackingAllowed) {
+ appendShapeOutline(new Line2D.Double(x1, y1, x2, y2));
+ }
+ }
+
+ @Override
+ public void drawOval(int x, int y, int width, int height) {
+ canvasGraphics.drawOval(x, y, width, height);
+ if (drawingComponent && trackingAllowed) {
+ appendShapeOutline(new Ellipse2D.Double(x, y, width, height));
+ }
+ }
+
+ @Override
+ public void drawPolygon(int[] points, int[] points2, int points3) {
+ canvasGraphics.drawPolygon(points, points2, points3);
+ if (drawingComponent && trackingAllowed) {
+ appendShapeOutline(new Polygon(points, points2, points3));
+ }
+ }
+
+ @Override
+ public void drawPolyline(int[] points, int[] points2, int points3) {
+ canvasGraphics.drawPolyline(points, points2, points3);
+ }
+
+ @Override
+ public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {
+ canvasGraphics.drawRoundRect(x, y, width, height, arcWidth, arcHeight);
+ if (drawingComponent && trackingAllowed) {
+ appendShapeOutline(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
+ }
+ }
+
+ @Override
+ public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) {
+ canvasGraphics.fillArc(x, y, width, height, startAngle, arcAngle);
+ if (drawingComponent && trackingAllowed) {
+ appendShape(new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.PIE));
+ }
+ }
+
+ @Override
+ public void fillOval(int x, int y, int width, int height) {
+ canvasGraphics.fillOval(x, y, width, height);
+ if (drawingComponent && trackingAllowed) {
+ appendShape(new Ellipse2D.Double(x, y, width, height));
+ }
+ }
+
+ @Override
+ public void fillPolygon(int[] points, int[] points2, int points3) {
+ canvasGraphics.fillPolygon(points, points2, points3);
+ if (drawingComponent && trackingAllowed) {
+ appendShape(new Polygon(points, points2, points3));
+ }
+ }
+
+ @Override
+ public void drawRect(int x, int y, int width, int height) {
+ canvasGraphics.drawRect(x, y, width, height);
+ if (drawingComponent && trackingAllowed) {
+ appendShapeOutline(new Rectangle(x, y, width, height));
+ }
+ }
+
+ @Override
+ public void fillRect(int x, int y, int width, int height) {
+ canvasGraphics.fillRect(x, y, width, height);
+ if (drawingComponent && trackingAllowed) {
+ appendShape(new Rectangle(x, y, width, height));
+ }
+ }
+
+ @Override
+ public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {
+ canvasGraphics.fillRoundRect(x, y, width, height, arcWidth, arcHeight);
+ if (drawingComponent && trackingAllowed) {
+ appendShape(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
+ }
+ }
+
+ @Override
+ public Shape getClip() {
+ return canvasGraphics.getClip();
+ }
+
+ @Override
+ public Rectangle getClipBounds() {
+ return canvasGraphics.getClipBounds();
+ }
+
+ @Override
+ public Color getColor() {
+ return canvasGraphics.getColor();
+ }
+
+ @Override
+ public Font getFont() {
+ return canvasGraphics.getFont();
+ }
+
+ @Override
+ public FontMetrics getFontMetrics(Font f) {
+ return canvasGraphics.getFontMetrics(f);
+ }
+
+ @Override
+ public void setClip(Shape clip) {
+ canvasGraphics.setClip(clip);
+ }
+
+ @Override
+ public void setClip(int x, int y, int width, int height) {
+ canvasGraphics.setClip(x, y, width, height);
+ }
+
+ @Override
+ public void setColor(Color c) {
+ canvasGraphics.setColor(c);
+ }
+
+ @Override
+ public void setFont(Font font) {
+ canvasGraphics.setFont(font);
+ }
+
+ @Override
+ public void setPaintMode() {
+ canvasGraphics.setPaintMode();
+ }
+
+ @Override
+ public void setXORMode(Color c1) {
+ canvasGraphics.setXORMode(c1);
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/InstantiationManager.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/InstantiationManager.java
new file mode 100644
index 00000000..0205e636
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/InstantiationManager.java
@@ -0,0 +1,399 @@
+package org.diylc.presenter;
+
+import java.awt.Point;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+import org.diylc.appframework.miscutils.ConfigurationManager;
+import org.diylc.common.ComponentType;
+import org.diylc.common.IPlugInPort;
+import org.diylc.common.Orientation;
+import org.diylc.common.OrientationHV;
+import org.diylc.common.PropertyWrapper;
+import org.diylc.core.CreationMethod;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.Project;
+import org.diylc.core.Template;
+import org.diylc.core.measures.Size;
+
+/**
+ * Manages component instantiation.
+ *
+ * @author Branislav Stojkovic
+ */
+public class InstantiationManager {
+
+ private static final Logger LOG = Logger
+ .getLogger(InstantiationManager.class);
+
+ public static int MAX_RECENT_COMPONENTS = 16;
+
+ private ComponentType componentTypeSlot;
+ private Template template;
+ private List> componentSlot;
+ private Point firstControlPoint;
+ private Point potentialControlPoint;
+
+ private static final ComponentType clipboardType = new ComponentType(
+ "Clipboard contents", "Components from the clipboard",
+ CreationMethod.SINGLE_CLICK, "Multi", "", "", null, null, 0, false,
+ false, null, false, true);
+
+ public InstantiationManager() {
+ }
+
+ public ComponentType getComponentTypeSlot() {
+ return componentTypeSlot;
+ }
+
+ public Template getTemplate() {
+ return template;
+ }
+
+ public List> getComponentSlot() {
+ return componentSlot;
+ }
+
+ public Point getFirstControlPoint() {
+ return firstControlPoint;
+ }
+
+ public Point getPotentialControlPoint() {
+ return potentialControlPoint;
+ }
+
+ public void setComponentTypeSlot(ComponentType componentTypeSlot,
+ Template template, Project currentProject) throws Exception {
+ this.componentTypeSlot = componentTypeSlot;
+ this.template = template;
+ if (componentTypeSlot == null) {
+ this.componentSlot = null;
+ } else {
+ switch (componentTypeSlot.getCreationMethod()) {
+ case POINT_BY_POINT:
+ this.componentSlot = null;
+ break;
+ case SINGLE_CLICK:
+ this.componentSlot = instantiateComponent(componentTypeSlot,
+ template, new Point(0, 0), currentProject);
+ break;
+ }
+ }
+ this.firstControlPoint = null;
+ this.potentialControlPoint = null;
+ }
+
+ public void instatiatePointByPoint(Point scaledPoint, Project currentProject)
+ throws Exception {
+ firstControlPoint = scaledPoint;
+ componentSlot = instantiateComponent(componentTypeSlot, template,
+ firstControlPoint, currentProject);
+
+ // Set the other control point to the same location, we'll
+ // move it later when mouse moves.
+ componentSlot.get(0).setControlPoint(firstControlPoint, 0);
+ componentSlot.get(0).setControlPoint(firstControlPoint, 1);
+ }
+
+ /**
+ * Updates component in the slot with the new second control point.
+ *
+ * @param scaledPoint
+ * @return true, if any change is made
+ */
+ public boolean updatePointByPoint(Point scaledPoint) {
+ boolean changeMade = !scaledPoint.equals(potentialControlPoint);
+ potentialControlPoint = scaledPoint;
+ if (componentSlot != null && !componentSlot.isEmpty()) {
+ componentSlot.get(0).setControlPoint(scaledPoint, 1);
+ }
+ return changeMade;
+ }
+
+ public void pasteComponents(List> components,
+ Point scaledPoint, boolean snapToGrid, Size gridSpacing) {
+ // Adjust location of components so they are centered under the mouse
+ // cursor
+ int minX = Integer.MAX_VALUE;
+ int maxX = Integer.MIN_VALUE;
+ int minY = Integer.MAX_VALUE;
+ int maxY = Integer.MIN_VALUE;
+ for (IDIYComponent> component : components) {
+ for (int i = 0; i < component.getControlPointCount(); i++) {
+ Point p = component.getControlPoint(i);
+ if (p.x > maxX) {
+ maxX = p.x;
+ }
+ if (p.x < minX) {
+ minX = p.x;
+ }
+ if (p.y > maxY) {
+ maxY = p.y;
+ }
+ if (p.y < minY) {
+ minY = p.y;
+ }
+ }
+ }
+ int x = minX;
+ int y = minY;
+ if (snapToGrid) {
+ x = CalcUtils.roundToGrid(x, gridSpacing);
+ x = CalcUtils.roundToGrid(x, gridSpacing);
+ }
+ for (IDIYComponent> component : components) {
+ for (int i = 0; i < component.getControlPointCount(); i++) {
+ Point p = component.getControlPoint(i);
+ p.translate(-x, -y);
+ component.setControlPoint(p, i);
+ }
+ }
+
+ // Update component slot
+ this.componentSlot = new ArrayList>(components);
+
+ // Update the component type slot so the app knows that something's
+ // being instantiated.
+ this.componentTypeSlot = clipboardType;
+
+ if (snapToGrid) {
+ scaledPoint = new Point(scaledPoint);
+ CalcUtils.snapPointToGrid(scaledPoint, gridSpacing);
+ }
+ // Update the location according to mouse location
+ updateSingleClick(scaledPoint, snapToGrid, gridSpacing);
+ }
+
+ /**
+ * Updates location of component slot based on the new mouse location.
+ *
+ * @param scaledPoint
+ * @param snapToGrid
+ * @param gridSpacing
+ * @return true if we need to refresh the canvas
+ */
+ public boolean updateSingleClick(Point scaledPoint, boolean snapToGrid,
+ Size gridSpacing) {
+ if (potentialControlPoint == null) {
+ potentialControlPoint = new Point(0, 0);
+ }
+ if (scaledPoint == null) {
+ scaledPoint = new Point(0, 0);
+ }
+ int dx = scaledPoint.x - potentialControlPoint.x;
+ int dy = scaledPoint.y - potentialControlPoint.y;
+ if (snapToGrid) {
+ dx = CalcUtils.roundToGrid(dx, gridSpacing);
+ dy = CalcUtils.roundToGrid(dy, gridSpacing);
+ }
+ // Only repaint if there's an actual change.
+ if (dx == 0 && dy == 0) {
+ return false;
+ }
+ potentialControlPoint.translate(dx, dy);
+ if (componentSlot == null) {
+ LOG.error("Component slot should not be null!");
+ } else {
+ Point p = new Point();
+ for (IDIYComponent> component : componentSlot) {
+ for (int i = 0; i < component.getControlPointCount(); i++) {
+ p.setLocation(component.getControlPoint(i));
+ p.translate(dx, dy);
+ component.setControlPoint(p, i);
+ }
+ }
+ }
+ return true;
+ }
+
+ @SuppressWarnings("unchecked")
+ public List> instantiateComponent(
+ ComponentType componentType, Template template, Point point,
+ Project currentProject) throws Exception {
+ LOG.info("Instatiating component of type: "
+ + componentType.getInstanceClass().getName());
+
+ // Instantiate the component.
+ IDIYComponent> component = componentType.getInstanceClass()
+ .newInstance();
+
+ component.setName(createUniqueName(componentType, currentProject));
+
+ // Translate them to the desired location.
+ if (point != null) {
+ for (int j = 0; j < component.getControlPointCount(); j++) {
+ Point controlPoint = new Point(component.getControlPoint(j));
+ controlPoint.translate(point.x, point.y);
+ // snapPointToGrid(controlPoint);
+ component.setControlPoint(controlPoint, j);
+ }
+ }
+
+ loadComponentShapeFromTemplate(component, template);
+
+ fillWithDefaultProperties(component, template);
+
+ // Write to recent components
+ List recentComponentTypes = (List) ConfigurationManager
+ .getInstance().readObject(IPlugInPort.RECENT_COMPONENTS_KEY,
+ new ArrayList());
+ String className = componentType.getInstanceClass().getName();
+ if (recentComponentTypes.size() == 0
+ || !recentComponentTypes.get(0).equals(className)) {
+ // Remove if it's already somewhere in the list.
+ recentComponentTypes.remove(className);
+ // Add to the end of the list.
+ recentComponentTypes.add(0, className);
+ // Trim the list if necessary.
+ if (recentComponentTypes.size() > MAX_RECENT_COMPONENTS) {
+ recentComponentTypes.remove(recentComponentTypes.size() - 1);
+ }
+ ConfigurationManager.getInstance().writeValue(
+ IPlugInPort.RECENT_COMPONENTS_KEY, recentComponentTypes);
+ }
+
+ List> list = new ArrayList>();
+ list.add(component);
+ return list;
+ }
+
+ public String createUniqueName(ComponentType componentType,
+ Project currentProject) {
+ boolean exists = true;
+ List> components = currentProject.getComponents();
+ String[] takenNames = new String[components.size()];
+ for (int j = 0; j < currentProject.getComponents().size(); j++) {
+ takenNames[j] = components.get(j).getName();
+ }
+ Arrays.sort(takenNames);
+ int i = 0;
+ while (exists) {
+ i++;
+ String name = componentType.getNamePrefix() + i;
+ exists = false;
+ if (Arrays.binarySearch(takenNames, name) >= 0) {
+ exists = true;
+ }
+ }
+ return componentType.getNamePrefix() + i;
+ }
+
+ /**
+ * Finds any properties that have default values and injects default values.
+ * Typically it should be used for {@link IDIYComponent} and {@link Project}
+ * objects.
+ *
+ * @param object
+ * @param template
+ * @throws IllegalArgumentException
+ * @throws IllegalAccessException
+ * @throws InvocationTargetException
+ * @throws NoSuchMethodException
+ * @throws SecurityException
+ */
+ public void fillWithDefaultProperties(Object object, Template template)
+ throws IllegalArgumentException, IllegalAccessException,
+ InvocationTargetException, SecurityException, NoSuchMethodException {
+ // Extract properties.
+ List properties = ComponentProcessor.getInstance()
+ .extractProperties(object.getClass());
+ Map propertyCache = new HashMap();
+ // Override with default values if available.
+ for (PropertyWrapper property : properties) {
+ propertyCache.put(property.getName(), property);
+ Object defaultValue = ConfigurationManager.getInstance()
+ .readObject(
+ Presenter.DEFAULTS_KEY_PREFIX
+ + object.getClass().getName() + ":"
+ + property.getName(), null);
+ if (defaultValue != null) {
+ property.setValue(defaultValue);
+ property.writeTo(object);
+ }
+ }
+ if (template != null) {
+ for (Map.Entry pair : template.getValues()
+ .entrySet()) {
+ PropertyWrapper property = propertyCache.get(pair.getKey());
+ if (property == null) {
+ LOG.warn("Cannot find property " + pair.getKey());
+ } else {
+ LOG.debug("Filling value from template for "
+ + pair.getKey());
+ property.setValue(pair.getValue());
+ property.writeTo(object);
+ }
+ }
+ }
+ }
+
+ /**
+ * Uses stored control points from the template to shape component.
+ *
+ * @param component
+ * @param template
+ */
+ public void loadComponentShapeFromTemplate(IDIYComponent> component,
+ Template template) {
+ if (template != null
+ && template.getPoints() != null
+ && template.getPoints().size() >= component
+ .getControlPointCount()) {
+ for (int i = 0; i < component.getControlPointCount(); i++) {
+ Point p = new Point(component.getControlPoint(0));
+ p.translate(template.getPoints().get(i).x, template.getPoints()
+ .get(i).y);
+ component.setControlPoint(p, i);
+ }
+ }
+ }
+
+ public void tryToRotateComponentSlot() {
+ if (this.componentSlot == null) {
+ LOG.debug("Component slot is empty, cannot rotate");
+ return;
+ }
+ List properties = ComponentProcessor.getInstance()
+ .extractProperties(this.componentTypeSlot.getInstanceClass());
+ PropertyWrapper angleProperty = null;
+ for (PropertyWrapper propertyWrapper : properties) {
+ if (propertyWrapper.getType().getName().equals(
+ Orientation.class.getName())
+ || propertyWrapper.getType().getName().equals(
+ OrientationHV.class.getName())) {
+ angleProperty = propertyWrapper;
+ break;
+ }
+ }
+ if (angleProperty == null) {
+ LOG
+ .debug("Component in the slot does not have a property of type Orientation, cannot rotate");
+ return;
+ }
+ try {
+ for (IDIYComponent> component : this.componentSlot) {
+ angleProperty.readFrom(component);
+ Object value = angleProperty.getValue();
+ if (value instanceof Orientation) {
+ angleProperty
+ .setValue(Orientation.values()[(((Orientation) value)
+ .ordinal() + 1)
+ % Orientation.values().length]);
+ } else if (value instanceof OrientationHV) {
+ angleProperty
+ .setValue(OrientationHV.values()[(((OrientationHV) value)
+ .ordinal() + 1)
+ % OrientationHV.values().length]);
+ }
+ angleProperty.writeTo(component);
+ }
+ } catch (Exception e) {
+ LOG.warn("Error trying to rotate the component", e);
+ }
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/PCBLayerFiler.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/PCBLayerFiler.java
new file mode 100644
index 00000000..0800e614
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/PCBLayerFiler.java
@@ -0,0 +1,29 @@
+package org.diylc.presenter;
+
+import java.lang.reflect.Method;
+
+import org.diylc.common.IComponentFiler;
+import org.diylc.common.PCBLayer;
+import org.diylc.core.IDIYComponent;
+
+public class PCBLayerFiler implements IComponentFiler {
+
+ private PCBLayer layer;
+
+ public PCBLayerFiler(PCBLayer layer) {
+ super();
+ this.layer = layer;
+ }
+
+ @Override
+ public boolean testComponent(IDIYComponent> component) {
+ Class> clazz = component.getClass();
+ try {
+ Method m = clazz.getMethod("getLayer");
+ PCBLayer l = (PCBLayer) m.invoke(component);
+ return layer == l;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/PointConverter.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/PointConverter.java
new file mode 100644
index 00000000..d7e79d32
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/PointConverter.java
@@ -0,0 +1,41 @@
+package org.diylc.presenter;
+
+import java.awt.Point;
+
+import org.diylc.utils.Constants;
+
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+/**
+ * Serializes {@link Point} objects by converting coordinates from pixels to
+ * inches, thus avoiding the resolution to affect point placement.
+ *
+ * @author Branislav Stojkovic
+ */
+public class PointConverter implements Converter {
+
+ @Override
+ public void marshal(Object object, HierarchicalStreamWriter writer, MarshallingContext context) {
+ Point point = (Point) object;
+ writer.addAttribute("x", Double.toString(1d * point.x / Constants.PIXELS_PER_INCH));
+ writer.addAttribute("y", Double.toString(1d * point.y / Constants.PIXELS_PER_INCH));
+ }
+
+ @Override
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ double x = Double.parseDouble(reader.getAttribute("x"));
+ double y = Double.parseDouble(reader.getAttribute("y"));
+ return new Point((int) Math.round(x * Constants.PIXELS_PER_INCH), (int) Math.round(y
+ * Constants.PIXELS_PER_INCH));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean canConvert(Class clazz) {
+ return Point.class.isAssignableFrom(clazz);
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/Presenter.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/Presenter.java
new file mode 100644
index 00000000..28811558
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/diylc-core/src/org/diylc/presenter/Presenter.java
@@ -0,0 +1,2146 @@
+package org.diylc.presenter;
+
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.dnd.DnDConstants;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Area;
+import java.awt.geom.Point2D;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.JOptionPane;
+
+import org.apache.log4j.Logger;
+import org.diylc.appframework.miscutils.ConfigurationManager;
+import org.diylc.appframework.miscutils.Utils;
+import org.diylc.appframework.simplemq.MessageDispatcher;
+import org.diylc.appframework.update.Version;
+import org.diylc.appframework.update.VersionNumber;
+import org.diylc.common.ComponentType;
+import org.diylc.common.DrawOption;
+import org.diylc.common.EventType;
+import org.diylc.common.IComponentFiler;
+import org.diylc.common.IKeyProcessor;
+import org.diylc.common.IPlugIn;
+import org.diylc.common.IPlugInPort;
+import org.diylc.common.Orientation;
+import org.diylc.common.OrientationHV;
+import org.diylc.common.PropertyWrapper;
+import org.diylc.components.connectivity.SolderPad;
+import org.diylc.core.ExpansionMode;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IView;
+import org.diylc.core.Project;
+import org.diylc.core.Template;
+import org.diylc.core.Theme;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.utils.Constants;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.DomDriver;
+
+/**
+ * The main presenter class, contains core app logic and drawing routines.
+ *
+ * @author Branislav Stojkovic
+ */
+public class Presenter implements IPlugInPort {
+
+ private static final Logger LOG = Logger.getLogger(Presenter.class);
+
+ public static VersionNumber CURRENT_VERSION = new VersionNumber(3, 0, 0);
+ // Read the latest version from the local update.xml file
+ static {
+ try {
+ BufferedInputStream in = new BufferedInputStream(
+ new FileInputStream("update.xml"));
+ XStream xStream = new XStream(new DomDriver());
+ List allVersions = (List) xStream.fromXML(in);
+ in.close();
+ CURRENT_VERSION = allVersions.get(allVersions.size() - 1)
+ .getVersionNumber();
+ } catch (IOException e) {
+ LOG.error("Could not find version number, using default", e);
+ }
+ }
+ public static final String DEFAULTS_KEY_PREFIX = "default.";
+
+ public static final List> EMPTY_SELECTION = Collections
+ .emptyList();
+
+ public static final int ICON_SIZE = 32;
+
+ private Project currentProject;
+ private Map> componentTypes;
+ // Maps component class names to ComponentType objects.
+ private List plugIns;
+
+ private List> 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;
+ private Set> lockedComponents;
+
+ // Utilities
+ // private Cloner cloner;
+ private DrawingManager drawingManager;
+ private ProjectFileManager projectFileManager;
+ private InstantiationManager instantiationManager;
+
+ private Rectangle selectionRect;
+
+ private final IView view;
+
+ private MessageDispatcher messageDispatcher;
+
+ // Layers
+ // private Set lockedLayers;
+ // private Set visibleLayers;
+
+ // D&D
+ private boolean dragInProgress = false;
+ // Previous mouse location, not scaled for zoom factor.
+ private Point previousDragPoint = null;
+ private Project preDragProject = null;
+ private int dragAction;
+ private Point previousScaledPoint;
+
+ public Presenter(IView view) {
+ super();
+ this.view = view;
+ plugIns = new ArrayList();
+ messageDispatcher = new MessageDispatcher(true);
+ selectedComponents = new ArrayList>();
+ lockedComponents = new HashSet>();
+ currentProject = new Project();
+ // cloner = new Cloner();
+ drawingManager = new DrawingManager(messageDispatcher);
+ projectFileManager = new ProjectFileManager(messageDispatcher);
+ instantiationManager = new InstantiationManager();
+
+ // lockedLayers = EnumSet.noneOf(ComponentLayer.class);
+ // visibleLayers = EnumSet.allOf(ComponentLayer.class);
+ }
+
+ public void installPlugin(IPlugIn plugIn) {
+ LOG.info(String.format("installPlugin(%s)", plugIn.getClass()
+ .getSimpleName()));
+ plugIns.add(plugIn);
+ plugIn.connect(this);
+ messageDispatcher.registerListener(plugIn);
+ }
+
+ public void dispose() {
+ for (IPlugIn plugIn : plugIns) {
+ messageDispatcher.unregisterListener(plugIn);
+ }
+ }
+
+ // IPlugInPort
+
+ @Override
+ public Double[] getAvailableZoomLevels() {
+ return new Double[] { 0.25d, 0.3333d, 0.5d, 0.6667d, 0.75d, 1d, 1.25d,
+ 1.5d, 2d };
+ }
+
+ @Override
+ public double getZoomLevel() {
+ return drawingManager.getZoomLevel();
+ }
+
+ @Override
+ public void setZoomLevel(double zoomLevel) {
+ LOG.info(String.format("setZoomLevel(%s)", zoomLevel));
+ if (drawingManager.getZoomLevel() == zoomLevel) {
+ return;
+ }
+ drawingManager.setZoomLevel(zoomLevel);
+ }
+
+ @Override
+ public Cursor getCursorAt(Point point) {
+ // Only change the cursor if we're not making a new component.
+ if (instantiationManager.getComponentTypeSlot() == null) {
+ // Scale point to remove zoom factor.
+ Point2D scaledPoint = scalePoint(point);
+ if (controlPointMap != null && !controlPointMap.isEmpty()) {
+ return Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
+ }
+ for (IDIYComponent> component : currentProject.getComponents()) {
+ if (!isComponentLocked(component)) {
+ Area area = drawingManager.getComponentArea(component);
+ if (area != null && area.contains(scaledPoint)) {
+ return Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
+ }
+ }
+ }
+ }
+ return Cursor.getDefaultCursor();
+ }
+
+ @Override
+ public Dimension getCanvasDimensions(boolean useZoom) {
+ return drawingManager.getCanvasDimensions(currentProject,
+ drawingManager.getZoomLevel(), useZoom);
+ }
+
+ @Override
+ public Project getCurrentProject() {
+ return currentProject;
+ }
+
+ @Override
+ public void loadProject(Project project, boolean freshStart) {
+ LOG.info(String.format("loadProject(%s, %s)", project.getTitle(),
+ freshStart));
+ this.currentProject = project;
+ drawingManager.clearComponentAreaMap();
+ updateSelection(EMPTY_SELECTION);
+ messageDispatcher.dispatchMessage(EventType.PROJECT_LOADED, project,
+ freshStart);
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ messageDispatcher.dispatchMessage(EventType.LAYER_STATE_CHANGED,
+ currentProject.getLockedLayers());
+ }
+
+ @Override
+ public void createNewProject() {
+ LOG.info("createNewFile()");
+ try {
+ Project project = new Project();
+ instantiationManager.fillWithDefaultProperties(project, null);
+ loadProject(project, true);
+ projectFileManager.startNewFile();
+ } catch (Exception e) {
+ LOG.error("Could not create new file", e);
+ view.showMessage(
+ "Could not create a new file. Check the log for details.",
+ "Error", IView.ERROR_MESSAGE);
+ }
+ }
+
+ @Override
+ public void loadProjectFromFile(String fileName) {
+ LOG.info(String.format("loadProjectFromFile(%s)", fileName));
+ try {
+ List warnings = new ArrayList();
+ Project project = (Project) projectFileManager
+ .deserializeProjectFromFile(fileName, warnings);
+ loadProject(project, true);
+ projectFileManager.fireFileStatusChanged();
+ if (!warnings.isEmpty()) {
+ StringBuilder builder = new StringBuilder(
+ "File was opened, but there were some issues with it:
");
+ for (String warning : warnings) {
+ builder.append(warning);
+ builder.append("
");
+ }
+ builder.append("> getComponentTypes() {
+ if (componentTypes == null) {
+ LOG.info("Loading component types.");
+ componentTypes = new HashMap>();
+ Set> componentTypeClasses = null;
+ try {
+ componentTypeClasses = Utils.getClasses("org.diylc.components");
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ // for (String path : classPath) {
+ // path = path.replace('\\', '/');
+ // File f = new File(path);
+ // try {
+ // if (f.isDirectory()) {
+ // LOG.info("Scanning folder for components: "
+ // + f.getAbsolutePath());
+ // componentTypeClasses.addAll(JarScanner.getInstance()
+ // .scanFolder(path, IDIYComponent.class));
+ // } else {
+ // LOG.info("Scanning JAR for components: "
+ // + f.getAbsolutePath());
+ // componentTypeClasses.addAll(JarScanner.getInstance()
+ // .scanJar(f, IDIYComponent.class));
+ // }
+ // } catch (Exception e) {
+ // LOG.warn("Could not scan path " + path, e);
+ // }
+ // }
+ for (Class> clazz : componentTypeClasses) {
+ if (!Modifier.isAbstract(clazz.getModifiers())
+ && IDIYComponent.class.isAssignableFrom(clazz)) {
+ ComponentType componentType = ComponentProcessor
+ .getInstance().extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) clazz);
+ List nestedList;
+ if (componentTypes.containsKey(componentType.getCategory())) {
+ nestedList = componentTypes.get(componentType
+ .getCategory());
+ } else {
+ nestedList = new ArrayList();
+ componentTypes.put(componentType.getCategory(),
+ nestedList);
+ }
+ nestedList.add(componentType);
+ }
+ }
+ }
+ return componentTypes;
+ }
+
+ @Override
+ public void draw(Graphics2D g2d, Set drawOptions,
+ IComponentFiler filter) {
+ if (currentProject == null) {
+ return;
+ }
+ Set> groupedComponents = new HashSet>();
+ for (IDIYComponent> component : currentProject.getComponents()) {
+ // Only try to draw control points of ungrouped components.
+ if (findAllGroupedComponents(component).size() > 1) {
+ groupedComponents.add(component);
+ }
+ }
+ // Don't draw the component in the slot if both control points
+ // match.
+ List> componentSlotToDraw;
+ if (instantiationManager.getFirstControlPoint() != null
+ && instantiationManager.getPotentialControlPoint() != null
+ && instantiationManager.getFirstControlPoint().equals(
+ instantiationManager.getPotentialControlPoint())) {
+ componentSlotToDraw = null;
+ } else {
+ componentSlotToDraw = instantiationManager.getComponentSlot();
+ }
+ List> failedComponents = drawingManager.drawProject(
+ g2d, currentProject, drawOptions, filter, selectionRect,
+ selectedComponents, getLockedComponents(), groupedComponents,
+ Arrays.asList(instantiationManager.getFirstControlPoint(),
+ instantiationManager.getPotentialControlPoint()),
+ componentSlotToDraw, dragInProgress);
+ List failedComponentNames = new ArrayList();
+ for (IDIYComponent> component : failedComponents) {
+ failedComponentNames.add(component.getName());
+ }
+ Collections.sort(failedComponentNames);
+ if (!failedComponentNames.isEmpty()) {
+ messageDispatcher.dispatchMessage(EventType.STATUS_MESSAGE_CHANGED,
+ "Failed to draw components: "
+ + Utils.toCommaString(failedComponentNames)
+ + "");
+ } else {
+ messageDispatcher.dispatchMessage(EventType.STATUS_MESSAGE_CHANGED,
+ "");
+ }
+ }
+
+ /**
+ * Finds all components whose areas include the specified {@link Point}.
+ * Point is not scaled by the zoom factor. Components that belong to
+ * locked layers are ignored.
+ *
+ * @return
+ */
+ public List> findComponentsAtScaled(Point point) {
+ List> components = drawingManager.findComponentsAt(
+ point, currentProject);
+ Iterator> iterator = components.iterator();
+ while (iterator.hasNext()) {
+ if (isComponentLocked(iterator.next())) {
+ iterator.remove();
+ }
+ }
+ return components;
+ }
+
+ @Override
+ public List> findComponentsAt(Point point) {
+ Point scaledPoint = scalePoint(point);
+ List> components = findComponentsAtScaled(scaledPoint);
+ return components;
+ }
+
+ @Override
+ public void mouseClicked(Point point, int button, boolean ctrlDown,
+ boolean shiftDown, boolean altDown, int clickCount) {
+ LOG.debug(String.format("mouseClicked(%s, %s, %s, %s, %s)", point,
+ button, ctrlDown, shiftDown, altDown));
+ Point scaledPoint = scalePoint(point);
+ if (clickCount >= 2) {
+ editSelection();
+ } else {
+ if (instantiationManager.getComponentTypeSlot() != null) {
+ // Try to rotate the component on right click while creating.
+ if (button != IPlugInPort.BUTTON1) {
+ instantiationManager.tryToRotateComponentSlot();
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ return;
+ }
+ // Keep the reference to component type for later.
+ ComponentType componentTypeSlot = instantiationManager
+ .getComponentTypeSlot();
+ Template template = instantiationManager.getTemplate();
+ Project oldProject = currentProject.clone();
+ switch (componentTypeSlot.getCreationMethod()) {
+ case SINGLE_CLICK:
+ try {
+ if (isSnapToGrid()) {
+ CalcUtils.snapPointToGrid(scaledPoint,
+ currentProject.getGridSpacing());
+ }
+ List> componentSlot = instantiationManager
+ .getComponentSlot();
+ List> newSelection = new ArrayList>();
+ for (IDIYComponent> component : componentSlot) {
+ addComponent(component, true);
+ newSelection.add(component);
+ }
+ // Select the new component
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_CHANGED,
+ // selectedComponents);
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_SIZE_CHANGED,
+ // calculateSelectionDimension());
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ updateSelection(newSelection);
+ } catch (Exception e) {
+ LOG.error("Error instatiating component of type: "
+ + componentTypeSlot.getInstanceClass()
+ .getName(), e);
+ }
+
+ if (componentTypeSlot.isAutoEdit()
+ && ConfigurationManager.getInstance().readBoolean(
+ IPlugInPort.AUTO_EDIT_KEY, false)) {
+ editSelection();
+ }
+ if (ConfigurationManager.getInstance().readBoolean(
+ IPlugInPort.CONTINUOUS_CREATION_KEY, false)) {
+ setNewComponentTypeSlot(componentTypeSlot, template);
+ } else {
+ setNewComponentTypeSlot(null, null);
+ }
+ break;
+ case POINT_BY_POINT:
+ // First click is just to set the controlPointSlot and
+ // componentSlot.
+ if (isSnapToGrid()) {
+ CalcUtils.snapPointToGrid(scaledPoint, currentProject
+ .getGridSpacing());
+ }
+ if (instantiationManager.getComponentSlot() == null) {
+ try {
+ instantiationManager.instatiatePointByPoint(
+ scaledPoint, currentProject);
+ } catch (Exception e) {
+ view
+ .showMessage(
+ "Could not create component. Check log for details.",
+ "Error", IView.ERROR_MESSAGE);
+ LOG.error("Could not create component", e);
+ }
+ messageDispatcher.dispatchMessage(
+ EventType.SLOT_CHANGED, componentTypeSlot,
+ instantiationManager.getFirstControlPoint());
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ } else {
+ // On the second click, add the component to the
+ // project.
+ List> componentSlot = instantiationManager
+ .getComponentSlot();
+ componentSlot.get(0).setControlPoint(scaledPoint, 1);
+ List> newSelection = new ArrayList>();
+ for (IDIYComponent> component : componentSlot) {
+ addComponent(component, true);
+ // Select the new component if it's not locked.
+ if (!isComponentLocked(component)) {
+ newSelection.add(component);
+ }
+ }
+
+ updateSelection(newSelection);
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+
+ if (componentTypeSlot.isAutoEdit()
+ && ConfigurationManager.getInstance()
+ .readBoolean(IPlugInPort.AUTO_EDIT_KEY,
+ false)) {
+ editSelection();
+ }
+ if (ConfigurationManager.getInstance().readBoolean(
+ IPlugInPort.CONTINUOUS_CREATION_KEY, false)) {
+ setNewComponentTypeSlot(componentTypeSlot, template);
+ } else {
+ setNewComponentTypeSlot(null, null);
+ }
+ }
+ break;
+ default:
+ LOG.error("Unknown creation method: "
+ + componentTypeSlot.getCreationMethod());
+ }
+ // Notify the listeners.
+ if (!oldProject.equals(currentProject)) {
+ messageDispatcher.dispatchMessage(
+ EventType.PROJECT_MODIFIED, oldProject,
+ currentProject.clone(), "Add "
+ + componentTypeSlot.getName());
+ projectFileManager.notifyFileChange();
+ }
+ } else {
+ List> newSelection = new ArrayList>(
+ selectedComponents);
+ List> components = findComponentsAtScaled(scaledPoint);
+ // If there's nothing under mouse cursor deselect all.
+ if (components.isEmpty()) {
+ if (!ctrlDown) {
+ newSelection.clear();
+ }
+ } else {
+ IDIYComponent> topComponent = components.get(0);
+ // If ctrl is pressed just toggle the component under mouse
+ // cursor.
+ if (ctrlDown) {
+ if (newSelection.contains(topComponent)) {
+ newSelection
+ .removeAll(findAllGroupedComponents(topComponent));
+ } else {
+ newSelection
+ .addAll(findAllGroupedComponents(topComponent));
+ }
+ } else {
+ // Otherwise just select that one component.
+ if (button == BUTTON1
+ || !newSelection.contains(topComponent)) {
+ newSelection.clear();
+ }
+ newSelection
+ .addAll(findAllGroupedComponents(topComponent));
+ }
+ }
+ updateSelection(newSelection);
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_CHANGED,
+ // selectedComponents);
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_SIZE_CHANGED,
+ // calculateSelectionDimension());
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ }
+ }
+ }
+
+ @Override
+ public boolean keyPressed(int key, boolean ctrlDown, boolean shiftDown,
+ boolean altDown) {
+ if (key != VK_DOWN && key != VK_LEFT && key != VK_UP && key != VK_RIGHT) {
+ return false;
+ }
+ LOG.debug(String.format("keyPressed(%s, %s, %s, %s)", key, ctrlDown,
+ shiftDown, altDown));
+ Map, Set> controlPointMap = new HashMap, Set>();
+ // If there aren't any control points, try to add all the selected
+ // components with all their control points. That will allow the
+ // user to drag the whole components.
+ for (IDIYComponent> c : selectedComponents) {
+ Set pointIndices = new HashSet();
+ if (c.getControlPointCount() > 0) {
+ for (int i = 0; i < c.getControlPointCount(); i++) {
+ pointIndices.add(i);
+ }
+ controlPointMap.put(c, pointIndices);
+ }
+ }
+ if (controlPointMap.isEmpty()) {
+ return false;
+ }
+
+ boolean snapToGrid = ConfigurationManager.getInstance().readBoolean(
+ IPlugInPort.SNAP_TO_GRID_KEY, true);
+ if (shiftDown) {
+ snapToGrid = !snapToGrid;
+ }
+
+ if (altDown) {
+ Project oldProject = null;
+ if (key == IKeyProcessor.VK_RIGHT) {
+ oldProject = currentProject.clone();
+ rotateComponents(this.selectedComponents, 1, snapToGrid);
+ } else if (key == IKeyProcessor.VK_LEFT) {
+ oldProject = currentProject.clone();
+ rotateComponents(this.selectedComponents, -1, snapToGrid);
+ } else
+ return false;
+ messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED,
+ oldProject, currentProject.clone(), "Rotate Selection");
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ return true;
+ }
+
+ // Expand control points to include all stuck components.
+ boolean sticky = ConfigurationManager.getInstance().readBoolean(
+ IPlugInPort.STICKY_POINTS_KEY, true);
+ if (ctrlDown) {
+ sticky = !sticky;
+ }
+
+ if (sticky) {
+ includeStuckComponents(controlPointMap);
+ }
+
+ int d;
+ if (snapToGrid) {
+ d = (int) currentProject.getGridSpacing().convertToPixels();
+ } else {
+ d = 1;
+ }
+ int dx = 0;
+ int dy = 0;
+ switch (key) {
+ case IKeyProcessor.VK_DOWN:
+ dy = d;
+ break;
+ case IKeyProcessor.VK_LEFT:
+ dx = -d;
+ break;
+ case IKeyProcessor.VK_UP:
+ dy = -d;
+ break;
+ case IKeyProcessor.VK_RIGHT:
+ dx = d;
+ break;
+ default:
+ return false;
+ }
+
+ Project oldProject = currentProject.clone();
+ moveComponents(controlPointMap, dx, dy, snapToGrid);
+ messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED,
+ oldProject, currentProject.clone(), "Move Selection");
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ return true;
+ }
+
+ @Override
+ public void editSelection() {
+ List properties = getMutualSelectionProperties();
+ if (properties != null && !properties.isEmpty()) {
+ Set defaultedProperties = new HashSet();
+ boolean edited = view.editProperties(properties,
+ defaultedProperties);
+ if (edited) {
+ try {
+ applyPropertiesToSelection(properties);
+ } catch (Exception e1) {
+ view
+ .showMessage(
+ "Error occured while editing selection. Check the log for details.",
+ "Error", JOptionPane.ERROR_MESSAGE);
+ LOG.error("Error applying properties", e1);
+ }
+ // Save default values.
+ for (PropertyWrapper property : defaultedProperties) {
+ if (property.getValue() != null) {
+ setSelectionDefaultPropertyValue(property.getName(),
+ property.getValue());
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void mouseMoved(Point point, boolean ctrlDown, boolean shiftDown,
+ boolean altDown) {
+
+ if (shiftDown) {
+ dragAction = IPlugInPort.DND_TOGGLE_SNAP;
+ } else {
+ dragAction = 0;
+ }
+
+ Map, Set> components = new HashMap, Set>();
+ this.previousScaledPoint = scalePoint(point);
+ if (instantiationManager.getComponentTypeSlot() != null) {
+ if (isSnapToGrid()) {
+ CalcUtils.snapPointToGrid(previousScaledPoint, currentProject
+ .getGridSpacing());
+ }
+ boolean refresh = false;
+ switch (instantiationManager.getComponentTypeSlot()
+ .getCreationMethod()) {
+ case POINT_BY_POINT:
+ refresh = instantiationManager
+ .updatePointByPoint(previousScaledPoint);
+ break;
+ case SINGLE_CLICK:
+ refresh = instantiationManager.updateSingleClick(
+ previousScaledPoint, isSnapToGrid(), currentProject
+ .getGridSpacing());
+ break;
+ }
+ if (refresh) {
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ }
+ } else {
+ // Go backwards so we take the highest z-order components first.
+ for (int i = currentProject.getComponents().size() - 1; i >= 0; i--) {
+ IDIYComponent> component = currentProject.getComponents()
+ .get(i);
+ ComponentType componentType = ComponentProcessor.getInstance()
+ .extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) component
+ .getClass());
+ for (int pointIndex = 0; pointIndex < component
+ .getControlPointCount(); pointIndex++) {
+ Point controlPoint = component.getControlPoint(pointIndex);
+ // Only consider selected components that are not grouped.
+ if (selectedComponents.contains(component)
+ && componentType.isStretchable()
+ && findAllGroupedComponents(component).size() == 1) {
+ try {
+ if (previousScaledPoint.distance(controlPoint) < DrawingManager.CONTROL_POINT_SIZE) {
+ Set indices = new HashSet();
+ if (componentType.isStretchable()) {
+ indices.add(pointIndex);
+ } else {
+ for (int j = 0; j < component
+ .getControlPointCount(); j++) {
+ indices.add(j);
+ }
+ }
+ components.put(component, indices);
+ break;
+ }
+ } catch (Exception e) {
+ LOG
+ .warn("Error reading control point for component of type: "
+ + component.getClass().getName());
+ }
+ }
+ }
+ }
+ }
+
+ messageDispatcher.dispatchMessage(EventType.MOUSE_MOVED,
+ previousScaledPoint);
+
+ if (!components.equals(controlPointMap)) {
+ controlPointMap = components;
+ messageDispatcher.dispatchMessage(
+ EventType.AVAILABLE_CTRL_POINTS_CHANGED,
+ new HashMap, Set>(components));
+ }
+ }
+
+ @Override
+ public List> getSelectedComponents() {
+ return selectedComponents;
+ }
+
+ @Override
+ public void selectAll(int layer) {
+ LOG.info("selectAll()");
+ List> newSelection = new ArrayList>(
+ currentProject.getComponents());
+ newSelection.removeAll(getLockedComponents());
+ if (layer > 0) {
+ Iterator> i = newSelection.iterator();
+ while (i.hasNext()) {
+ IDIYComponent> c = i.next();
+ ComponentType type = ComponentProcessor.getInstance()
+ .extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) c
+ .getClass());
+ if ((int) type.getZOrder() != layer)
+ i.remove();
+ }
+ }
+ updateSelection(newSelection);
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_CHANGED,
+ // selectedComponents);
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_SIZE_CHANGED,
+ // calculateSelectionDimension());
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ }
+
+ @Override
+ public VersionNumber getCurrentVersionNumber() {
+ return CURRENT_VERSION;
+ }
+
+ @Override
+ public void dragStarted(Point point, int dragAction) {
+ LOG.debug(String.format("dragStarted(%s, %s)", point, dragAction));
+ if (instantiationManager.getComponentTypeSlot() != null) {
+ LOG
+ .debug("Cannot start drag because a new component is being created.");
+ mouseClicked(point, IPlugInPort.BUTTON1,
+ dragAction == DnDConstants.ACTION_COPY,
+ dragAction == DnDConstants.ACTION_LINK,
+ dragAction == DnDConstants.ACTION_MOVE, 1);
+ return;
+ }
+ this.dragInProgress = true;
+ this.dragAction = dragAction;
+ this.preDragProject = currentProject.clone();
+ Point scaledPoint = scalePoint(point);
+ this.previousDragPoint = scaledPoint;
+ List> components = findComponentsAtScaled(scaledPoint);
+ if (!this.controlPointMap.isEmpty()) {
+ // If we're dragging control points reset selection.
+ updateSelection(new ArrayList>(
+ this.controlPointMap.keySet()));
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_CHANGED,
+ // selectedComponents);
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_SIZE_CHANGED,
+ // calculateSelectionDimension());
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ } else if (components.isEmpty()) {
+ // If there are no components are under the cursor, reset selection.
+ updateSelection(EMPTY_SELECTION);
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_CHANGED,
+ // selectedComponents);
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_SIZE_CHANGED,
+ // calculateSelectionDimension());
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ } else {
+ // Take the last component, i.e. the top order component.
+ IDIYComponent> component = components.get(0);
+ // If the component under the cursor is not already selected, make
+ // it into the only selected component.
+ if (!selectedComponents.contains(component)) {
+ updateSelection(new ArrayList>(
+ findAllGroupedComponents(component)));
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_CHANGED,
+ // selectedComponents);
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_SIZE_CHANGED,
+ // calculateSelectionDimension());
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ }
+ // If there aren't any control points, try to add all the selected
+ // components with all their control points. That will allow the
+ // user to drag the whole components.
+ for (IDIYComponent> c : selectedComponents) {
+ Set pointIndices = new HashSet();
+ if (c.getControlPointCount() > 0) {
+ for (int i = 0; i < c.getControlPointCount(); i++) {
+ pointIndices.add(i);
+ }
+ this.controlPointMap.put(c, pointIndices);
+ }
+ }
+ // Expand control points to include all stuck components.
+ boolean sticky = ConfigurationManager.getInstance().readBoolean(
+ IPlugInPort.STICKY_POINTS_KEY, true);
+ if (this.dragAction == IPlugInPort.DND_TOGGLE_STICKY) {
+ sticky = !sticky;
+ }
+ if (sticky) {
+ includeStuckComponents(controlPointMap);
+ }
+ }
+ }
+
+ @Override
+ public void dragActionChanged(int dragAction) {
+ LOG.debug("dragActionChanged(" + dragAction + ")");
+ this.dragAction = dragAction;
+ }
+
+ /**
+ * Finds any components that are stuck to one of the components already in
+ * the map.
+ *
+ * @param controlPointMap
+ */
+ private void includeStuckComponents(
+ Map, Set> controlPointMap) {
+ int oldSize = controlPointMap.size();
+ LOG.trace("Expanding selected component map");
+ for (IDIYComponent> component : currentProject.getComponents()) {
+ ComponentType componentType = ComponentProcessor.getInstance()
+ .extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) component
+ .getClass());
+
+ // Check if there's a control point in the current selection
+ // that matches with one of its control points.
+ for (int i = 0; i < component.getControlPointCount(); i++) {
+ // Do not process a control point if it's already in the map and
+ // if it's locked.
+ if ((!controlPointMap.containsKey(component) || !controlPointMap
+ .get(component).contains(i))
+ && !isComponentLocked(component)) {
+ if (component.isControlPointSticky(i)) {
+ boolean componentMatches = false;
+ for (Map.Entry, Set> entry : controlPointMap
+ .entrySet()) {
+ if (componentMatches) {
+ break;
+ }
+ for (Integer j : entry.getValue()) {
+ Point firstPoint = component.getControlPoint(i);
+ if (entry.getKey().isControlPointSticky(j)) {
+ Point secondPoint = entry.getKey()
+ .getControlPoint(j);
+ // If they are close enough we can consider
+ // them matched.
+ if (firstPoint.distance(secondPoint) < DrawingManager.CONTROL_POINT_SIZE) {
+ componentMatches = true;
+ break;
+ }
+ }
+ }
+ }
+ if (componentMatches) {
+ LOG.trace("Including component: " + component);
+ Set indices = new HashSet();
+ // For stretchable components just add the
+ // matching component. Otherwise, add all control
+ // points.
+ if (componentType.isStretchable()) {
+ indices.add(i);
+ } else {
+ for (int k = 0; k < component
+ .getControlPointCount(); k++) {
+ indices.add(k);
+ }
+ }
+ if (controlPointMap.containsKey(component)) {
+ controlPointMap.get(component).addAll(indices);
+ } else {
+ controlPointMap.put(component, indices);
+ }
+ }
+ }
+ }
+ }
+ }
+ int newSize = controlPointMap.size();
+ // As long as we're adding new components, do another iteration.
+ if (newSize > oldSize) {
+ LOG.trace("Component count changed, trying one more time.");
+ includeStuckComponents(controlPointMap);
+ } else {
+ LOG.trace("Component count didn't change, done with expanding.");
+ }
+ }
+
+ private boolean isSnapToGrid() {
+ boolean snapToGrid = ConfigurationManager.getInstance().readBoolean(
+ IPlugInPort.SNAP_TO_GRID_KEY, true);
+ if (this.dragAction == IPlugInPort.DND_TOGGLE_SNAP)
+ snapToGrid = !snapToGrid;
+ return snapToGrid;
+ }
+
+ @Override
+ public boolean dragOver(Point point) {
+ if (point == null) {
+ return false;
+ }
+ Point scaledPoint = scalePoint(point);
+ if (!controlPointMap.isEmpty()) {
+ // We're dragging control point(s).
+ int dx = (scaledPoint.x - previousDragPoint.x);
+ int dy = (scaledPoint.y - previousDragPoint.y);
+
+ Point actualD = moveComponents(this.controlPointMap, dx, dy,
+ isSnapToGrid());
+ if (actualD == null)
+ return true;
+
+ previousDragPoint.translate(actualD.x, actualD.y);
+ } else if (selectedComponents.isEmpty()
+ && instantiationManager.getComponentTypeSlot() == null) {
+ // If there's no selection, the only thing to do is update the
+ // selection rectangle and refresh.
+ Rectangle oldSelectionRect = selectionRect == null ? null
+ : new Rectangle(selectionRect);
+ this.selectionRect = Utils.createRectangle(scaledPoint,
+ previousDragPoint);
+ if (selectionRect.equals(oldSelectionRect)) {
+ return true;
+ }
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_RECT_CHANGED,
+ // selectionRect);
+ }
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ return true;
+ }
+
+ private Point moveComponents(
+ Map, Set> controlPointMap, int dx,
+ int dy, boolean snapToGrid) {
+ // After we make the transfer and snap to grid, calculate actual dx
+ // and dy. We'll use them to translate the previous drag point.
+ int actualDx = 0;
+ int actualDy = 0;
+ // For each component, do a simulation of the move to see if any of
+ // them will overlap or go out of bounds.
+ int width = (int) currentProject.getWidth().convertToPixels();
+ int height = (int) currentProject.getHeight().convertToPixels();
+
+ if (controlPointMap.size() == 1) {
+ Map.Entry, Set> entry = controlPointMap
+ .entrySet().iterator().next();
+
+ Point firstPoint = entry.getKey().getControlPoint(
+ entry.getValue().toArray(new Integer[] {})[0]);
+ Point testPoint = new Point(firstPoint);
+ testPoint.translate(dx, dy);
+ if (snapToGrid) {
+ CalcUtils.snapPointToGrid(testPoint, currentProject
+ .getGridSpacing());
+ }
+
+ actualDx = testPoint.x - firstPoint.x;
+ actualDy = testPoint.y - firstPoint.y;
+ } else if (snapToGrid) {
+ actualDx = CalcUtils.roundToGrid(dx, currentProject
+ .getGridSpacing());
+ actualDy = CalcUtils.roundToGrid(dy, currentProject
+ .getGridSpacing());
+ } else {
+ actualDx = dx;
+ actualDy = dy;
+ }
+
+ if (actualDx == 0 && actualDy == 0) {
+ // Nothing to move.
+ return null;
+ }
+
+ // Validate if moving can be done.
+ for (Map.Entry, Set> entry : controlPointMap
+ .entrySet()) {
+ IDIYComponent> component = entry.getKey();
+ Point[] controlPoints = new Point[component.getControlPointCount()];
+ for (int index = 0; index < component.getControlPointCount(); index++) {
+ controlPoints[index] = new Point(component
+ .getControlPoint(index));
+ // When the first point is moved, calculate how much it
+ // actually moved after snapping.
+ if (entry.getValue().contains(index)) {
+ controlPoints[index].translate(actualDx, actualDy);
+ if (controlPoints[index].x < 0
+ || controlPoints[index].y < 0
+ || controlPoints[index].x > width
+ || controlPoints[index].y > height) {
+ // At least one control point went out of bounds.
+ return null;
+ }
+ }
+ // For control points that may overlap, just write null,
+ // we'll ignore them later.
+ if (component.canControlPointOverlap(index)) {
+ controlPoints[index] = null;
+ }
+ }
+
+ for (int i = 0; i < controlPoints.length - 1; i++) {
+ for (int j = i + 1; j < controlPoints.length; j++) {
+ if (controlPoints[i] != null && controlPoints[j] != null
+ && controlPoints[i].equals(controlPoints[j])) {
+ // Control points collision detected, cannot make
+ // this move.
+ return null;
+ }
+ }
+ }
+ }
+
+ // Update all points to new location.
+ for (Map.Entry, Set> entry : controlPointMap
+ .entrySet()) {
+ IDIYComponent> c = entry.getKey();
+ drawingManager.invalidateComponent(c);
+ for (Integer index : entry.getValue()) {
+ Point p = new Point(c.getControlPoint(index));
+ p.translate(actualDx, actualDy);
+ c.setControlPoint(p, index);
+ }
+ }
+ return new Point(actualDx, actualDy);
+ }
+
+ @Override
+ public void rotateSelection(int direction) {
+ if (!selectedComponents.isEmpty()) {
+ Project oldProject = currentProject.clone();
+ rotateComponents(this.selectedComponents, direction, isSnapToGrid());
+ messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED,
+ oldProject, currentProject.clone(), "Rotate Selection");
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ }
+ }
+
+ /**
+ *
+ * @param direction
+ * 1 for clockwise, -1 for counter-clockwise
+ */
+ private void rotateComponents(List> components,
+ int direction, boolean snapToGrid) {
+ Point center = getCenterOf(components, snapToGrid);
+
+ AffineTransform rotate = AffineTransform.getRotateInstance(Math.PI / 2
+ * direction, center.x, center.y);
+
+ // Update all points to new location.
+ for (IDIYComponent> component : components) {
+ drawingManager.invalidateComponent(component);
+ ComponentType type = ComponentProcessor.getInstance()
+ .extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) component
+ .getClass());
+ if (type.isRotatable()) {
+ for (int index = 0; index < component.getControlPointCount(); index++) {
+ Point p = new Point(component.getControlPoint(index));
+ rotate.transform(p, p);
+ component.setControlPoint(p, index);
+ }
+ // If component has orientation, change it too
+ List newProperties = ComponentProcessor
+ .getInstance().extractProperties(component.getClass());
+ for (PropertyWrapper property : newProperties) {
+ if (property.getType() == Orientation.class) {
+ try {
+ property.readFrom(component);
+ Orientation orientation = (Orientation) property
+ .getValue();
+ Orientation[] values = Orientation.values();
+ int newIndex = orientation.ordinal() + direction;
+ if (newIndex < 0)
+ newIndex = values.length - 1;
+ else if (newIndex >= values.length)
+ newIndex = 0;
+ property.setValue(values[newIndex]);
+ property.writeTo(component);
+ } catch (Exception e) {
+ LOG.error(
+ "Could not change component orientation for "
+ + component.getName(), e);
+ }
+ } else if (property.getType() == OrientationHV.class) {
+ try {
+ property.readFrom(component);
+ OrientationHV orientation = (OrientationHV) property
+ .getValue();
+ property
+ .setValue(OrientationHV.values()[1 - orientation
+ .ordinal()]);
+ property.writeTo(component);
+ } catch (Exception e) {
+ LOG.error(
+ "Could not change component orientation for "
+ + component.getName(), e);
+ }
+ }
+ }
+ } else {
+ // Non-rotatable
+ Point componentCenter = getCenterOf(Arrays
+ .asList(new IDIYComponent>[] { component }), false);
+ Point rotatedComponentCenter = new Point();
+ rotate.transform(componentCenter, rotatedComponentCenter);
+ for (int index = 0; index < component.getControlPointCount(); index++) {
+ Point p = new Point(component.getControlPoint(index));
+ p.translate(rotatedComponentCenter.x - componentCenter.x,
+ rotatedComponentCenter.y - componentCenter.y);
+ component.setControlPoint(p, index);
+ }
+ }
+ }
+ }
+
+ private Point getCenterOf(List> components,
+ boolean snapToGrid) {
+ // Determine center of rotation
+ int minX = Integer.MAX_VALUE;
+ int minY = Integer.MAX_VALUE;
+ int maxX = Integer.MIN_VALUE;
+ int maxY = Integer.MIN_VALUE;
+ for (IDIYComponent> component : components) {
+ for (int i = 0; i < component.getControlPointCount(); i++) {
+ Point p = component.getControlPoint(i);
+ if (minX > p.x) {
+ minX = p.x;
+ }
+ if (maxX < p.x) {
+ maxX = p.x;
+ }
+ if (minY > p.y) {
+ minY = p.y;
+ }
+ if (maxY < p.y) {
+ maxY = p.y;
+ }
+ }
+ }
+ int centerX = (maxX + minX) / 2;
+ int centerY = (maxY + minY) / 2;
+
+ if (snapToGrid) {
+ CalcUtils
+ .roundToGrid(centerX, this.currentProject.getGridSpacing());
+ CalcUtils
+ .roundToGrid(centerY, this.currentProject.getGridSpacing());
+ }
+
+ return new Point(centerX, centerY);
+ }
+
+ @Override
+ public void dragEnded(Point point) {
+ LOG.debug(String.format("dragEnded(%s)", point));
+ if (!dragInProgress) {
+ return;
+ }
+ Point scaledPoint = scalePoint(point);
+ if (selectedComponents.isEmpty()) {
+ // If there's no selection finalize selectionRect and see which
+ // components intersect with it.
+ if (scaledPoint != null) {
+ this.selectionRect = Utils.createRectangle(scaledPoint,
+ previousDragPoint);
+ }
+ List> newSelection = new ArrayList>();
+ for (IDIYComponent> component : currentProject.getComponents()) {
+ if (!isComponentLocked(component)) {
+ Area area = drawingManager.getComponentArea(component);
+ if ((area != null) && (selectionRect != null)
+ && area.intersects(selectionRect)) {
+ newSelection
+ .addAll(findAllGroupedComponents(component));
+ }
+ }
+ }
+ selectionRect = null;
+ updateSelection(newSelection);
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_CHANGED,
+ // selectedComponents);
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_SIZE_CHANGED,
+ // calculateSelectionDimension());
+ } else {
+ updateSelection(selectedComponents);
+ }
+ // There is selection, so we need to finalize the drag&drop
+ // operation.
+
+ if (!preDragProject.equals(currentProject)) {
+ messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED,
+ preDragProject, currentProject.clone(), "Drag");
+ projectFileManager.notifyFileChange();
+ }
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ dragInProgress = false;
+ }
+
+ @Override
+ public void pasteComponents(List> components) {
+ LOG.info(String.format("pasteComponents(%s)", components));
+ instantiationManager.pasteComponents(components,
+ this.previousScaledPoint, isSnapToGrid(), currentProject
+ .getGridSpacing());
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ messageDispatcher.dispatchMessage(EventType.SLOT_CHANGED,
+ instantiationManager.getComponentTypeSlot(),
+ instantiationManager.getFirstControlPoint());
+ }
+
+ @Override
+ public void deleteSelectedComponents() {
+ LOG.info("deleteSelectedComponents()");
+ if (selectedComponents.isEmpty()) {
+ LOG.debug("Nothing to delete");
+ return;
+ }
+ Project oldProject = currentProject.clone();
+ // Remove selected components from any groups.
+ ungroupComponents(selectedComponents);
+ // Remove from area map.
+ for (IDIYComponent> component : selectedComponents) {
+ drawingManager.invalidateComponent(component);
+ }
+ currentProject.getComponents().removeAll(selectedComponents);
+ messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED,
+ oldProject, currentProject.clone(), "Delete");
+ projectFileManager.notifyFileChange();
+ updateSelection(EMPTY_SELECTION);
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ }
+
+ @Override
+ public void setSelectionDefaultPropertyValue(String propertyName,
+ Object value) {
+ LOG.info(String.format("setSelectionDefaultPropertyValue(%s, %s)",
+ propertyName, value));
+ for (IDIYComponent> component : selectedComponents) {
+ String className = component.getClass().getName();
+ LOG.debug("Default property value set for " + className + ":"
+ + propertyName);
+ ConfigurationManager.getInstance()
+ .writeValue(
+ DEFAULTS_KEY_PREFIX + className + ":"
+ + propertyName, value);
+ }
+ }
+
+ @Override
+ public void setProjectDefaultPropertyValue(String propertyName, Object value) {
+ LOG.info(String.format("setProjectDefaultPropertyValue(%s, %s)",
+ propertyName, value));
+ LOG.debug("Default property value set for " + Project.class.getName()
+ + ":" + propertyName);
+ ConfigurationManager.getInstance().writeValue(
+ DEFAULTS_KEY_PREFIX + Project.class.getName() + ":"
+ + propertyName, value);
+ }
+
+ @Override
+ public void setMetric(boolean isMetric) {
+ ConfigurationManager.getInstance().writeValue(Presenter.METRIC_KEY,
+ isMetric);
+ }
+
+ @Override
+ public void groupSelectedComponents() {
+ LOG.info("groupSelectedComponents()");
+ Project oldProject = currentProject.clone();
+ // First remove the selected components from other groups.
+ ungroupComponents(selectedComponents);
+ // Then group them together.
+ currentProject.getGroups().add(
+ new HashSet>(selectedComponents));
+ // Notify the listeners.
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ if (!oldProject.equals(currentProject)) {
+ messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED,
+ oldProject, currentProject.clone(), "Group");
+ projectFileManager.notifyFileChange();
+ }
+ }
+
+ @Override
+ public void ungroupSelectedComponents() {
+ LOG.info("ungroupSelectedComponents()");
+ Project oldProject = currentProject.clone();
+ ungroupComponents(selectedComponents);
+ // Notify the listeners.
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ if (!oldProject.equals(currentProject)) {
+ messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED,
+ oldProject, currentProject.clone(), "Ungroup");
+ projectFileManager.notifyFileChange();
+ }
+ }
+
+ @Override
+ public void setLayerLocked(int layerZOrder, boolean locked) {
+ LOG.info(String.format("setLayerLocked(%s, %s)", layerZOrder, locked));
+ if (locked) {
+ currentProject.getLockedLayers().add(layerZOrder);
+ } else {
+ currentProject.getLockedLayers().remove(layerZOrder);
+ }
+ updateSelection(EMPTY_SELECTION);
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ messageDispatcher.dispatchMessage(EventType.LAYER_STATE_CHANGED,
+ currentProject.getLockedLayers());
+ }
+
+ @Override
+ public void sendSelectionToBack() {
+ LOG.info("sendSelectionToBack()");
+ Project oldProject = currentProject.clone();
+ for (IDIYComponent> component : selectedComponents) {
+ ComponentType componentType = ComponentProcessor.getInstance()
+ .extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) component
+ .getClass());
+ int index = currentProject.getComponents().indexOf(component);
+ if (index < 0) {
+ LOG.warn("Component not found in the project: "
+ + component.getName());
+ } else
+ while (index > 0) {
+ IDIYComponent> componentBefore = currentProject
+ .getComponents().get(index - 1);
+ ComponentType componentBeforeType = ComponentProcessor
+ .getInstance()
+ .extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) componentBefore
+ .getClass());
+ if (!componentType.isFlexibleZOrder()
+ && componentBeforeType.getZOrder() < componentType
+ .getZOrder())
+ break;
+ Collections.swap(currentProject.getComponents(), index,
+ index - 1);
+ index--;
+ }
+ }
+ if (!oldProject.equals(currentProject)) {
+ messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED,
+ oldProject, currentProject.clone(), "Send to Back");
+ projectFileManager.notifyFileChange();
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ }
+ }
+
+ @Override
+ public void bringSelectionToFront() {
+ LOG.info("bringSelectionToFront()");
+ Project oldProject = currentProject.clone();
+ for (IDIYComponent> component : selectedComponents) {
+ ComponentType componentType = ComponentProcessor.getInstance()
+ .extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) component
+ .getClass());
+ int index = currentProject.getComponents().indexOf(component);
+ if (index < 0) {
+ LOG.warn("Component not found in the project: "
+ + component.getName());
+ } else
+ while (index < currentProject.getComponents().size() - 1) {
+ IDIYComponent> componentAfter = currentProject
+ .getComponents().get(index + 1);
+ ComponentType componentAfterType = ComponentProcessor
+ .getInstance()
+ .extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) componentAfter
+ .getClass());
+ if (!componentType.isFlexibleZOrder()
+ && componentAfterType.getZOrder() > componentType
+ .getZOrder())
+ break;
+ Collections.swap(currentProject.getComponents(), index,
+ index + 1);
+ index++;
+ }
+ }
+ if (!oldProject.equals(currentProject)) {
+ messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED,
+ oldProject, currentProject.clone(), "Bring to Front");
+ projectFileManager.notifyFileChange();
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ }
+ }
+
+ @Override
+ public void refresh() {
+ LOG.info("refresh()");
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ }
+
+ @Override
+ public Theme getSelectedTheme() {
+ return drawingManager.getTheme();
+ }
+
+ @Override
+ public void setSelectedTheme(Theme theme) {
+ drawingManager.setTheme(theme);
+ }
+
+ @Override
+ public void renumberSelectedComponents(final boolean xAxisFirst) {
+ LOG.info("renumberSelectedComponents(" + xAxisFirst + ")");
+ if (getSelectedComponents().isEmpty()) {
+ return;
+ }
+ Project oldProject = currentProject.clone();
+ List> components = new ArrayList>(
+ getSelectedComponents());
+ // Sort components by their location.
+ Collections.sort(components, new Comparator>() {
+
+ @Override
+ public int compare(IDIYComponent> o1, IDIYComponent> o2) {
+ int sumX1 = 0;
+ int sumY1 = 0;
+ int sumX2 = 0;
+ int sumY2 = 0;
+ for (int i = 0; i < o1.getControlPointCount(); i++) {
+ sumX1 += o1.getControlPoint(i).getX();
+ sumY1 += o1.getControlPoint(i).getY();
+ }
+ for (int i = 0; i < o2.getControlPointCount(); i++) {
+ sumX2 += o2.getControlPoint(i).getX();
+ sumY2 += o2.getControlPoint(i).getY();
+ }
+ sumX1 /= o1.getControlPointCount();
+ sumY1 /= o1.getControlPointCount();
+ sumX2 /= o2.getControlPointCount();
+ sumY2 /= o2.getControlPointCount();
+
+ if (xAxisFirst) {
+ if (sumY1 < sumY2) {
+ return -1;
+ } else if (sumY1 > sumY2) {
+ return 1;
+ } else {
+ if (sumX1 < sumX2) {
+ return -1;
+ } else if (sumX1 > sumX2) {
+ return 1;
+ }
+ }
+ } else {
+ if (sumX1 < sumX2) {
+ return -1;
+ } else if (sumX1 > sumX2) {
+ return 1;
+ } else {
+ if (sumY1 < sumY2) {
+ return -1;
+ } else if (sumY1 > sumY2) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+ }
+ });
+ // Clear names.
+ for (IDIYComponent> component : components) {
+ component.setName("");
+ }
+ // Assign new ones.
+ for (IDIYComponent> component : components) {
+ component.setName(instantiationManager.createUniqueName(
+ ComponentProcessor.getInstance().extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) component
+ .getClass()), currentProject));
+ }
+
+ messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED,
+ oldProject, currentProject.clone(), "Renumber selection");
+ projectFileManager.notifyFileChange();
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ }
+
+ public void updateSelection(List> newSelection) {
+ this.selectedComponents = newSelection;
+ Map, Set> controlPointMap = new HashMap, Set>();
+ for (IDIYComponent> component : selectedComponents) {
+ Set indices = new HashSet();
+ for (int i = 0; i < component.getControlPointCount(); i++) {
+ indices.add(i);
+ }
+ controlPointMap.put(component, indices);
+ }
+ if (ConfigurationManager.getInstance().readBoolean(
+ IPlugInPort.STICKY_POINTS_KEY, true)) {
+ includeStuckComponents(controlPointMap);
+ }
+ messageDispatcher.dispatchMessage(EventType.SELECTION_CHANGED,
+ selectedComponents, controlPointMap.keySet());
+ }
+
+ @Override
+ public void expandSelection(ExpansionMode expansionMode) {
+ LOG.info(String.format("expandSelection(%s)", expansionMode));
+ List> newSelection = new ArrayList>(
+ this.selectedComponents);
+ // Find control points of all selected components and all types
+ Set selectedNamePrefixes = new HashSet();
+ if (expansionMode == ExpansionMode.SAME_TYPE) {
+ for (IDIYComponent> component : getSelectedComponents()) {
+ selectedNamePrefixes.add(ComponentProcessor.getInstance()
+ .extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) component
+ .getClass()).getNamePrefix());
+ }
+ }
+ // Now try to find components that intersect with at least one component
+ // in the pool.
+ for (IDIYComponent> component : getCurrentProject().getComponents()) {
+ // Skip already selected components or ones that cannot be stuck to
+ // other components.
+ Area area = drawingManager.getComponentArea(component);
+ if (newSelection.contains(component)
+ || !component.isControlPointSticky(0) || area == null)
+ continue;
+ boolean matches = false;
+ for (IDIYComponent> selectedComponent : this.selectedComponents) {
+ Area selectedArea = drawingManager
+ .getComponentArea(selectedComponent);
+ if (selectedArea == null)
+ continue;
+ Area intersection = new Area(area);
+ intersection.intersect(selectedArea);
+ if (!intersection.isEmpty()) {
+ matches = true;
+ break;
+ }
+ }
+
+ if (matches) {
+ switch (expansionMode) {
+ case ALL:
+ case IMMEDIATE:
+ newSelection.add(component);
+ break;
+ case SAME_TYPE:
+ if (selectedNamePrefixes
+ .contains(ComponentProcessor
+ .getInstance()
+ .extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) component
+ .getClass())
+ .getNamePrefix())) {
+ newSelection.add(component);
+ }
+ break;
+ }
+ }
+ }
+
+ int oldSize = this.getSelectedComponents().size();
+ updateSelection(newSelection);
+ // Go deeper if possible.
+ if (newSelection.size() > oldSize
+ && expansionMode != ExpansionMode.IMMEDIATE) {
+ expandSelection(expansionMode);
+ }
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ }
+
+ /**
+ * Removes all the groups that contain at least one of the specified
+ * components.
+ *
+ * @param components
+ */
+ private void ungroupComponents(Collection> components) {
+ Iterator>> groupIterator = currentProject
+ .getGroups().iterator();
+ while (groupIterator.hasNext()) {
+ Set> group = groupIterator.next();
+ group.removeAll(components);
+ if (group.isEmpty()) {
+ groupIterator.remove();
+ }
+ }
+ }
+
+ /**
+ * Finds all components that are grouped with the specified component. This
+ * should be called any time components are added or removed from the
+ * selection.
+ *
+ * @param component
+ * @return set of all components that belong to the same group with the
+ * specified component. At the minimum, set contains that single
+ * component.
+ */
+ private Set> findAllGroupedComponents(
+ IDIYComponent> component) {
+ Set> components = new HashSet>();
+ components.add(component);
+ for (Set> group : currentProject.getGroups()) {
+ if (group.contains(component)) {
+ components.addAll(group);
+ break;
+ }
+ }
+ return components;
+ }
+
+ @Override
+ public Point2D calculateSelectionDimension() {
+ if (selectedComponents.isEmpty()) {
+ return null;
+ }
+ boolean metric = ConfigurationManager.getInstance().readBoolean(
+ METRIC_KEY, true);
+ Area area = new Area();
+ for (IDIYComponent> component : selectedComponents) {
+ Area componentArea = drawingManager.getComponentArea(component);
+ if (componentArea != null) {
+ area.add(componentArea);
+ } else {
+ LOG.warn("No area found for: " + component.getName());
+ }
+ }
+ double width = area.getBounds2D().getWidth();
+ double height = area.getBounds2D().getHeight();
+ width /= Constants.PIXELS_PER_INCH;
+ height /= Constants.PIXELS_PER_INCH;
+ if (metric) {
+ width *= SizeUnit.in.getFactor() / SizeUnit.cm.getFactor();
+ height *= SizeUnit.in.getFactor() / SizeUnit.cm.getFactor();
+ }
+ Point2D dimension = new Point2D.Double(width, height);
+ return dimension;
+ }
+
+ // @Override
+ // public Rectangle2D getSelectedAreaRect() {
+ // if (selectedComponents.isEmpty()) {
+ // return null;
+ // }
+ // Area area = new Area();
+ // for (IDIYComponent> component : selectedComponents) {
+ // Area componentArea = drawingManager.getComponentArea(component);
+ // if (componentArea != null) {
+ // area.add(componentArea);
+ // } else {
+ // LOG.warn("No area found for: " + component.getName());
+ // }
+ // }
+ // return area.getBounds2D();
+ // }
+
+ /**
+ * Adds a component to the project taking z-order into account.
+ *
+ * @param component
+ */
+ private void addComponent(IDIYComponent> component, boolean canCreatePads) {
+ int index = currentProject.getComponents().size();
+ while (index > 0
+ && ComponentProcessor.getInstance().extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) component
+ .getClass()).getZOrder() < ComponentProcessor
+ .getInstance()
+ .extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) currentProject
+ .getComponents().get(index - 1)
+ .getClass()).getZOrder()) {
+ index--;
+ }
+ if (index < currentProject.getComponents().size()) {
+ currentProject.getComponents().add(index, component);
+ } else {
+ currentProject.getComponents().add(component);
+ }
+ if (canCreatePads
+ && ConfigurationManager.getInstance().readBoolean(
+ IPlugInPort.AUTO_PADS_KEY, false)
+ && !(component instanceof SolderPad)) {
+ ComponentType padType = ComponentProcessor.getInstance()
+ .extractComponentTypeFrom(SolderPad.class);
+ for (int i = 0; i < component.getControlPointCount(); i++) {
+ if (component.isControlPointSticky(i)) {
+ try {
+ IDIYComponent> pad = instantiationManager
+ .instantiateComponent(padType, null,
+ component.getControlPoint(i),
+ currentProject).get(0);
+ pad.setControlPoint(component.getControlPoint(i), 0);
+ addComponent(pad, false);
+ } catch (Exception e) {
+ LOG.warn("Could not auto-create solder pad", e);
+ }
+ // SolderPad pad = new SolderPad();
+ // pad.setControlPoint(component.getControlPoint(i), 0);
+ // addComponent(pad,
+ // ComponentProcessor.getInstance().extractComponentTypeFrom(
+ // SolderPad.class), false);
+ }
+ }
+ }
+ }
+
+ @Override
+ public List getMutualSelectionProperties() {
+ try {
+ return ComponentProcessor.getInstance()
+ .getMutualSelectionProperties(selectedComponents);
+ } catch (Exception e) {
+ LOG.error("Could not get mutual selection properties", e);
+ return null;
+ }
+ }
+
+ private void applyPropertiesToSelection(List properties) {
+ LOG.debug(String.format("applyPropertiesToSelection(%s)", properties));
+ Project oldProject = currentProject.clone();
+ try {
+ for (IDIYComponent> component : selectedComponents) {
+ drawingManager.invalidateComponent(component);
+ for (PropertyWrapper property : properties) {
+ if (property.isChanged()) {
+ property.writeTo(component);
+ }
+ }
+ }
+ } catch (Exception e) {
+ LOG.error("Could not apply selection properties", e);
+ view
+ .showMessage(
+ "Could not apply changes to the selection. Check the log for details.",
+ "Error", IView.ERROR_MESSAGE);
+ } finally {
+ // Notify the listeners.
+ if (!oldProject.equals(currentProject)) {
+ messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED,
+ oldProject, currentProject.clone(), "Edit Selection");
+ projectFileManager.notifyFileChange();
+ }
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ }
+ }
+
+ @Override
+ public List getProjectProperties() {
+ List properties = ComponentProcessor.getInstance()
+ .extractProperties(Project.class);
+ try {
+ for (PropertyWrapper property : properties) {
+ property.readFrom(currentProject);
+ }
+ } catch (Exception e) {
+ LOG.error("Could not get project properties", e);
+ return null;
+ }
+ Collections.sort(properties, ComparatorFactory.getInstance()
+ .getPropertyNameComparator());
+ return properties;
+ }
+
+ @Override
+ public void applyPropertiesToProject(List properties) {
+ LOG.debug(String.format("applyPropertiesToProject(%s)", properties));
+ Project oldProject = currentProject.clone();
+ try {
+ for (PropertyWrapper property : properties) {
+ property.writeTo(currentProject);
+ }
+ } catch (Exception e) {
+ LOG.error("Could not apply project properties", e);
+ view
+ .showMessage(
+ "Could not apply changes to the project. Check the log for details.",
+ "Error", IView.ERROR_MESSAGE);
+ } finally {
+ // Notify the listeners.
+ if (!oldProject.equals(currentProject)) {
+ messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED,
+ oldProject, currentProject.clone(), "Edit Project");
+ projectFileManager.notifyFileChange();
+ }
+ drawingManager.fireZoomChanged();
+ }
+ }
+
+ @Override
+ public ComponentType getNewComponentTypeSlot() {
+ return instantiationManager.getComponentTypeSlot();
+ }
+
+ @Override
+ public void setNewComponentTypeSlot(ComponentType componentType,
+ Template template) {
+ LOG.info(String.format("setNewComponentSlot(%s)",
+ componentType == null ? null : componentType.getName()));
+ if (componentType != null && componentType.getInstanceClass() == null) {
+ LOG.info("Cannot set new component type slot for type "
+ + componentType.getName());
+ setNewComponentTypeSlot(null, null);
+ return;
+ }
+ try {
+ instantiationManager.setComponentTypeSlot(componentType, template,
+ currentProject);
+ if (componentType != null) {
+ updateSelection(EMPTY_SELECTION);
+ }
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_CHANGED,
+ // selectedComponents);
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_SIZE_CHANGED,
+ // calculateSelectionDimension());
+ messageDispatcher.dispatchMessage(EventType.SLOT_CHANGED,
+ instantiationManager.getComponentTypeSlot(),
+ instantiationManager.getFirstControlPoint());
+ } catch (Exception e) {
+ LOG.error("Could not set component type slot", e);
+ view
+ .showMessage(
+ "Could not set component type slot. Check log for details.",
+ "Error", IView.ERROR_MESSAGE);
+ }
+ }
+
+ @Override
+ public void saveSelectedComponentAsTemplate(String templateName) {
+ LOG.info(String.format("saveSelectedComponentAsTemplate(%s)",
+ templateName));
+ if (selectedComponents.size() != 1) {
+ throw new RuntimeException(
+ "Can only save a single component as a template at once.");
+ }
+ IDIYComponent> component = selectedComponents.iterator().next();
+ ComponentType type = ComponentProcessor.getInstance()
+ .extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) component
+ .getClass());
+ Map