diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/.classpath b/ diy-layout-creator --username bancika@gmail.com/diylc-core/.classpath
new file mode 100644
index 00000000..aedd5a59
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/.classpath
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/.project b/ diy-layout-creator --username bancika@gmail.com/diylc-core/.project
new file mode 100644
index 00000000..c76f779e
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/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/.settings/org.eclipse.jdt.ui.prefs b/ diy-layout-creator --username bancika@gmail.com/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/.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/DIYLCStarter.launch b/ diy-layout-creator --username bancika@gmail.com/diylc-core/DIYLCStarter.launch
new file mode 100644
index 00000000..1abb28ef
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/DIYLCStarter.launch
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/build.xml b/ diy-layout-creator --username bancika@gmail.com/diylc-core/build.xml
new file mode 100644
index 00000000..ae556a0f
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/build.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Compile with JDK ${java.runtime.version}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/config.properties b/ diy-layout-creator --username bancika@gmail.com/diylc-core/config.properties
new file mode 100644
index 00000000..c6abd215
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/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/lib/bancika-gui-framework.jar b/ diy-layout-creator --username bancika@gmail.com/diylc-core/lib/bancika-gui-framework.jar
new file mode 100644
index 00000000..439eb6b9
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/lib/bancika-gui-framework.jar differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/lib/cloning-1.5.jar b/ diy-layout-creator --username bancika@gmail.com/diylc-core/lib/cloning-1.5.jar
new file mode 100644
index 00000000..781fe27d
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/lib/cloning-1.5.jar differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/lib/iText-2.1.7.jar b/ diy-layout-creator --username bancika@gmail.com/diylc-core/lib/iText-2.1.7.jar
new file mode 100644
index 00000000..3f2c1882
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/lib/iText-2.1.7.jar differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/lib/jettison-1.0.1.jar b/ diy-layout-creator --username bancika@gmail.com/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/lib/jettison-1.0.1.jar differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/lib/junit-4.7.jar b/ diy-layout-creator --username bancika@gmail.com/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/lib/junit-4.7.jar differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/lib/log4j-1.2.15.jar b/ diy-layout-creator --username bancika@gmail.com/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/lib/log4j-1.2.15.jar differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/lib/objenesis-1.1.jar b/ diy-layout-creator --username bancika@gmail.com/diylc-core/lib/objenesis-1.1.jar
new file mode 100644
index 00000000..f178db33
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/lib/objenesis-1.1.jar differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/lib/poi-3.5-FINAL-20090928.jar b/ diy-layout-creator --username bancika@gmail.com/diylc-core/lib/poi-3.5-FINAL-20090928.jar
new file mode 100644
index 00000000..9ff92b02
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/lib/poi-3.5-FINAL-20090928.jar differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/lib/xstream-1.3.1.jar b/ diy-layout-creator --username bancika@gmail.com/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/lib/xstream-1.3.1.jar differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/library/main.jar b/ diy-layout-creator --username bancika@gmail.com/diylc-core/library/main.jar
new file mode 100644
index 00000000..6686875d
Binary files /dev/null and b/ diy-layout-creator --username bancika@gmail.com/diylc-core/library/main.jar differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/DIYLCStarter.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/DIYLCStarter.java
new file mode 100644
index 00000000..850c355f
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/DIYLCStarter.java
@@ -0,0 +1,76 @@
+package org.diylc;
+
+import java.io.FileInputStream;
+import java.net.URL;
+import java.util.Properties;
+
+import javax.swing.UIManager;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.PropertyConfigurator;
+import org.diylc.presenter.Presenter;
+import org.diylc.swing.gui.MainFrame;
+import org.diylc.swing.gui.TemplateDialog;
+
+import com.diyfever.gui.miscutils.ConfigurationManager;
+import com.diyfever.gui.miscutils.PropertyInjector;
+
+/**
+ * Main class that runs DIYLC.
+ *
+ * @author Branislav Stojkovic
+ *
+ * @see Presenter
+ * @see MainFrame
+ */
+public class DIYLCStarter {
+
+ private static final Logger LOG = Logger.getLogger(Presenter.class);
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ URL url = DIYLCStarter.class.getResource("log4j.properties");
+ Properties properties = new Properties();
+ try {
+ properties.load(url.openStream());
+ PropertyConfigurator.configure(properties);
+ } catch (Exception e) {
+ LOG.error("Could not initialize log4j configuration", e);
+ }
+
+ LOG.debug("Java version: " + System.getProperty("java.runtime.version") + " by " + System.getProperty("java.vm.vendor"));
+
+ LOG.info("Starting DIYLC with working directory " + System.getProperty("user.dir"));
+
+ try {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ } catch (Exception e) {
+ LOG.error("Could not set Look&Feel", e);
+ }
+
+ MainFrame mainFrame = new MainFrame();
+ mainFrame.setLocationRelativeTo(null);
+ mainFrame.setVisible(true);
+
+ boolean showTemplates = ConfigurationManager.getInstance().readBoolean(
+ TemplateDialog.SHOW_TEMPLATES_KEY, true);
+ if (showTemplates) {
+ TemplateDialog templateDialog = new TemplateDialog(mainFrame, mainFrame.getPresenter());
+ if (!templateDialog.getFiles().isEmpty()) {
+ templateDialog.setVisible(true);
+ }
+ }
+
+ url = DIYLCStarter.class.getResource("/config.properties");
+ properties = new Properties();
+ try {
+ LOG.info("Injecting default properties.");
+ properties.load(new FileInputStream("config.properties"));
+ PropertyInjector.injectProperties(properties);
+ } catch (Exception e) {
+ LOG.error("Could not read config.properties file", e);
+ }
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/common/BadPositionException.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/common/ComponentType.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/common/ComponentType.java
new file mode 100644
index 00000000..f10717cb
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/common/ComponentType.java
@@ -0,0 +1,88 @@
+package org.diylc.common;
+
+import javax.swing.Icon;
+
+import org.diylc.core.CreationMethod;
+import org.diylc.core.IDIYComponent;
+
+/**
+ * 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 stretchable;
+
+ public ComponentType(String name, String description, CreationMethod creationMethod,
+ String category, String namePrefix, String author, Icon icon,
+ Class extends IDIYComponent>> instanceClass, double zOrder, boolean stretchable) {
+ 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.stretchable = stretchable;
+ }
+
+ 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 isStretchable() {
+ return stretchable;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/common/Display.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/common/DrawOption.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/common/EventType.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/common/GridType.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/common/IComponentFiler.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/common/IPlugIn.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/common/IPlugIn.java
new file mode 100644
index 00000000..3764fc44
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/common/IPlugIn.java
@@ -0,0 +1,19 @@
+package org.diylc.common;
+
+import com.diyfever.gui.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/src/org/diylc/common/IPlugInPort.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/common/IPlugInPort.java
new file mode 100644
index 00000000..a87d9d74
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/common/IPlugInPort.java
@@ -0,0 +1,375 @@
+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.core.IDIYComponent;
+import org.diylc.core.Project;
+import org.diylc.core.Theme;
+import org.diylc.swing.plugins.edit.ComponentTransferable;
+
+import com.diyfever.gui.simplemq.IMessageListener;
+import com.diyfever.gui.simplemq.MessageDispatcher;
+import com.diyfever.gui.update.VersionNumber;
+
+/**
+ * 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 {
+
+ 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";
+
+ /**
+ * 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);
+
+ /**
+ * Notifies the presenter that mouse is clicked.
+ *
+ * Note: point coordinates are display based, i.e. scaled for zoom factor.
+ *
+ * @param point
+ * @param ctrlDown
+ * @param shiftDown
+ * @param altDown
+ * @param clickCount
+ */
+ void mouseClicked(Point point, 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);
+
+ /**
+ * 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);
+
+ /**
+ * Selects all components in the project.
+ */
+ void selectAll();
+
+ /**
+ * Notification that drag has been started from the specified point.
+ *
+ * Note: point coordinates are scaled for zoom factor.
+ *
+ * @param point
+ * @param ctrlDown
+ * @param shiftDown
+ * @param altDown
+ */
+ void dragStarted(Point point, boolean ctrlDown, boolean shiftDown, boolean altDown);
+
+ /**
+ * 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);
+
+ /**
+ * Notification that drag has been ended in the specified point.
+ *
+ * Note: point coordinates are scaled for zoom factor.
+ *
+ * @param point
+ */
+ void dragEnded(Point point);
+
+ /**
+ * 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);
+
+ /**
+ * Sets the new component slot. Specified component type will be used to
+ * instantiate new component.
+ */
+ void setNewComponentTypeSlot(ComponentType componentType);
+
+ /**
+ * 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();
+
+ /**
+ * 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/src/org/diylc/common/ITask.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/common/LabelPosition.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/common/ObjectCache.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/common/Orientation.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/common/OrientationHV.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/common/PropertyWrapper.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/common/PropertyWrapper.java
new file mode 100644
index 00000000..d07af5f4
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/common/PropertyWrapper.java
@@ -0,0 +1,156 @@
+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 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/src/org/diylc/common/ResistorColorCode.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/components/AbstractComponent.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/components/AbstractComponent.java
new file mode 100644
index 00000000..251dd1cd
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/components/AbstractComponent.java
@@ -0,0 +1,73 @@
+package org.diylc.components;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Shape;
+
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.annotations.EditableProperty;
+
+/**
+ * Abstract implementation of {@link IDIYComponent} that contains component name
+ * and toString.
+ *
+ * @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);
+
+ @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;
+ }
+
+ /**
+ * 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;
+ }
+ }
+ return true;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/components/AbstractCurvedComponent.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/components/AbstractLeadedComponent.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/components/AbstractLeadedComponent.java
new file mode 100644
index 00000000..c58a5cfc
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/components/AbstractLeadedComponent.java
@@ -0,0 +1,336 @@
+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.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;
+
+import com.diyfever.gui.miscutils.ConfigurationManager;
+
+/**
+ * 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("#236B8E");
+ 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 Display display = Display.VALUE;
+
+ 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(points[0].x - width / 2, 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 : bodyColor);
+ 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);
+ // Transform graphics to draw the body in the right place and at the
+ // right angle.
+ AffineTransform oldTransform = g2d.getTransform();
+ g2d.translate((points[0].x + points[1].x - shapeRect.width) / 2, (points[0].y
+ + points[1].y - shapeRect.height) / 2);
+ g2d.rotate(theta, shapeRect.width / 2, shapeRect.height / 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);
+ // Draw label.
+ g2d.setFont(LABEL_FONT);
+ 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().toString();
+ // 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, shapeRect.width / 2, shapeRect.height / 2);
+ }
+ Rectangle2D textRect = fontMetrics.getStringBounds(label, g2d);
+ g2d.drawString(label, (int) (shapeRect.width - textRect.getWidth()) / 2,
+ calculateLabelYCoordinate(shapeRect, textRect, fontMetrics));
+
+ // Go back to the original transformation to draw leads.
+ if (!outlineMode) {
+ g2d.setTransform(oldTransform);
+ int leadThickness = getClosestOdd(getLeadThickness());
+ double leadLength = (distance - shapeRect.width) / 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));
+ }
+ }
+ }
+ }
+
+ private void drawLead(Graphics2D g2d, ComponentState componentState) {
+ g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(getLeadThickness()));
+ Color leadColor = shouldShadeLeads() ? getLeadColor(componentState).darker()
+ : getLeadColor(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 = getLeadColor(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;
+ }
+
+ /**
+ * @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();
+
+ /**
+ * @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 getLeadColor(ComponentState componentState) {
+ return componentState == ComponentState.SELECTED
+ || componentState == ComponentState.DRAGGING ? SELECTION_COLOR : LEAD_COLOR;
+ }
+
+ @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;
+ }
+
+ @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;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/components/AbstractSchematicLeadedSymbol.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/components/AbstractSchematicLeadedSymbol.java
new file mode 100644
index 00000000..0e5c0591
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/components/AbstractSchematicLeadedSymbol.java
@@ -0,0 +1,72 @@
+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.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 getLeadColor(ComponentState componentState) {
+ return componentState == ComponentState.SELECTED
+ || componentState == ComponentState.DRAGGING ? SELECTION_COLOR : LEAD_COLOR;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/components/AbstractTransparentComponent.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/core/AllowAllValidator.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/core/ComponentState.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/core/CreationMethod.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/core/IDIYComponent.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/IDIYComponent.java
new file mode 100644
index 00000000..ecf23912
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/IDIYComponent.java
@@ -0,0 +1,122 @@
+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);
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/IDrawingObserver.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/core/IPropertyValidator.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/core/IView.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/IView.java
new file mode 100644
index 00000000..fe11e4b5
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/IView.java
@@ -0,0 +1,38 @@
+package org.diylc.core;
+
+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);
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/Project.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/Project.java
new file mode 100644
index 00000000..a4c3bd9f
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/Project.java
@@ -0,0 +1,225 @@
+package org.diylc.core;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.diylc.core.annotations.EditableProperty;
+import org.diylc.core.annotations.PositiveMeasureValidator;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+
+import com.diyfever.gui.update.VersionNumber;
+
+/**
+ * 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.equals(other.components))
+ 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(1d, SizeUnit.mm)) < 0) {
+ throw new ValidationException("must be greater than 1mm");
+ }
+ if (size.compareTo(new Size(1d, SizeUnit.in)) > 0) {
+ throw new ValidationException("must be less than 1in");
+ }
+ }
+ }
+
+ // @Override
+ // public Project clone() throws CloneNotSupportedException {
+ // List clonedComponents = new
+ // ArrayList();
+ // for (IDIYComponent component : components) {
+ // clonedComponents.add(component.clone());
+ // }
+ // Project project = new Project(title, author, description,
+ // width.clone(), height.clone(), clonedComponents);
+ // return project;
+ // }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/Theme.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/core/ValidationException.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/core/VisibilityPolicy.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/core/annotations/ComponentDescriptor.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/annotations/ComponentDescriptor.java
new file mode 100644
index 00000000..c9bbaa21
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/annotations/ComponentDescriptor.java
@@ -0,0 +1,62 @@
+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 when false, moving one control point will cause all the others to
+ * move together with it.
+ */
+ boolean stretchable() default true;
+
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/annotations/EditableProperty.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/core/annotations/PositiveMeasureValidator.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/annotations/PositiveMeasureValidator.java
new file mode 100644
index 00000000..5d38a258
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/annotations/PositiveMeasureValidator.java
@@ -0,0 +1,20 @@
+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 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/src/org/diylc/core/measures/AbstractMeasure.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/measures/AbstractMeasure.java
new file mode 100644
index 00000000..0245fba9
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/measures/AbstractMeasure.java
@@ -0,0 +1,81 @@
+package org.diylc.core.measures;
+
+import java.io.Serializable;
+import java.text.DecimalFormat;
+import java.text.Format;
+
+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/src/org/diylc/core/measures/Capacitance.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/measures/Capacitance.java
new file mode 100644
index 00000000..4e2e79c3
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/core/measures/CapacitanceUnit.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/core/measures/Inductance.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/measures/Inductance.java
new file mode 100644
index 00000000..af598a2a
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/core/measures/InductanceUnit.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/measures/InductanceUnit.java
new file mode 100644
index 00000000..e00facd8
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/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, "\u03bcH"), 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/src/org/diylc/core/measures/Resistance.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/measures/Resistance.java
new file mode 100644
index 00000000..63236e70
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/measures/Resistance.java
@@ -0,0 +1,78 @@
+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;
+ 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() - 1).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/src/org/diylc/core/measures/ResistanceUnit.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/measures/ResistanceUnit.java
new file mode 100644
index 00000000..9d7a917f
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/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, "\u03a9"), 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/src/org/diylc/core/measures/Size.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/core/measures/Size.java
new file mode 100644
index 00000000..e7543161
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/core/measures/SizeUnit.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/core/measures/Unit.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/images/IconLoader.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/IconLoader.java
new file mode 100644
index 00000000..baef6933
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/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");
+
+ 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/src/org/diylc/images/about.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/about.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/add.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/add.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/arrow_down_blue.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/arrow_down_blue.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/arrow_left_blue.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/arrow_left_blue.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/arrow_right_blue.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/arrow_right_blue.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/arrow_up_blue.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/arrow_up_blue.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/at.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/at.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/back.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/back.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/blackboard.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/blackboard.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/bom.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/bom.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/bug.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/bug.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/check.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/check.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/chest.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/chest.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/component.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/component.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/copy.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/copy.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/csv.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/csv.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/cut.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/cut.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/delete.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/delete.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/disk_blue.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/disk_blue.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/disks.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/disks.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/document.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/document.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/document_edit.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/document_edit.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/document_info.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/document_info.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/document_plain_yellow.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/document_plain_yellow.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/donate.gif b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/donate.gif differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/donate.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/donate.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/edit.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/edit.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/edit_component.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/edit_component.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/error.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/error.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/excel.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/excel.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/exit.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/exit.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/export.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/export.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/faq.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/faq.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/folder_music.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/folder_music.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/folder_out.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/folder_out.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/forbidden.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/forbidden.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/form_blue.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/form_blue.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/form_blue_add.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/form_blue_add.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/front.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/front.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/garbage.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/garbage.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/gear.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/gear.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/gears.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/gears.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/graph_edge.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/graph_edge.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/graph_edge_curved.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/graph_edge_curved.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/graph_edge_directed.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/graph_edge_directed.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/group.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/group.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/html.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/html.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/icon_large.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/icon_large.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/icon_medium.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/icon_medium.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/icon_small.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/icon_small.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/id_card.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/id_card.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/id_card_add.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/id_card_add.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/image.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/image.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/lightbulb_off.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/lightbulb_off.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/lightbulb_on.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/lightbulb_on.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/manual.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/manual.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/media_play_green.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/media_play_green.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/media_stop_red.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/media_stop_red.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/move_small.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/move_small.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/navigate_check.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/navigate_check.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/navigate_plus.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/navigate_plus.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/notebook_add.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/notebook_add.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/paste.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/paste.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/pdf.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/pdf.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/pens.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/pens.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/photo_scenery.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/photo_scenery.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/plugin.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/plugin.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/print.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/print.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/redo.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/redo.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/refresh.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/refresh.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/save_as.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/save_as.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/selection.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/selection.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/size.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/size.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/sort.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/sort.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/trace_mask.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/trace_mask.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/undo.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/undo.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/ungroup.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/ungroup.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/upload.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/upload.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/view.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/view.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/warning.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/warning.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/window_colors.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/window_colors.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/wrench.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/wrench.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/zoom_in.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/zoom_in.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/zoom_out.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/zoom_out.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/images/zoom_small.png b/ diy-layout-creator --username bancika@gmail.com/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/src/org/diylc/images/zoom_small.png differ
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/log4j.properties b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/presenter/CalcUtils.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/presenter/ComparatorFactory.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/presenter/ComponentProcessor.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/presenter/ComponentProcessor.java
new file mode 100644
index 00000000..3eb9bee2
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/presenter/ComponentProcessor.java
@@ -0,0 +1,225 @@
+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.ComponentDescriptor;
+import org.diylc.core.annotations.EditableProperty;
+
+import com.rits.cloning.Cloner;
+
+/**
+ * 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;
+
+ private Cloner cloner;
+
+ public static ComponentProcessor getInstance() {
+ if (instance == null) {
+ instance = new ComponentProcessor();
+ }
+ return instance;
+ }
+
+ private ComponentProcessor() {
+ super();
+ this.cloner = new Cloner();
+ 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 stretchable;
+ 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();
+ stretchable = annotation.stretchable();
+ } else {
+ name = clazz.getSimpleName();
+ description = "";
+ creationMethod = CreationMethod.SINGLE_CLICK;
+ category = "Uncategorized";
+ namePrefix = "Unknown";
+ author = "Unknown";
+ zOrder = IDIYComponent.COMPONENT;
+ stretchable = 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);
+ componentInstance.drawIcon(g2d, Presenter.ICON_SIZE, Presenter.ICON_SIZE);
+ icon = new ImageIcon(image);
+ } catch (InstantiationException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ ComponentType componentType = new ComponentType(name, description, creationMethod,
+ category, namePrefix, author, icon, clazz, zOrder, stretchable);
+ 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 cloner.deepClone(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 cloner.deepClone(properties);
+ }
+
+ /**
+ * 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)
+ || !newProperty.getValue().equals(oldProperty.getValue())) {
+ // Values don't match, so the property is not unique valued.
+ 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/src/org/diylc/presenter/ComponentZOrderFiler.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/presenter/ComponentZOrderFiler.java
new file mode 100644
index 00000000..5410f1df
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/presenter/ComponentZOrderFiler.java
@@ -0,0 +1,23 @@
+package org.diylc.presenter;
+
+import org.diylc.common.ComponentType;
+import org.diylc.common.IComponentFiler;
+import org.diylc.core.IDIYComponent;
+
+public class ComponentZOrderFiler implements IComponentFiler {
+
+ private int zOrder;
+
+ public ComponentZOrderFiler(int zOrder) {
+ super();
+ this.zOrder = zOrder;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean testComponent(IDIYComponent> component) {
+ ComponentType componentType = ComponentProcessor.getInstance().extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) component.getClass());
+ return Math.round(componentType.getZOrder()) == zOrder;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/presenter/DrawingManager.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/presenter/DrawingManager.java
new file mode 100644
index 00000000..778208de
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/presenter/DrawingManager.java
@@ -0,0 +1,396 @@
+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.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;
+
+import com.diyfever.gui.miscutils.ConfigurationManager;
+import com.diyfever.gui.simplemq.MessageDispatcher;
+
+/**
+ * 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,
+ IDIYComponent> 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);
+ } 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;
+ 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);
+ try {
+ componentSlot.draw(g2dWrapper, ComponentState.NORMAL, drawOptions
+ .contains(DrawOption.OUTLINE_MODE), project, g2dWrapper);
+ } catch (Exception e) {
+ LOG.error("Error drawing " + componentSlot.getName(), e);
+ failedComponents.add(componentSlot);
+ }
+ 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();
+ }
+
+ 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(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/src/org/diylc/presenter/G2DWrapper.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/presenter/G2DWrapper.java
new file mode 100644
index 00000000..10e638f0
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/presenter/G2DWrapper.java
@@ -0,0 +1,673 @@
+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.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 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();
+ 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);
+ appendShape(new Rectangle2D.Double(rect.getX() + x, rect.getY() + y, rect.getWidth(),
+ rect.getHeight()));
+ }
+ }
+
+ @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 = Tx;
+ }
+
+ @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/src/org/diylc/presenter/InstantiationManager.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/presenter/InstantiationManager.java
new file mode 100644
index 00000000..e2176333
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/presenter/InstantiationManager.java
@@ -0,0 +1,219 @@
+package org.diylc.presenter;
+
+import java.awt.Point;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.diylc.common.ComponentType;
+import org.diylc.common.IPlugInPort;
+import org.diylc.common.PropertyWrapper;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.Project;
+import org.diylc.core.measures.Size;
+
+import com.diyfever.gui.miscutils.ConfigurationManager;
+import com.rits.cloning.Cloner;
+
+/**
+ * Manages component instantiation.
+ *
+ * @author Branislav Stojkovic
+ */
+public class InstantiationManager {
+
+ private static final Logger LOG = Logger.getLogger(InstantiationManager.class);
+
+ private static final int MAX_RECENT_COMPONENTS = 16;
+
+ private Cloner cloner;
+
+ private ComponentType componentTypeSlot;
+ private IDIYComponent> componentSlot;
+ private Point firstControlPoint;
+ private Point potentialControlPoint;
+
+ public InstantiationManager() {
+ cloner = new Cloner();
+ }
+
+ public ComponentType getComponentTypeSlot() {
+ return componentTypeSlot;
+ }
+
+ public IDIYComponent> getComponentSlot() {
+ return componentSlot;
+ }
+
+ public Point getFirstControlPoint() {
+ return firstControlPoint;
+ }
+
+ public Point getPotentialControlPoint() {
+ return potentialControlPoint;
+ }
+
+ public void setComponentTypeSlot(ComponentType componentTypeSlot, Project currentProject)
+ throws Exception {
+ this.componentTypeSlot = componentTypeSlot;
+ 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, 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, firstControlPoint, currentProject);
+
+ // Set the other control point to the same location, we'll
+ // move it later when mouse moves.
+ componentSlot.setControlPoint(firstControlPoint, 0);
+ componentSlot.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.setControlPoint(scaledPoint, 1);
+ }
+ return changeMade;
+ }
+
+ public boolean updateSingleClick(Point scaledPoint, boolean snapToGrid, Size gridSpacing) {
+ if (potentialControlPoint == null) {
+ potentialControlPoint = 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 (int i = 0; i < componentSlot.getControlPointCount(); i++) {
+ p.setLocation(componentSlot.getControlPoint(i));
+ p.translate(dx, dy);
+ componentSlot.setControlPoint(p, i);
+ }
+ }
+ return true;
+ }
+
+ @SuppressWarnings("unchecked")
+ public IDIYComponent> instantiateComponent(ComponentType componentType, 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);
+ }
+ }
+
+ fillWithDefaultProperties(component);
+
+ // 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);
+ }
+
+ return component;
+ }
+
+ public String createUniqueName(ComponentType componentType, Project currentProject) {
+ int i = 0;
+ boolean exists = true;
+ while (exists) {
+ i++;
+ String name = componentType.getNamePrefix() + i;
+ exists = false;
+ for (IDIYComponent> c : currentProject.getComponents()) {
+ if (c.getName().equals(name)) {
+ exists = true;
+ break;
+ }
+ }
+ }
+ 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
+ * @throws IllegalArgumentException
+ * @throws IllegalAccessException
+ * @throws InvocationTargetException
+ * @throws NoSuchMethodException
+ * @throws SecurityException
+ */
+ public void fillWithDefaultProperties(Object object) throws IllegalArgumentException,
+ IllegalAccessException, InvocationTargetException, SecurityException,
+ NoSuchMethodException {
+ // Extract properties.
+ List properties = ComponentProcessor.getInstance().extractProperties(
+ object.getClass());
+ // Override with default values if available.
+ for (PropertyWrapper property : properties) {
+ Object defaultValue = ConfigurationManager.getInstance().readObject(
+ Presenter.DEFAULTS_KEY_PREFIX + object.getClass().getName() + ":"
+ + property.getName(), null);
+ if (defaultValue != null) {
+ property.setValue(cloner.deepClone(defaultValue));
+ property.writeTo(object);
+ }
+ }
+ }
+
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/presenter/PointConverter.java b/ diy-layout-creator --username bancika@gmail.com/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/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/src/org/diylc/presenter/Presenter.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/presenter/Presenter.java
new file mode 100644
index 00000000..df3ecbd1
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/presenter/Presenter.java
@@ -0,0 +1,1391 @@
+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.geom.Area;
+import java.awt.geom.Point2D;
+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.common.ComponentType;
+import org.diylc.common.DrawOption;
+import org.diylc.common.EventType;
+import org.diylc.common.IComponentFiler;
+import org.diylc.common.IPlugIn;
+import org.diylc.common.IPlugInPort;
+import org.diylc.common.PropertyWrapper;
+import org.diylc.components.connectivity.SolderPad;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IView;
+import org.diylc.core.Project;
+import org.diylc.core.Theme;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.swing.plugins.edit.ComponentTransferable;
+import org.diylc.utils.Constants;
+
+import com.diyfever.gui.miscutils.ConfigurationManager;
+import com.diyfever.gui.miscutils.JarScanner;
+import com.diyfever.gui.miscutils.Utils;
+import com.diyfever.gui.simplemq.MessageDispatcher;
+import com.diyfever.gui.update.VersionNumber;
+import com.rits.cloning.Cloner;
+
+/**
+ * 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 final VersionNumber CURRENT_VERSION = new VersionNumber(3, 0, 9);
+ 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;
+
+ 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);
+ 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>();
+ List> componentTypeClasses = JarScanner.getInstance().scanFolder("library/",
+ IDIYComponent.class);
+ for (Class> clazz : componentTypeClasses) {
+ if (!Modifier.isAbstract(clazz.getModifiers())) {
+ 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.
+ IDIYComponent> 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
+ */
+ private List> findComponentsAt(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 void mouseClicked(Point point, boolean ctrlDown, boolean shiftDown, boolean altDown,
+ int clickCount) {
+ LOG.debug(String
+ .format("mouseClicked(%s, %s, %s, %s)", point, ctrlDown, shiftDown, altDown));
+ Point scaledPoint = scalePoint(point);
+ if (clickCount >= 2) {
+ editSelection();
+ } else {
+ if (instantiationManager.getComponentTypeSlot() != null) {
+ // Keep the reference to component type for later.
+ ComponentType componentTypeSlot = instantiationManager.getComponentTypeSlot();
+ Project oldProject = cloner.deepClone(currentProject);
+ switch (componentTypeSlot.getCreationMethod()) {
+ case SINGLE_CLICK:
+ try {
+ if (ConfigurationManager.getInstance().readBoolean(
+ IPlugInPort.SNAP_TO_GRID_KEY, true)) {
+ CalcUtils.snapPointToGrid(scaledPoint, currentProject.getGridSpacing());
+ }
+ IDIYComponent> component = instantiationManager.instantiateComponent(
+ componentTypeSlot, scaledPoint, currentProject);
+ addComponent(component, componentTypeSlot, true);
+ // Select the new component
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_CHANGED,
+ // selectedComponents);
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_SIZE_CHANGED,
+ // calculateSelectionDimension());
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ List> newSelection = new ArrayList>();
+ newSelection.add(component);
+ updateSelection(newSelection);
+ } catch (Exception e) {
+ LOG.error("Error instatiating component of type: "
+ + componentTypeSlot.getInstanceClass().getName(), e);
+ }
+
+ if (ConfigurationManager.getInstance().readBoolean(IPlugInPort.AUTO_EDIT_KEY,
+ false)) {
+ editSelection();
+ }
+ if (ConfigurationManager.getInstance().readBoolean(
+ IPlugInPort.CONTINUOUS_CREATION_KEY, false)) {
+ setNewComponentTypeSlot(componentTypeSlot);
+ } else {
+ setNewComponentTypeSlot(null);
+ }
+ break;
+ case POINT_BY_POINT:
+ // First click is just to set the controlPointSlot and
+ // componentSlot.
+ if (ConfigurationManager.getInstance().readBoolean(
+ IPlugInPort.SNAP_TO_GRID_KEY, true)) {
+ 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.
+ IDIYComponent> componentSlot = instantiationManager.getComponentSlot();
+ componentSlot.setControlPoint(scaledPoint, 1);
+ addComponent(componentSlot, componentTypeSlot, true);
+ // Select the new component if it's not locked.
+ List> newSelection = new ArrayList>();
+ if (!isComponentLocked(componentSlot)) {
+ newSelection.add(componentSlot);
+ }
+ updateSelection(newSelection);
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+
+ if (ConfigurationManager.getInstance().readBoolean(
+ IPlugInPort.AUTO_EDIT_KEY, false)) {
+ editSelection();
+ }
+ if (ConfigurationManager.getInstance().readBoolean(
+ IPlugInPort.CONTINUOUS_CREATION_KEY, false)) {
+ setNewComponentTypeSlot(componentTypeSlot);
+ } else {
+ setNewComponentTypeSlot(null);
+ }
+ }
+ break;
+ default:
+ LOG.error("Unknown creation method: " + componentTypeSlot.getCreationMethod());
+ }
+ // Notify the listeners.
+ if (!oldProject.equals(currentProject)) {
+ messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED, oldProject,
+ cloner.deepClone(currentProject), "Add " + componentTypeSlot.getName());
+ projectFileManager.notifyFileChange();
+ }
+ } else {
+ List> newSelection = new ArrayList>(
+ selectedComponents);
+ List> components = findComponentsAt(scaledPoint);
+ // If there's nothing under mouse cursor deselect all.
+ if (components.isEmpty()) {
+ if (!ctrlDown) {
+ newSelection.clear();
+ }
+ } else {
+ IDIYComponent> component = components.get(components.size() - 1);
+ // If ctrl is pressed just toggle the component under mouse
+ // cursor.
+ if (ctrlDown) {
+ if (newSelection.contains(component)) {
+ newSelection.removeAll(findAllGroupedComponents(component));
+ } else {
+ newSelection.addAll(findAllGroupedComponents(component));
+ }
+ } else {
+ // Otherwise just select that one component.
+ newSelection.clear();
+ newSelection.addAll(findAllGroupedComponents(component));
+ }
+ }
+ updateSelection(newSelection);
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_CHANGED,
+ // selectedComponents);
+ // messageDispatcher.dispatchMessage(EventType.SELECTION_SIZE_CHANGED,
+ // calculateSelectionDimension());
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ }
+ }
+ }
+
+ @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) {
+ Map, Set> components = new HashMap, Set>();
+ Point scaledPoint = scalePoint(point);
+ if (instantiationManager.getComponentTypeSlot() != null) {
+ if (ConfigurationManager.getInstance().readBoolean(IPlugInPort.SNAP_TO_GRID_KEY, true)) {
+ CalcUtils.snapPointToGrid(scaledPoint, currentProject.getGridSpacing());
+ }
+ boolean refresh = false;
+ switch (instantiationManager.getComponentTypeSlot().getCreationMethod()) {
+ case POINT_BY_POINT:
+ refresh = instantiationManager.updatePointByPoint(scaledPoint);
+ break;
+ case SINGLE_CLICK:
+ refresh = instantiationManager.updateSingleClick(scaledPoint, ConfigurationManager
+ .getInstance().readBoolean(IPlugInPort.SNAP_TO_GRID_KEY, true),
+ 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 (scaledPoint.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, scaledPoint);
+
+ 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() {
+ LOG.info("selectAll()");
+ updateSelection(new ArrayList>(currentProject.getComponents()));
+ // 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, boolean ctrlDown, boolean shiftDown, boolean altDown) {
+ LOG.debug(String.format("dragStarted(%s)", point));
+ if (instantiationManager.getComponentTypeSlot() != null) {
+ LOG.debug("Cannot start drag because a new component is being created.");
+ mouseClicked(point, ctrlDown, shiftDown, altDown, 1);
+ return;
+ }
+ dragInProgress = true;
+ preDragProject = cloner.deepClone(currentProject);
+ Point scaledPoint = scalePoint(point);
+ previousDragPoint = scaledPoint;
+ List> components = findComponentsAt(scaledPoint);
+ if (!controlPointMap.isEmpty()) {
+ // If we're dragging control points reset selection.
+ updateSelection(new ArrayList>(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(components.size() - 1);
+ // 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);
+ }
+ controlPointMap.put(c, pointIndices);
+ }
+ }
+ // 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);
+ }
+ }
+ }
+
+ /**
+ * 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.");
+ }
+ }
+
+ @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);
+ // 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();
+ boolean isFirst = true;
+ 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)) {
+ if (isFirst) {
+ isFirst = false;
+ Point testPoint = new Point(controlPoints[index]);
+ testPoint.translate(dx, dy);
+ if (ConfigurationManager.getInstance().readBoolean(
+ IPlugInPort.SNAP_TO_GRID_KEY, true)) {
+ CalcUtils.snapPointToGrid(testPoint, currentProject
+ .getGridSpacing());
+ }
+ actualDx = testPoint.x - component.getControlPoint(index).x;
+ actualDy = testPoint.y - component.getControlPoint(index).y;
+ if (actualDx == 0 && actualDy == 0) {
+ // Nothing to move.
+ return true;
+ }
+ }
+ 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 true;
+ }
+ }
+ // 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 true;
+ }
+ }
+ }
+ }
+
+ // Update all points.
+ 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);
+ }
+ }
+ previousDragPoint.translate(actualDx, actualDy);
+ } 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;
+ }
+
+ @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, cloner
+ .deepClone(currentProject), "Drag");
+ projectFileManager.notifyFileChange();
+ }
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ dragInProgress = false;
+ }
+
+ @Override
+ public void pasteComponents(List> components) {
+ LOG.info(String.format("addComponents(%s)", components));
+ Project oldProject = cloner.deepClone(currentProject);
+ for (IDIYComponent> component : components) {
+ for (int i = 0; i < component.getControlPointCount(); i++) {
+ Point point = new Point(component.getControlPoint(i));
+ point.translate((int) currentProject.getGridSpacing().convertToPixels(),
+ (int) currentProject.getGridSpacing().convertToPixels());
+ component.setControlPoint(point, i);
+ }
+ component.setName(instantiationManager.createUniqueName(ComponentProcessor
+ .getInstance().extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) component.getClass()),
+ currentProject));
+ addComponent(component, ComponentProcessor.getInstance().extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) component.getClass()), false);
+ }
+ messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED, oldProject, cloner
+ .deepClone(currentProject), "Add");
+ projectFileManager.notifyFileChange();
+ updateSelection(new ArrayList>(components));
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ }
+
+ @Override
+ public void deleteSelectedComponents() {
+ LOG.info("deleteSelectedComponents()");
+ if (selectedComponents.isEmpty()) {
+ LOG.debug("Nothing to delete");
+ return;
+ }
+ Project oldProject = cloner.deepClone(currentProject);
+ // 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, cloner
+ .deepClone(currentProject), "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 = cloner.deepClone(currentProject);
+ // 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, cloner
+ .deepClone(currentProject), "Group");
+ projectFileManager.notifyFileChange();
+ }
+ }
+
+ @Override
+ public void ungroupSelectedComponents() {
+ LOG.info("ungroupSelectedComponents()");
+ Project oldProject = cloner.deepClone(currentProject);
+ ungroupComponents(selectedComponents);
+ // Notify the listeners.
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ if (!oldProject.equals(currentProject)) {
+ messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED, oldProject, cloner
+ .deepClone(currentProject), "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 = cloner.deepClone(currentProject);
+ 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 if (index > 0) {
+ IDIYComponent> componentBefore = currentProject.getComponents().get(index - 1);
+ ComponentType componentBeforeType = ComponentProcessor.getInstance()
+ .extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) componentBefore.getClass());
+ Collections.swap(currentProject.getComponents(), index, index - 1);
+ }
+ }
+ if (!oldProject.equals(currentProject)) {
+ messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED, oldProject, cloner
+ .deepClone(currentProject), "Send to Back");
+ projectFileManager.notifyFileChange();
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ }
+ }
+
+ @Override
+ public void bringSelectionToFront() {
+ LOG.info("bringSelectionToFront()");
+ Project oldProject = cloner.deepClone(currentProject);
+ 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 if (index < currentProject.getComponents().size() - 1) {
+ IDIYComponent> componentAfter = currentProject.getComponents().get(index + 1);
+ ComponentType componentAfterType = ComponentProcessor.getInstance()
+ .extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) componentAfter.getClass());
+ Collections.swap(currentProject.getComponents(), index, index + 1);
+ }
+ }
+ if (!oldProject.equals(currentProject)) {
+ messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED, oldProject, cloner
+ .deepClone(currentProject), "Bring to Front");
+ projectFileManager.notifyFileChange();
+ messageDispatcher.dispatchMessage(EventType.REPAINT);
+ }
+ }
+
+ @Override
+ public void 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) {
+ if (getSelectedComponents().isEmpty()) {
+ return;
+ }
+ Project oldProject = cloner.deepClone(currentProject);
+ List> components = new ArrayList>(getSelectedComponents());
+ // Sort components by their location.
+ Collections.sort(components, new Comparator>() {
+
+ @Override
+ public int compare(IDIYComponent> o1, IDIYComponent> o2) {
+ Area area1 = drawingManager.getComponentArea(o1);
+ Area area2 = drawingManager.getComponentArea(o2);
+ if (area1 != null && area2 != null) {
+ Point point1 = area1.getBounds().getLocation();
+ Point point2 = area2.getBounds().getLocation();
+ if (xAxisFirst) {
+ if (point1.y < point2.y) {
+ return -1;
+ } else if (point1.y > point2.y) {
+ return 1;
+ } else {
+ if (point1.x < point1.x) {
+ return -1;
+ } else if (point1.x > point1.x) {
+ return 1;
+ }
+ }
+ } else {
+ if (point1.x < point2.x) {
+ return -1;
+ } else if (point1.x > point2.x) {
+ return 1;
+ } else {
+ if (point1.y < point1.y) {
+ return -1;
+ } else if (point1.y > point1.y) {
+ 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, cloner
+ .deepClone(currentProject), "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());
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Adds a component to the project taking z-order into account.
+ *
+ * @param component
+ * @param componentType
+ */
+ private void addComponent(IDIYComponent> component, ComponentType componentType,
+ boolean canCreatePads) {
+ int index = 0;
+ while (index < currentProject.getComponents().size()
+ && componentType.getZOrder() >= ComponentProcessor.getInstance()
+ .extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) currentProject.getComponents()
+ .get(index).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,
+ component.getControlPoint(i), currentProject);
+ pad.setControlPoint(component.getControlPoint(i), 0);
+ addComponent(pad, padType, 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 = cloner.deepClone(currentProject);
+ 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, cloner
+ .deepClone(currentProject), "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 = cloner.deepClone(currentProject);
+ 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, cloner
+ .deepClone(currentProject), "Edit Project");
+ projectFileManager.notifyFileChange();
+ }
+ drawingManager.fireZoomChanged();
+ }
+ }
+
+ @Override
+ public void setNewComponentTypeSlot(ComponentType componentType) {
+ LOG.info(String.format("setNewComponentSlot(%s)", componentType == null ? null
+ : componentType.getName()));
+ try {
+ instantiationManager.setComponentTypeSlot(componentType, 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);
+ }
+ }
+
+ private Set> getLockedComponents() {
+ lockedComponents.clear();
+ for (IDIYComponent> component : currentProject.getComponents()) {
+ if (isComponentLocked(component)) {
+ lockedComponents.add(component);
+ }
+ }
+ return lockedComponents;
+ }
+
+ private boolean isComponentLocked(IDIYComponent> component) {
+ ComponentType componentType = ComponentProcessor.getInstance().extractComponentTypeFrom(
+ (Class extends IDIYComponent>>) component.getClass());
+ return currentProject.getLockedLayers().contains(
+ (int) Math.round(componentType.getZOrder()));
+ }
+
+ /**
+ * Scales point from display base to actual base.
+ *
+ * @param point
+ * @return
+ */
+ private Point scalePoint(Point point) {
+ return point == null ? null : new Point((int) (point.x / drawingManager.getZoomLevel()),
+ (int) (point.y / drawingManager.getZoomLevel()));
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/presenter/ProjectFileManager.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/presenter/ProjectFileManager.java
new file mode 100644
index 00000000..bf55eb83
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/presenter/ProjectFileManager.java
@@ -0,0 +1,456 @@
+package org.diylc.presenter;
+
+import java.awt.Color;
+import java.awt.Point;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.log4j.Logger;
+import org.diylc.common.EventType;
+import org.diylc.common.Orientation;
+import org.diylc.components.boards.AbstractBoard;
+import org.diylc.components.boards.BlankBoard;
+import org.diylc.components.boards.PerfBoard;
+import org.diylc.components.boards.VeroBoard;
+import org.diylc.components.connectivity.CopperTrace;
+import org.diylc.components.connectivity.HookupWire;
+import org.diylc.components.connectivity.Jumper;
+import org.diylc.components.connectivity.SolderPad;
+import org.diylc.components.misc.Label;
+import org.diylc.components.passive.RadialElectrolytic;
+import org.diylc.components.passive.RadialFilmCapacitor;
+import org.diylc.components.passive.Resistor;
+import org.diylc.components.semiconductors.DIL_IC;
+import org.diylc.components.semiconductors.DiodePlastic;
+import org.diylc.components.semiconductors.LED;
+import org.diylc.components.semiconductors.TransistorTO92;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.Project;
+import org.diylc.core.measures.Capacitance;
+import org.diylc.core.measures.Resistance;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.utils.Constants;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import com.diyfever.gui.simplemq.MessageDispatcher;
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.DomDriver;
+
+public class ProjectFileManager {
+
+ private static final Logger LOG = Logger.getLogger(ProjectFileManager.class);
+
+ // private static final int V1_PIXELS_PER_INCH = 200;
+ private static final Size V1_GRID_SPACING = new Size(0.1d, SizeUnit.in);
+ private static final Map V1_COLOR_MAP = new HashMap();
+ static {
+ V1_COLOR_MAP.put("red", Color.red);
+ V1_COLOR_MAP.put("blue", Color.blue);
+ V1_COLOR_MAP.put("white", Color.white);
+ V1_COLOR_MAP.put("green", Color.green.darker());
+ V1_COLOR_MAP.put("black", Color.black);
+ V1_COLOR_MAP.put("yellow", Color.yellow);
+ }
+
+ private XStream xStream;
+ // Legacy deserializer for 3.0.1 through 3.0.7, loads Points referenced in
+ // pixels.
+ private XStream xStreamOld;
+
+ private String currentFileName = null;
+ private boolean modified = false;
+
+ private MessageDispatcher messageDispatcher;
+
+ public ProjectFileManager(MessageDispatcher messageDispatcher) {
+ super();
+ this.xStream = new XStream(new DomDriver());
+ xStream.autodetectAnnotations(true);
+ xStream.registerConverter(new PointConverter());
+ this.xStreamOld = new XStream(new DomDriver());
+ xStreamOld.autodetectAnnotations(true);
+ this.messageDispatcher = messageDispatcher;
+ }
+
+ public void startNewFile() {
+ currentFileName = null;
+ modified = false;
+ fireFileStatusChanged();
+ }
+
+ public void serializeProjectToFile(Project project, String fileName, boolean isBackup)
+ throws IOException {
+ if (!isBackup) {
+ LOG.info(String.format("saveProjectToFile(%s)", fileName));
+ }
+ FileOutputStream fos;
+ fos = new FileOutputStream(fileName);
+ xStream.toXML(project, fos);
+ fos.close();
+ if (!isBackup) {
+ this.currentFileName = fileName;
+ this.modified = false;
+ fireFileStatusChanged();
+ }
+ }
+
+ public Project deserializeProjectFromFile(String fileName, List warnings)
+ throws SAXException, IOException, ParserConfigurationException {
+ LOG.info(String.format("loadProjectFromFile(%s)", fileName));
+ Project project;
+ File file = new File(fileName);
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ DocumentBuilder db = dbf.newDocumentBuilder();
+ Document doc = db.parse(file);
+ doc.getDocumentElement().normalize();
+ if (doc.getDocumentElement().getNodeName().equalsIgnoreCase(Project.class.getName())) {
+ project = parseV3File(fileName);
+ } else {
+ if (!doc.getDocumentElement().getNodeName().equalsIgnoreCase("layout")) {
+ throw new IllegalArgumentException(
+ "Could not open DIY file. Root node is not named 'Layout'.");
+ }
+ String formatVersion = doc.getDocumentElement().getAttribute("formatVersion");
+ if (formatVersion == null || formatVersion.trim().isEmpty()) {
+ LOG.debug("Detected v1 file.");
+ project = parseV1File(doc.getDocumentElement(), warnings);
+ } else if (formatVersion.equals("2.0")) {
+ LOG.debug("Detected v2 file.");
+ project = parseV2File(doc.getDocumentElement());
+ } else {
+ throw new IllegalArgumentException("Unknown file format version: " + formatVersion);
+ }
+ }
+ Collections.sort(warnings);
+ this.currentFileName = fileName;
+ this.modified = false;
+ return project;
+ }
+
+ public void notifyFileChange() {
+ this.modified = true;
+ fireFileStatusChanged();
+ }
+
+ public String getCurrentFileName() {
+ return currentFileName;
+ }
+
+ public boolean isModified() {
+ return modified;
+ }
+
+ public void fireFileStatusChanged() {
+ messageDispatcher.dispatchMessage(EventType.FILE_STATUS_CHANGED, getCurrentFileName(),
+ isModified());
+ }
+
+ private Project parseV1File(Element root, List warnings) {
+ Project project = new Project();
+ project.setTitle(root.getAttribute("Project"));
+ project.setAuthor(root.getAttribute("Credits"));
+ project.setGridSpacing(V1_GRID_SPACING);
+ String type = root.getAttribute("Type");
+
+ // Create the board.
+ int width = Integer.parseInt(root.getAttribute("Width")) + 1;
+ int height = Integer.parseInt(root.getAttribute("Height")) + 1;
+ int boardWidth = (int) (width * Constants.PIXELS_PER_INCH * V1_GRID_SPACING.getValue());
+ int boardHeight = (int) (height * Constants.PIXELS_PER_INCH * V1_GRID_SPACING.getValue());
+ int projectWidth = (int) project.getWidth().convertToPixels();
+ int projectHeight = (int) project.getHeight().convertToPixels();
+ int x = (projectWidth - boardWidth) / 2;
+ int y = (projectHeight - boardHeight) / 2;
+ AbstractBoard board;
+ if (type.equalsIgnoreCase("pcb")) {
+ board = new BlankBoard();
+ board.setBoardColor(Color.white);
+ board.setBorderColor(Color.black);
+ } else if (type.equalsIgnoreCase("perfboard")) {
+ board = new PerfBoard();
+ } else if (type.equalsIgnoreCase("stripboard")) {
+ board = new VeroBoard();
+ } else {
+ throw new IllegalArgumentException("Unrecognized board type: " + type);
+ }
+ board.setName("Main board");
+ Point referencePoint = new Point(CalcUtils.roundToGrid(x, V1_GRID_SPACING), CalcUtils
+ .roundToGrid(y, V1_GRID_SPACING));
+ board.setControlPoint(referencePoint, 0);
+ board.setControlPoint(new Point(CalcUtils.roundToGrid(x + boardWidth, V1_GRID_SPACING),
+ CalcUtils.roundToGrid(y + boardHeight, V1_GRID_SPACING)), 1);
+ project.getComponents().add(board);
+
+ NodeList childNodes = root.getChildNodes();
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node node = childNodes.item(i);
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ String nodeName = node.getNodeName();
+ String nameAttr = node.getAttributes().getNamedItem("Name").getNodeValue();
+ Node valueNode = node.getAttributes().getNamedItem("Value");
+ String valueAttr = valueNode == null ? null : valueNode.getNodeValue();
+ int x1Attr = Integer.parseInt(node.getAttributes().getNamedItem("X1")
+ .getNodeValue());
+ int y1Attr = Integer.parseInt(node.getAttributes().getNamedItem("Y1")
+ .getNodeValue());
+ Point point1 = convertV1CoordinatesToV3Point(referencePoint, x1Attr, y1Attr);
+ Point point2 = null;
+ Integer x2Attr = null;
+ Integer y2Attr = null;
+ Color color = null;
+ if (node.getAttributes().getNamedItem("Color") != null) {
+ String colorAttr = node.getAttributes().getNamedItem("Color").getNodeValue();
+ color = V1_COLOR_MAP.get(colorAttr.toLowerCase());
+ }
+ if (node.getAttributes().getNamedItem("X2") != null
+ && node.getAttributes().getNamedItem("Y2") != null) {
+ x2Attr = Integer.parseInt(node.getAttributes().getNamedItem("X2")
+ .getNodeValue());
+ y2Attr = Integer.parseInt(node.getAttributes().getNamedItem("Y2")
+ .getNodeValue());
+ point2 = convertV1CoordinatesToV3Point(referencePoint, x2Attr, y2Attr);
+ }
+ IDIYComponent> component = null;
+ if (nodeName.equalsIgnoreCase("text")) {
+ LOG.debug("Recognized " + nodeName);
+ Label label = new Label();
+ label.setName(nameAttr);
+ if (color != null) {
+ label.setColor(color);
+ }
+ label.setText(valueAttr);
+ label.setCenter(false);
+ label.setControlPoint(convertV1CoordinatesToV3Point(referencePoint, x1Attr,
+ y1Attr), 0);
+ component = label;
+ } else if (nodeName.equalsIgnoreCase("pad")) {
+ LOG.debug("Recognized " + nodeName);
+ SolderPad pad = new SolderPad();
+ pad.setName(nameAttr);
+ if (color != null) {
+ pad.setColor(color);
+ }
+ pad.setControlPoint(convertV1CoordinatesToV3Point(referencePoint, x1Attr,
+ y1Attr), 0);
+ component = pad;
+ } else if (nodeName.equalsIgnoreCase("trace")) {
+ LOG.debug("Recognized " + nodeName);
+ CopperTrace trace = new CopperTrace();
+ trace.setName(nameAttr);
+ if (color != null) {
+ trace.setLeadColor(color);
+ }
+ trace.setControlPoint(point1, 0);
+ trace.setControlPoint(point2, 1);
+ component = trace;
+ } else if (nodeName.equalsIgnoreCase("jumper")) {
+ LOG.debug("Recognized " + nodeName);
+ Jumper jumper = new Jumper();
+ jumper.setName(nameAttr);
+ jumper.setControlPoint(point1, 0);
+ jumper.setControlPoint(point2, 1);
+ component = jumper;
+ } else if (nodeName.equalsIgnoreCase("wire")) {
+ LOG.debug("Recognized " + nodeName);
+ HookupWire wire = new HookupWire();
+ Point midPoint = new Point((point1.x + point2.x) / 2, (point1.y + point2.y) / 2);
+ wire.setName(nameAttr);
+ wire.setControlPoint(point1, 0);
+ wire.setControlPoint(midPoint, 1);
+ wire.setControlPoint(midPoint, 2);
+ wire.setControlPoint(point2, 3);
+ component = wire;
+ } else if (nodeName.equalsIgnoreCase("resistor")) {
+ LOG.debug("Recognized " + nodeName);
+ Resistor resistor = new Resistor();
+ resistor.setName(nameAttr);
+ try {
+ resistor.setValue(Resistance.parseResistance(valueAttr));
+ } catch (Exception e) {
+ LOG.debug("Could not set value of " + nameAttr);
+ }
+ resistor.setLength(new Size(6d, SizeUnit.mm));
+ resistor.setWidth(new Size(2d, SizeUnit.mm));
+ resistor.setControlPoint(point1, 0);
+ resistor.setControlPoint(point2, 1);
+ component = resistor;
+ } else if (nodeName.equalsIgnoreCase("capacitor")) {
+ LOG.debug("Recognized " + nodeName);
+ RadialFilmCapacitor capacitor = new RadialFilmCapacitor();
+ capacitor.setName(nameAttr);
+ try {
+ capacitor.setValue(Capacitance.parseCapacitance(valueAttr));
+ } catch (Exception e) {
+ LOG.debug("Could not set value of " + nameAttr);
+ }
+ capacitor.setLength(new Size(6d, SizeUnit.mm));
+ capacitor.setWidth(new Size(2d, SizeUnit.mm));
+ capacitor.setControlPoint(point1, 0);
+ capacitor.setControlPoint(point2, 1);
+ component = capacitor;
+ } else if (nodeName.equalsIgnoreCase("electrolyte")) {
+ LOG.debug("Recognized " + nodeName);
+ RadialElectrolytic capacitor = new RadialElectrolytic();
+ capacitor.setName(nameAttr);
+ try {
+ capacitor.setValue(Capacitance.parseCapacitance(valueAttr));
+ } catch (Exception e) {
+ LOG.debug("Could not set value of " + nameAttr);
+ }
+ String sizeAttr = node.getAttributes().getNamedItem("Size").getNodeValue();
+ if (sizeAttr.equalsIgnoreCase("small")) {
+ capacitor.setLength(new Size(3.5d, SizeUnit.mm));
+ } else if (sizeAttr.equalsIgnoreCase("medium")) {
+ capacitor.setLength(new Size(5d, SizeUnit.mm));
+ } else if (sizeAttr.equalsIgnoreCase("large")) {
+ capacitor.setLength(new Size(7d, SizeUnit.mm));
+ } else {
+ capacitor.setLength(new Size(4d, SizeUnit.mm));
+ }
+ capacitor.setControlPoint(point1, 0);
+ capacitor.setControlPoint(point2, 1);
+ component = capacitor;
+ } else if (nodeName.equalsIgnoreCase("diode")) {
+ LOG.debug("Recognized " + nodeName);
+ DiodePlastic capacitor = new DiodePlastic();
+ capacitor.setName(nameAttr);
+ try {
+ capacitor.setValue(valueAttr);
+ } catch (Exception e) {
+ LOG.debug("Could not set value of " + nameAttr);
+ }
+ capacitor.setLength(new Size(6d, SizeUnit.mm));
+ capacitor.setWidth(new Size(2d, SizeUnit.mm));
+ capacitor.setControlPoint(point1, 0);
+ capacitor.setControlPoint(point2, 1);
+ component = capacitor;
+ } else if (nodeName.equalsIgnoreCase("led")) {
+ LOG.debug("Recognized " + nodeName);
+ LED led = new LED();
+ led.setName(nameAttr);
+ led.setValue(valueAttr);
+ led.setBodyColor(Color.red);
+ led.setBorderColor(Color.red.darker());
+ led.setLength(new Size(3d, SizeUnit.mm));
+ led.setControlPoint(point1, 0);
+ led.setControlPoint(point2, 1);
+ component = led;
+ } else if (nodeName.equalsIgnoreCase("transistor")) {
+ LOG.debug("Recognized " + nodeName);
+ TransistorTO92 transistor = new TransistorTO92();
+ transistor.setName(nameAttr);
+ try {
+ transistor.setValue(valueAttr);
+ } catch (Exception e) {
+ LOG.debug("Could not set value of " + nameAttr);
+ }
+ transistor.setControlPoint(point1, 0);
+ if (point1.y > point2.y) {
+ transistor.setOrientation(Orientation._180);
+ } else if (point1.y < point2.y) {
+ transistor.setOrientation(Orientation.DEFAULT);
+ } else if (point1.x < point2.x) {
+ transistor.setOrientation(Orientation._270);
+ } else if (point1.x > point2.x) {
+ transistor.setOrientation(Orientation._90);
+ }
+ // capacitor.setControlPoint(point2, 1);
+ component = transistor;
+ } else if (nodeName.equalsIgnoreCase("ic")) {
+ LOG.debug("Recognized " + nodeName);
+ DIL_IC ic = new DIL_IC();
+ if (x1Attr + 3 == x2Attr) {
+ if (y1Attr < y2Attr) {
+ ic
+ .setPinCount(DIL_IC.PinCount.valueOf("_" + 2
+ * (y2Attr - y1Attr + 1)));
+ } else {
+ ic.setOrientation(Orientation._180);
+ ic
+ .setPinCount(DIL_IC.PinCount.valueOf("_" + 2
+ * (y1Attr - y2Attr + 1)));
+ }
+ }
+ ic.setName(nameAttr);
+ // Translate control points.
+ for (int j = 0; j < ic.getControlPointCount(); j++) {
+ Point p = new Point(ic.getControlPoint(j));
+ p.translate(point1.x, point1.y);
+ ic.setControlPoint(p, j);
+ }
+ ic.setValue(valueAttr);
+ component = ic;
+ } else {
+ String message = "Could not recognize component type " + nodeName;
+ LOG.debug(message);
+ if (!warnings.contains(message)) {
+ warnings.add(message);
+ }
+ }
+ if (component != null) {
+ project.getComponents().add(component);
+ }
+ }
+ }
+ Collections.sort(project.getComponents(), ComparatorFactory.getInstance()
+ .getComponentZOrderComparator());
+ return project;
+ }
+
+ private Point convertV1CoordinatesToV3Point(Point reference, int x, int y) {
+ Point point = new Point(reference);
+ point.translate((int) (x * Constants.PIXELS_PER_INCH * V1_GRID_SPACING.getValue()),
+ (int) (y * Constants.PIXELS_PER_INCH * V1_GRID_SPACING.getValue()));
+ return point;
+ }
+
+ private Project parseV2File(Element root) {
+ Project project = new Project();
+ String projectName = root.getAttribute("projectName");
+ String credits = root.getAttribute("credits");
+ String width = root.getAttribute("width");
+ String height = root.getAttribute("height");
+ NodeList childNodes = root.getChildNodes();
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node node = childNodes.item(i);
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ if (node.getNodeName().equalsIgnoreCase("component")) {
+ LOG.debug(node.getAttributes().getNamedItem("name").getNodeValue());
+ } else {
+ LOG.debug("Unrecognized node name found: " + node.getNodeName());
+ }
+ }
+ }
+ return project;
+ }
+
+ private Project parseV3File(String fileName) throws IOException {
+ Project project;
+ FileInputStream fis = new FileInputStream(fileName);
+ try {
+ project = (Project) xStream.fromXML(fis);
+ } catch (Exception e) {
+ LOG.warn("Could not open with the new xStream, trying the old one");
+ fis.close();
+ fis = new FileInputStream(fileName);
+ project = (Project) xStreamOld.fromXML(fis);
+ }
+ fis.close();
+ return project;
+ }
+}
\ No newline at end of file
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/ActionFactory.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/ActionFactory.java
new file mode 100644
index 00000000..4fd80d3a
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/ActionFactory.java
@@ -0,0 +1,870 @@
+package org.diylc.swing;
+
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.ClipboardOwner;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.awt.print.PrinterException;
+import java.io.File;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.KeyStroke;
+
+import org.apache.log4j.Logger;
+import org.diylc.common.IPlugInPort;
+import org.diylc.common.ITask;
+import org.diylc.common.PropertyWrapper;
+import org.diylc.core.IDIYComponent;
+import org.diylc.core.IView;
+import org.diylc.core.Theme;
+import org.diylc.images.IconLoader;
+import org.diylc.swing.gui.DialogFactory;
+import org.diylc.swing.gui.editor.PropertyEditorDialog;
+import org.diylc.swing.plugins.edit.ComponentTransferable;
+import org.diylc.swing.plugins.file.BomDialog;
+import org.diylc.swing.plugins.file.BomEntry;
+import org.diylc.swing.plugins.file.BomMaker;
+import org.diylc.swing.plugins.file.FileFilterEnum;
+
+import com.diyfever.gui.ButtonDialog;
+import com.diyfever.gui.IDrawingProvider;
+import com.diyfever.gui.export.DrawingExporter;
+import com.diyfever.gui.miscutils.ConfigurationManager;
+import com.rits.cloning.Cloner;
+
+public class ActionFactory {
+
+ private static final Logger LOG = Logger.getLogger(ActionFactory.class);
+
+ private static ActionFactory instance;
+ private static Cloner cloner = new Cloner();
+
+ public static ActionFactory getInstance() {
+ if (instance == null) {
+ instance = new ActionFactory();
+ }
+ return instance;
+ }
+
+ private ActionFactory() {
+ }
+
+ // File menu actions.
+
+ public NewAction createNewAction(IPlugInPort plugInPort) {
+ return new NewAction(plugInPort);
+ }
+
+ public OpenAction createOpenAction(IPlugInPort plugInPort, ISwingUI swingUI) {
+ return new OpenAction(plugInPort, swingUI);
+ }
+
+ public SaveAction createSaveAction(IPlugInPort plugInPort, ISwingUI swingUI) {
+ return new SaveAction(plugInPort, swingUI);
+ }
+
+ public SaveAsAction createSaveAsAction(IPlugInPort plugInPort, ISwingUI swingUI) {
+ return new SaveAsAction(plugInPort, swingUI);
+ }
+
+ public CreateBomAction createBomAction(IPlugInPort plugInPort) {
+ return new CreateBomAction(plugInPort);
+ }
+
+ public ExportPDFAction createExportPDFAction(IDrawingProvider drawingProvider, ISwingUI swingUI) {
+ return new ExportPDFAction(drawingProvider, swingUI);
+ }
+
+ public ExportPNGAction createExportPNGAction(IDrawingProvider drawingProvider, ISwingUI swingUI) {
+ return new ExportPNGAction(drawingProvider, swingUI);
+ }
+
+ public PrintAction createPrintAction(IDrawingProvider drawingProvider) {
+ return new PrintAction(drawingProvider);
+ }
+
+ public ExitAction createExitAction(IPlugInPort plugInPort) {
+ return new ExitAction(plugInPort);
+ }
+
+ // Edit menu actions.
+
+ public CutAction createCutAction(IPlugInPort plugInPort, Clipboard clipboard,
+ ClipboardOwner clipboardOwner) {
+ return new CutAction(plugInPort, clipboard, clipboardOwner);
+ }
+
+ public CopyAction createCopyAction(IPlugInPort plugInPort, Clipboard clipboard,
+ ClipboardOwner clipboardOwner) {
+ return new CopyAction(plugInPort, clipboard, clipboardOwner);
+ }
+
+ public PasteAction createPasteAction(IPlugInPort plugInPort, Clipboard clipboard) {
+ return new PasteAction(plugInPort, clipboard);
+ }
+
+ public SelectAllAction createSelectAllAction(IPlugInPort plugInPort) {
+ return new SelectAllAction(plugInPort);
+ }
+
+ public GroupAction createGroupAction(IPlugInPort plugInPort) {
+ return new GroupAction(plugInPort);
+ }
+
+ public UngroupAction createUngroupAction(IPlugInPort plugInPort) {
+ return new UngroupAction(plugInPort);
+ }
+
+ public EditProjectAction createEditProjectAction(IPlugInPort plugInPort) {
+ return new EditProjectAction(plugInPort);
+ }
+
+ public EditSelectionAction createEditSelectionAction(IPlugInPort plugInPort) {
+ return new EditSelectionAction(plugInPort);
+ }
+
+ public DeleteSelectionAction createDeleteSelectionAction(IPlugInPort plugInPort) {
+ return new DeleteSelectionAction(plugInPort);
+ }
+
+ public SendToBackAction createSendToBackAction(IPlugInPort plugInPort) {
+ return new SendToBackAction(plugInPort);
+ }
+
+ public BringToFrontAction createBringToFrontAction(IPlugInPort plugInPort) {
+ return new BringToFrontAction(plugInPort);
+ }
+
+ // Config actions.
+
+ public ConfigAction createConfigAction(IPlugInPort plugInPort, String title, String configKey,
+ boolean defaultValue) {
+ return new ConfigAction(plugInPort, title, configKey, defaultValue);
+ }
+
+ public ThemeAction createThemeAction(IPlugInPort plugInPort, Theme theme) {
+ return new ThemeAction(plugInPort, theme);
+ }
+
+ public RenumberAction createRenumberAction(IPlugInPort plugInPort, boolean xAxisFirst) {
+ return new RenumberAction(plugInPort, xAxisFirst);
+ }
+
+ // File menu actions.
+
+ public static class NewAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+
+ public NewAction(IPlugInPort plugInPort) {
+ super();
+ this.plugInPort = plugInPort;
+ putValue(AbstractAction.NAME, "New");
+ putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_N,
+ ActionEvent.CTRL_MASK));
+ putValue(AbstractAction.SMALL_ICON, IconLoader.DocumentPlainYellow.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info("NewAction triggered");
+ if (!plugInPort.allowFileAction()) {
+ return;
+ }
+ plugInPort.createNewProject();
+ List properties = plugInPort.getProjectProperties();
+ PropertyEditorDialog editor = DialogFactory.getInstance().createPropertyEditorDialog(
+ properties, "Edit Project");
+ editor.setVisible(true);
+ if (ButtonDialog.OK.equals(editor.getSelectedButtonCaption())) {
+ plugInPort.applyPropertiesToProject(properties);
+ }
+ // Save default values.
+ for (PropertyWrapper property : editor.getDefaultedProperties()) {
+ if (property.getValue() != null) {
+ plugInPort.setProjectDefaultPropertyValue(property.getName(), property
+ .getValue());
+ }
+ }
+ }
+ }
+
+ public static class OpenAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+ private ISwingUI swingUI;
+
+ public OpenAction(IPlugInPort plugInPort, ISwingUI swingUI) {
+ super();
+ this.plugInPort = plugInPort;
+ this.swingUI = swingUI;
+ putValue(AbstractAction.NAME, "Open");
+ putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_O,
+ ActionEvent.CTRL_MASK));
+ putValue(AbstractAction.SMALL_ICON, IconLoader.FolderOut.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info("OpenAction triggered");
+ if (!plugInPort.allowFileAction()) {
+ return;
+ }
+ final File file = DialogFactory.getInstance().showOpenDialog(
+ FileFilterEnum.DIY.getFilter(), null, FileFilterEnum.DIY.getExtensions()[0],
+ null);
+ if (file != null) {
+ swingUI.executeBackgroundTask(new ITask() {
+
+ @Override
+ public Void doInBackground() throws Exception {
+ LOG.debug("Opening from " + file.getAbsolutePath());
+ plugInPort.loadProjectFromFile(file.getAbsolutePath());
+ return null;
+ }
+
+ @Override
+ public void complete(Void result) {
+ }
+
+ @Override
+ public void failed(Exception e) {
+ swingUI.showMessage("Could not open file. " + e.getMessage(), "Error",
+ ISwingUI.ERROR_MESSAGE);
+ }
+ });
+ }
+ }
+ }
+
+ public static class SaveAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+ private ISwingUI swingUI;
+
+ public SaveAction(IPlugInPort plugInPort, ISwingUI swingUI) {
+ super();
+ this.plugInPort = plugInPort;
+ this.swingUI = swingUI;
+ putValue(AbstractAction.NAME, "Save");
+ putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_S,
+ ActionEvent.CTRL_MASK));
+ putValue(AbstractAction.SMALL_ICON, IconLoader.DiskBlue.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info("SaveAction triggered");
+ if (plugInPort.getCurrentFileName() == null) {
+ final File file = DialogFactory.getInstance().showSaveDialog(
+ FileFilterEnum.DIY.getFilter(), null,
+ FileFilterEnum.DIY.getExtensions()[0], null);
+ if (file != null) {
+ swingUI.executeBackgroundTask(new ITask() {
+
+ @Override
+ public Void doInBackground() throws Exception {
+ LOG.debug("Saving to " + file.getAbsolutePath());
+ plugInPort.saveProjectToFile(file.getAbsolutePath(), false);
+ return null;
+ }
+
+ @Override
+ public void complete(Void result) {
+ }
+
+ @Override
+ public void failed(Exception e) {
+ swingUI.showMessage("Could not save to file. " + e.getMessage(),
+ "Error", ISwingUI.ERROR_MESSAGE);
+ }
+ });
+ }
+ } else {
+ swingUI.executeBackgroundTask(new ITask() {
+
+ @Override
+ public Void doInBackground() throws Exception {
+ LOG.debug("Saving to " + plugInPort.getCurrentFileName());
+ plugInPort.saveProjectToFile(plugInPort.getCurrentFileName(), false);
+ return null;
+ }
+
+ @Override
+ public void complete(Void result) {
+ }
+
+ @Override
+ public void failed(Exception e) {
+ swingUI.showMessage("Could not save to file. " + e.getMessage(), "Error",
+ ISwingUI.ERROR_MESSAGE);
+ }
+ });
+ }
+ }
+ }
+
+ public static class SaveAsAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+ private ISwingUI swingUI;
+
+ public SaveAsAction(IPlugInPort plugInPort, ISwingUI swingUI) {
+ super();
+ this.plugInPort = plugInPort;
+ this.swingUI = swingUI;
+ putValue(AbstractAction.NAME, "Save As");
+ putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_S,
+ ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK));
+ putValue(AbstractAction.SMALL_ICON, IconLoader.DiskBlue.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info("SaveAsAction triggered");
+ final File file = DialogFactory.getInstance().showSaveDialog(
+ FileFilterEnum.DIY.getFilter(), null, FileFilterEnum.DIY.getExtensions()[0],
+ null);
+ if (file != null) {
+ swingUI.executeBackgroundTask(new ITask() {
+
+ @Override
+ public Void doInBackground() throws Exception {
+ LOG.debug("Saving to " + file.getAbsolutePath());
+ plugInPort.saveProjectToFile(file.getAbsolutePath(), false);
+ return null;
+ }
+
+ @Override
+ public void complete(Void result) {
+ }
+
+ @Override
+ public void failed(Exception e) {
+ swingUI.showMessage("Could not save to file. " + e.getMessage(), "Error",
+ ISwingUI.ERROR_MESSAGE);
+ }
+ });
+ }
+ }
+ }
+
+ public static class CreateBomAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+
+ public CreateBomAction(IPlugInPort plugInPort) {
+ super();
+ this.plugInPort = plugInPort;
+ putValue(AbstractAction.NAME, "Create B.O.M.");
+ putValue(AbstractAction.SMALL_ICON, IconLoader.BOM.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info("CreateBomAction triggered");
+ List bom = BomMaker.getInstance().createBom(
+ plugInPort.getCurrentProject().getComponents());
+ BomDialog dialog = DialogFactory.getInstance().createBomDialog(bom);
+ dialog.setVisible(true);
+ }
+ }
+
+ public static class ExportPDFAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IDrawingProvider drawingProvider;
+ private ISwingUI swingUI;
+
+ public ExportPDFAction(IDrawingProvider drawingProvider, ISwingUI swingUI) {
+ super();
+ this.drawingProvider = drawingProvider;
+ this.swingUI = swingUI;
+ putValue(AbstractAction.NAME, "Export to PDF");
+ putValue(AbstractAction.SMALL_ICON, IconLoader.PDF.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info("ExportPDFAction triggered");
+ final File file = DialogFactory.getInstance().showSaveDialog(
+ FileFilterEnum.PDF.getFilter(), null, FileFilterEnum.PDF.getExtensions()[0],
+ null);
+ if (file != null) {
+ swingUI.executeBackgroundTask(new ITask() {
+
+ @Override
+ public Void doInBackground() throws Exception {
+ LOG.debug("Exporting to " + file.getAbsolutePath());
+ DrawingExporter.getInstance().exportPDF(
+ ExportPDFAction.this.drawingProvider, file);
+ return null;
+ }
+
+ @Override
+ public void complete(Void result) {
+ }
+
+ @Override
+ public void failed(Exception e) {
+ swingUI.showMessage("Could not export to PDF. " + e.getMessage(), "Error",
+ ISwingUI.ERROR_MESSAGE);
+ }
+ });
+ }
+ }
+ }
+
+ public static class ExportPNGAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IDrawingProvider drawingProvider;
+ private ISwingUI swingUI;
+
+ public ExportPNGAction(IDrawingProvider drawingProvider, ISwingUI swingUI) {
+ super();
+ this.drawingProvider = drawingProvider;
+ this.swingUI = swingUI;
+ putValue(AbstractAction.NAME, "Export to PNG");
+ putValue(AbstractAction.SMALL_ICON, IconLoader.Image.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info("ExportPNGAction triggered");
+ final File file = DialogFactory.getInstance().showSaveDialog(
+ FileFilterEnum.PNG.getFilter(), null, FileFilterEnum.PNG.getExtensions()[0],
+ null);
+ if (file != null) {
+ swingUI.executeBackgroundTask(new ITask() {
+
+ @Override
+ public Void doInBackground() throws Exception {
+ LOG.debug("Exporting to " + file.getAbsolutePath());
+ DrawingExporter.getInstance().exportPNG(
+ ExportPNGAction.this.drawingProvider, file);
+ return null;
+ }
+
+ @Override
+ public void complete(Void result) {
+ }
+
+ @Override
+ public void failed(Exception e) {
+ swingUI.showMessage("Could not export to PNG. " + e.getMessage(), "Error",
+ ISwingUI.ERROR_MESSAGE);
+ }
+ });
+ }
+ }
+ }
+
+ public static class PrintAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IDrawingProvider drawingProvider;
+
+ public PrintAction(IDrawingProvider drawingProvider) {
+ super();
+ this.drawingProvider = drawingProvider;
+ putValue(AbstractAction.NAME, "Print...");
+ putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_P,
+ ActionEvent.CTRL_MASK));
+ putValue(AbstractAction.SMALL_ICON, IconLoader.Print.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info("ExportPNGAction triggered");
+ try {
+ DrawingExporter.getInstance().print(this.drawingProvider);
+ } catch (PrinterException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ }
+ }
+
+ public static class ExitAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+
+ public ExitAction(IPlugInPort plugInPort) {
+ super();
+ this.plugInPort = plugInPort;
+ putValue(AbstractAction.NAME, "Exit");
+ putValue(AbstractAction.SMALL_ICON, IconLoader.Exit.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (plugInPort.allowFileAction()) {
+ System.exit(0);
+ }
+ }
+ }
+
+ // Edit menu actions.
+
+ public static class CutAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+ private Clipboard clipboard;
+ private ClipboardOwner clipboardOwner;
+
+ public CutAction(IPlugInPort plugInPort, Clipboard clipboard, ClipboardOwner clipboardOwner) {
+ super();
+ this.plugInPort = plugInPort;
+ this.clipboard = clipboard;
+ this.clipboardOwner = clipboardOwner;
+ putValue(AbstractAction.NAME, "Cut");
+ putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_X,
+ ActionEvent.CTRL_MASK));
+ putValue(AbstractAction.SMALL_ICON, IconLoader.Cut.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info("Cut triggered");
+ clipboard.setContents(new ComponentTransferable(cloner.deepClone(plugInPort
+ .getSelectedComponents())), clipboardOwner);
+ plugInPort.deleteSelectedComponents();
+ }
+ }
+
+ public static class CopyAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+ private Clipboard clipboard;
+ private ClipboardOwner clipboardOwner;
+
+ public CopyAction(IPlugInPort plugInPort, Clipboard clipboard, ClipboardOwner clipboardOwner) {
+ super();
+ this.plugInPort = plugInPort;
+ this.clipboard = clipboard;
+ this.clipboardOwner = clipboardOwner;
+ putValue(AbstractAction.NAME, "Copy");
+ putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_C,
+ ActionEvent.CTRL_MASK));
+ putValue(AbstractAction.SMALL_ICON, IconLoader.Copy.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info("Copy triggered");
+ clipboard.setContents(new ComponentTransferable(cloner.deepClone(plugInPort
+ .getSelectedComponents())), clipboardOwner);
+ }
+ }
+
+ public static class PasteAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+ private Clipboard clipboard;
+
+ public PasteAction(IPlugInPort plugInPort, Clipboard clipboard) {
+ super();
+ this.plugInPort = plugInPort;
+ this.clipboard = clipboard;
+ putValue(AbstractAction.NAME, "Paste");
+ putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_V,
+ ActionEvent.CTRL_MASK));
+ putValue(AbstractAction.SMALL_ICON, IconLoader.Paste.getIcon());
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info("Paste triggered");
+ try {
+ List> components = (List>) clipboard
+ .getData(ComponentTransferable.listFlavor);
+ plugInPort.pasteComponents(cloner.deepClone(components));
+ } catch (Exception ex) {
+ LOG.error("Coule not paste.", ex);
+ }
+ }
+ }
+
+ public static class SelectAllAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+
+ public SelectAllAction(IPlugInPort plugInPort) {
+ super();
+ this.plugInPort = plugInPort;
+ putValue(AbstractAction.NAME, "Select All");
+ putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_A,
+ ActionEvent.CTRL_MASK));
+ putValue(AbstractAction.SMALL_ICON, IconLoader.Selection.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info("Select All triggered");
+ plugInPort.selectAll();
+ }
+ }
+
+ public static class GroupAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+
+ public GroupAction(IPlugInPort plugInPort) {
+ super();
+ this.plugInPort = plugInPort;
+ putValue(AbstractAction.NAME, "Group Selection");
+ putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_G,
+ ActionEvent.CTRL_MASK));
+ putValue(AbstractAction.SMALL_ICON, IconLoader.Group.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info("Group Selection triggered");
+ plugInPort.groupSelectedComponents();
+ }
+ }
+
+ public static class UngroupAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+
+ public UngroupAction(IPlugInPort plugInPort) {
+ super();
+ this.plugInPort = plugInPort;
+ putValue(AbstractAction.NAME, "Ungroup Selection");
+ putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_U,
+ ActionEvent.CTRL_MASK));
+ putValue(AbstractAction.SMALL_ICON, IconLoader.Ungroup.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info("Ungroup Selection triggered");
+ plugInPort.ungroupSelectedComponents();
+ }
+ }
+
+ public static class EditProjectAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+
+ public EditProjectAction(IPlugInPort plugInPort) {
+ super();
+ this.plugInPort = plugInPort;
+ putValue(AbstractAction.NAME, "Edit Project");
+ putValue(AbstractAction.SMALL_ICON, IconLoader.DocumentEdit.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info("Edit Project triggered");
+ List properties = plugInPort.getProjectProperties();
+ PropertyEditorDialog editor = DialogFactory.getInstance().createPropertyEditorDialog(
+ properties, "Edit Project");
+ editor.setVisible(true);
+ if (ButtonDialog.OK.equals(editor.getSelectedButtonCaption())) {
+ plugInPort.applyPropertiesToProject(properties);
+ }
+ // Save default values.
+ for (PropertyWrapper property : editor.getDefaultedProperties()) {
+ if (property.getValue() != null) {
+ plugInPort.setProjectDefaultPropertyValue(property.getName(), property
+ .getValue());
+ }
+ }
+ }
+ }
+
+ public static class EditSelectionAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+
+ public EditSelectionAction(IPlugInPort plugInPort) {
+ super();
+ this.plugInPort = plugInPort;
+ putValue(AbstractAction.NAME, "Edit Selection");
+ putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_E,
+ ActionEvent.CTRL_MASK));
+ putValue(AbstractAction.SMALL_ICON, IconLoader.EditComponent.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info("Edit Selection triggered");
+ List properties = plugInPort.getMutualSelectionProperties();
+ if (properties == null || properties.isEmpty()) {
+ LOG.info("Nothing to edit");
+ return;
+ }
+ plugInPort.editSelection();
+ }
+ }
+
+ public static class DeleteSelectionAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+
+ public DeleteSelectionAction(IPlugInPort plugInPort) {
+ super();
+ this.plugInPort = plugInPort;
+ putValue(AbstractAction.NAME, "Delete Selection");
+ putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
+ putValue(AbstractAction.SMALL_ICON, IconLoader.Delete.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info("Delete Selection triggered");
+ plugInPort.deleteSelectedComponents();
+ }
+ }
+
+ public static class SendToBackAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+
+ public SendToBackAction(IPlugInPort plugInPort) {
+ super();
+ this.plugInPort = plugInPort;
+ putValue(AbstractAction.NAME, "Send Backward");
+ putValue(AbstractAction.SMALL_ICON, IconLoader.Back.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info("Send to Back triggered");
+ plugInPort.sendSelectionToBack();
+ }
+ }
+
+ public static class BringToFrontAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+
+ public BringToFrontAction(IPlugInPort plugInPort) {
+ super();
+ this.plugInPort = plugInPort;
+ putValue(AbstractAction.NAME, "Bring Forward");
+ putValue(AbstractAction.SMALL_ICON, IconLoader.Front.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info("Bring to Front triggered");
+ plugInPort.bringSelectionToFront();
+ }
+ }
+
+ public static class ConfigAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+ private String configKey;
+
+ public ConfigAction(IPlugInPort plugInPort, String title, String configKey,
+ boolean defaultValue) {
+ super();
+ this.plugInPort = plugInPort;
+ this.configKey = configKey;
+ putValue(AbstractAction.NAME, title);
+ putValue(IView.CHECK_BOX_MENU_ITEM, true);
+ putValue(AbstractAction.SELECTED_KEY, ConfigurationManager.getInstance().readBoolean(
+ configKey, defaultValue));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info(getValue(AbstractAction.NAME) + " triggered");
+ ConfigurationManager.getInstance().writeValue(configKey,
+ getValue(AbstractAction.SELECTED_KEY));
+ plugInPort.refresh();
+ }
+ }
+
+ public static class ThemeAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+ private Theme theme;
+
+ public ThemeAction(IPlugInPort plugInPort, Theme theme) {
+ super();
+ this.plugInPort = plugInPort;
+ this.theme = theme;
+ putValue(AbstractAction.NAME, theme.getName());
+ putValue(IView.RADIO_BUTTON_GROUP_KEY, "theme");
+ putValue(AbstractAction.SELECTED_KEY, plugInPort.getSelectedTheme().getName().equals(
+ theme.getName()));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info(getValue(AbstractAction.NAME) + " triggered");
+ plugInPort.setSelectedTheme(theme);
+ }
+ }
+
+ public static class RenumberAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+ private boolean xAxisFirst;
+
+ public RenumberAction(IPlugInPort plugInPort, boolean xAxisFirst) {
+ super();
+ this.plugInPort = plugInPort;
+ this.xAxisFirst = xAxisFirst;
+ putValue(AbstractAction.NAME, xAxisFirst ? "X Axis First" : "Y Axis First");
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LOG.info(getValue(AbstractAction.NAME) + " triggered");
+ plugInPort.renumberSelectedComponents(xAxisFirst);
+ }
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/ISwingUI.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/ISwingUI.java
new file mode 100644
index 00000000..b316113f
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/ISwingUI.java
@@ -0,0 +1,64 @@
+package org.diylc.swing;
+
+import javax.swing.Action;
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.SwingConstants;
+import javax.swing.JPopupMenu.Separator;
+
+import org.diylc.common.BadPositionException;
+import org.diylc.common.ITask;
+import org.diylc.core.IView;
+
+/**
+ * Interface for plugin access to the swing front end.
+ *
+ * @author Branislav Stojkovic
+ */
+public interface ISwingUI extends IView {
+
+ /**
+ * Injects a custom GUI panels provided by the plug-in and desired position
+ * in the window. Application will layout plug-in panels accordingly.
+ * Valid positions are:
+ *
+ * - {@link SwingConstants#TOP}
+ * - {@link SwingConstants#BOTTOM}
+ * - {@link SwingConstants#LEFT}
+ * - {@link SwingConstants#RIGHT}
+ *
+ *
+ * Center position is reserved for the main canvas panel and cannot be used.
+ *
+ * @param component
+ * @param position
+ * @throws BadPositionException
+ * in case invalid position is specified
+ */
+ void injectGUIComponent(JComponent component, int position) throws BadPositionException;
+
+ /**
+ * Injects a custom menu action into application's main menu. If
+ * action is set to null {@link Separator} will be added. If
+ * the specified menu does not exist it will be automatically created.
+ *
+ * @param action
+ * {@link Action} to inser
+ * @param menuName
+ * name of the menu to insert into
+ */
+ void injectMenuAction(Action action, String menuName);
+
+ /**
+ * Injects a custom submenu into application's main menu. If
+ * action is set to null {@link Separator} will be added. If
+ * the specified menu does not exist it will be automatically created.
+ *
+ * @param name
+ * @param icon
+ * @param parentMenuName
+ */
+ void injectSubmenu(String name, Icon icon, String parentMenuName);
+
+ void executeBackgroundTask(ITask task);
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/CustomGlassPane.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/CustomGlassPane.java
new file mode 100644
index 00000000..9852bdcd
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/CustomGlassPane.java
@@ -0,0 +1,42 @@
+package org.diylc.swing.gui;
+
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+
+import javax.swing.Icon;
+import javax.swing.JComponent;
+
+public class CustomGlassPane extends JComponent {
+
+ private static final long serialVersionUID = 1L;
+ private Icon icon;
+
+ public CustomGlassPane() {
+ super();
+ addMouseMotionListener(new MouseMotionAdapter() {
+
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ if (icon != null) {
+ repaint();
+ }
+ }
+ });
+ }
+
+ public void setCursorIcon(Icon icon) {
+ this.icon = icon;
+ }
+
+ @Override
+ public void paint(Graphics g) {
+ Point cursor = getMousePosition();
+ if ((icon != null) && (cursor != null)) {
+ Graphics2D g2d = (Graphics2D) g;
+ icon.paintIcon(this, g2d, cursor.x + 10, cursor.y + 10);
+ }
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/DialogFactory.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/DialogFactory.java
new file mode 100644
index 00000000..d5a3b031
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/DialogFactory.java
@@ -0,0 +1,155 @@
+package org.diylc.swing.gui;
+
+import java.io.File;
+import java.util.List;
+
+import javax.swing.Icon;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.filechooser.FileFilter;
+
+import org.diylc.common.PropertyWrapper;
+import org.diylc.swing.gui.components.OverwritePromptFileChooser;
+import org.diylc.swing.gui.editor.PropertyEditorDialog;
+import org.diylc.swing.plugins.file.BomDialog;
+import org.diylc.swing.plugins.file.BomEntry;
+import org.diylc.swing.plugins.online.view.LoginDialog;
+import org.diylc.swing.plugins.online.view.NewUserDialog;
+import org.diylc.swing.plugins.online.view.UploadDialog;
+
+import com.diyfever.gui.AboutDialog;
+import com.diyfever.gui.IFileChooserAccessory;
+import com.diyfever.gui.ProgressDialog;
+import com.diyfever.gui.miscutils.ConfigurationManager;
+
+public class DialogFactory {
+
+ private static DialogFactory instance;
+
+ private static final String PATH_KEY = "lastPath";
+
+ public static DialogFactory getInstance() {
+ if (instance == null) {
+ instance = new DialogFactory();
+ }
+ return instance;
+ }
+
+ private JFrame mainFrame;
+ private File lastDirectory;
+
+ private DialogFactory() {
+ }
+
+ /**
+ * Sets the frame to be used as dialog parent. This should be called prior
+ * to any other methods in this class.
+ *
+ * @param mainFrame
+ */
+ public void initialize(JFrame mainFrame) {
+ this.mainFrame = mainFrame;
+ String lastDirectoryPath = (String) ConfigurationManager.getInstance()
+ .readString(PATH_KEY, null);
+ if (lastDirectoryPath != null) {
+ lastDirectory = new File(lastDirectoryPath);
+ }
+ }
+
+ public PropertyEditorDialog createPropertyEditorDialog(List properties,
+ String title) {
+ PropertyEditorDialog editor = new PropertyEditorDialog(mainFrame, properties, title);
+ return editor;
+ }
+
+ public BomDialog createBomDialog(List bom) {
+ BomDialog dialog = new BomDialog(mainFrame, bom);
+ return dialog;
+ }
+
+ public File showOpenDialog(FileFilter fileFilter, File initialFile, String defaultExtension,
+ IFileChooserAccessory accessory) {
+ JFileChooser openFileChooser = new JFileChooser();
+ initializeFileChooser(openFileChooser, fileFilter, initialFile, defaultExtension, accessory);
+
+ int result = openFileChooser.showOpenDialog(mainFrame);
+
+ return processFileChooserResult(result, openFileChooser, defaultExtension);
+ }
+
+ public File showSaveDialog(FileFilter fileFilter, File initialFile, String defaultExtension,
+ IFileChooserAccessory accessory) {
+ JFileChooser saveFileChooser = new OverwritePromptFileChooser();
+ initializeFileChooser(saveFileChooser, fileFilter, initialFile, defaultExtension, accessory);
+
+ int result = saveFileChooser.showSaveDialog(mainFrame);
+
+ return processFileChooserResult(result, saveFileChooser, defaultExtension);
+ }
+
+ private void initializeFileChooser(JFileChooser fileChooser, FileFilter fileFilter,
+ File initialFile, String defaultExtension, IFileChooserAccessory accessory) {
+ if (accessory != null) {
+ accessory.install(fileChooser);
+ }
+ for (FileFilter filter : fileChooser.getChoosableFileFilters()) {
+ fileChooser.removeChoosableFileFilter(filter);
+ }
+ if (fileChooser instanceof OverwritePromptFileChooser) {
+ ((OverwritePromptFileChooser) fileChooser).setFileFilter(fileFilter, defaultExtension);
+ } else {
+ fileChooser.setFileFilter(fileFilter);
+ }
+ if (lastDirectory != null) {
+ fileChooser.setCurrentDirectory(lastDirectory);
+ }
+ fileChooser.setSelectedFile(initialFile);
+ }
+
+ private File processFileChooserResult(int result, JFileChooser fileChooser,
+ String defaultExtension) {
+ fileChooser.setAccessory(null);
+ if (result == JFileChooser.APPROVE_OPTION) {
+ lastDirectory = fileChooser.getCurrentDirectory();
+ ConfigurationManager.getInstance().writeValue(PATH_KEY,
+ lastDirectory.getAbsolutePath());
+ if (fileChooser.getSelectedFile().getAbsolutePath().contains(".")) {
+ return fileChooser.getSelectedFile();
+ } else {
+ return new File(fileChooser.getSelectedFile().getAbsoluteFile() + "."
+ + defaultExtension);
+ }
+ } else {
+ return null;
+ }
+ }
+
+ public AboutDialog createAboutDialog(String appName, Icon icon, String version, String author,
+ String url, String mail, String htmlContent) {
+ AboutDialog dialog = new AboutDialog(mainFrame, appName, icon, version, author, url, mail,
+ htmlContent);
+ return dialog;
+ }
+
+ public NewUserDialog createNewUserDialog() {
+ NewUserDialog dialog = new NewUserDialog(mainFrame);
+ return dialog;
+ }
+
+ public LoginDialog createLoginDialog() {
+ LoginDialog dialog = new LoginDialog(mainFrame);
+ return dialog;
+ }
+
+ public UploadDialog createUploadDialog() {
+ UploadDialog dialog = new UploadDialog(mainFrame);
+ return dialog;
+ }
+
+ public ProgressDialog createProgressDialog(String title, String[] buttonCaptions,
+ String description, boolean useProgress) {
+ ProgressDialog dialog = new ProgressDialog(mainFrame, title, buttonCaptions, description,
+ useProgress);
+ return dialog;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/MainFrame.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/MainFrame.java
new file mode 100644
index 00000000..5bf68978
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/MainFrame.java
@@ -0,0 +1,370 @@
+package org.diylc.swing.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.Action;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.Icon;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButtonMenuItem;
+import javax.swing.SwingConstants;
+import javax.swing.SwingWorker;
+import javax.swing.Timer;
+
+import org.apache.log4j.Logger;
+import org.diylc.common.BadPositionException;
+import org.diylc.common.EventType;
+import org.diylc.common.IPlugIn;
+import org.diylc.common.IPlugInPort;
+import org.diylc.common.ITask;
+import org.diylc.common.PropertyWrapper;
+import org.diylc.core.IView;
+import org.diylc.images.IconLoader;
+import org.diylc.presenter.Presenter;
+import org.diylc.swing.ISwingUI;
+import org.diylc.swing.gui.editor.PropertyEditorDialog;
+import org.diylc.swing.plugins.autosave.AutoSavePlugin;
+import org.diylc.swing.plugins.canvas.CanvasPlugin;
+import org.diylc.swing.plugins.config.ConfigPlugin;
+import org.diylc.swing.plugins.edit.EditMenuPlugin;
+import org.diylc.swing.plugins.file.FileMenuPlugin;
+import org.diylc.swing.plugins.help.HelpMenuPlugin;
+import org.diylc.swing.plugins.layers.LayersMenuPlugin;
+import org.diylc.swing.plugins.statusbar.StatusBar;
+import org.diylc.swing.plugins.toolbox.ToolBox;
+
+import com.diyfever.gui.ButtonDialog;
+import com.diyfever.gui.miscutils.ConfigurationManager;
+
+public class MainFrame extends JFrame implements ISwingUI {
+
+ private static final Logger LOG = Logger.getLogger(MainFrame.class);
+
+ private static final long serialVersionUID = 1L;
+
+ private JPanel centerPanel;
+ private JPanel leftPanel;
+ private JPanel rightPanel;
+ private JPanel topPanel;
+ private JPanel bottomPanel;
+
+ private Presenter presenter;
+
+ private JMenuBar mainMenuBar;
+ private Map menuMap;
+ private Map buttonGroupMap;
+
+ private CanvasPlugin canvasPlugin;
+
+ public MainFrame() {
+ super("DIYLC 3");
+ setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+ setPreferredSize(new Dimension(800, 600));
+ createBasePanels();
+ menuMap = new HashMap();
+ buttonGroupMap = new HashMap();
+ setIconImages(Arrays.asList(IconLoader.IconSmall.getImage(), IconLoader.IconMedium
+ .getImage(), IconLoader.IconLarge.getImage()));
+ DialogFactory.getInstance().initialize(this);
+
+ this.presenter = new Presenter(this);
+
+ presenter.installPlugin(new ToolBox(this));
+ presenter.installPlugin(new FileMenuPlugin(this));
+ presenter.installPlugin(new EditMenuPlugin(this));
+ presenter.installPlugin(new ConfigPlugin(this));
+ presenter.installPlugin(new LayersMenuPlugin(this));
+ // presenter.installPlugin(new OnlineManager());
+ presenter.installPlugin(new HelpMenuPlugin(this));
+
+ presenter.installPlugin(new StatusBar(this));
+ canvasPlugin = new CanvasPlugin(this);
+ presenter.installPlugin(canvasPlugin);
+ presenter.installPlugin(new FramePlugin());
+
+ try {
+ File testFile = new File("test.tmp");
+ Writer out = new OutputStreamWriter(new FileOutputStream(testFile));
+ out.write("This is a test");
+ out.close();
+ testFile.delete();
+ presenter.installPlugin(new AutoSavePlugin(this));
+ } catch (Exception e) {
+ showMessage(
+ "The current user does not have permissions to access folder "
+ + new File(".").getAbsolutePath()
+ + ".\nAuto-save feature will not be available, contact your system administrator.",
+ "Warning", IView.WARNING_MESSAGE);
+ }
+
+ presenter.createNewProject();
+
+ addWindowListener(new WindowAdapter() {
+
+ @Override
+ public void windowClosed(WindowEvent e) {
+ if (presenter.allowFileAction()) {
+ ConfigurationManager.getInstance().writeValue(IPlugInPort.ABNORMAL_EXIT_KEY,
+ false);
+ dispose();
+ presenter.dispose();
+ System.exit(0);
+ }
+ }
+
+ @Override
+ public void windowClosing(WindowEvent e) {
+ if (presenter.allowFileAction()) {
+ ConfigurationManager.getInstance().writeValue(IPlugInPort.ABNORMAL_EXIT_KEY,
+ false);
+ dispose();
+ presenter.dispose();
+ System.exit(0);
+ }
+ }
+ });
+
+ // setGlassPane(new CustomGlassPane());
+ // getGlassPane().setVisible(true);
+ }
+
+ public Presenter getPresenter() {
+ return presenter;
+ }
+
+ @Override
+ public void setVisible(boolean b) {
+ super.setVisible(b);
+ // TODO: hack to prevent painting issues in the scroll bar rulers. Find
+ // a better fix if possible.
+ Timer timer = new Timer(500, new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ canvasPlugin.refresh();
+ }
+
+ });
+ timer.setRepeats(false);
+ timer.start();
+ // if (b) {
+ // SwingUtilities.invokeLater(new Runnable() {
+ // @Override
+ // public void run() {
+ //
+ // }
+ // });
+ // }
+ }
+
+ private void createBasePanels() {
+ Container c = new Container();
+ c.setLayout(new BorderLayout());
+
+ centerPanel = new JPanel(new BorderLayout());
+ c.add(centerPanel, BorderLayout.CENTER);
+
+ topPanel = new JPanel();
+ topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS));
+ c.add(topPanel, BorderLayout.NORTH);
+
+ leftPanel = new JPanel();
+ leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.X_AXIS));
+ c.add(leftPanel, BorderLayout.WEST);
+
+ bottomPanel = new JPanel();
+ bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.Y_AXIS));
+ c.add(bottomPanel, BorderLayout.SOUTH);
+
+ rightPanel = new JPanel();
+ rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.X_AXIS));
+ c.add(rightPanel, BorderLayout.EAST);
+
+ setContentPane(c);
+ }
+
+ private JMenuBar getMainMenuBar() {
+ if (mainMenuBar == null) {
+ mainMenuBar = new JMenuBar();
+ setJMenuBar(mainMenuBar);
+ }
+ return mainMenuBar;
+ }
+
+ // IView
+
+ @Override
+ public void showMessage(String message, String title, int messageType) {
+ JOptionPane.showMessageDialog(this, message, title, messageType);
+ }
+
+ @Override
+ public int showConfirmDialog(String message, String title, int optionType, int messageType) {
+ return JOptionPane.showConfirmDialog(this, message, title, optionType, messageType);
+ }
+
+ @Override
+ public boolean editProperties(List properties,
+ Set defaultedProperties) {
+ PropertyEditorDialog editor = DialogFactory.getInstance().createPropertyEditorDialog(
+ properties, "Edit Selection");
+ editor.setVisible(true);
+ defaultedProperties.addAll(editor.getDefaultedProperties());
+ return ButtonDialog.OK.equals(editor.getSelectedButtonCaption());
+ }
+
+ private JMenu findOrCreateMenu(String menuName) {
+ JMenu menu;
+ if (menuMap.containsKey(menuName)) {
+ menu = menuMap.get(menuName);
+ } else {
+ menu = new JMenu(menuName);
+ menuMap.put(menuName, menu);
+ getMainMenuBar().add(menu);
+ }
+ return menu;
+ }
+
+ @Override
+ public void injectGUIComponent(JComponent component, int position) throws BadPositionException {
+ LOG.info(String.format("injectGUIComponent(%s, %s)", component.getClass().getName(),
+ position));
+ switch (position) {
+ case SwingConstants.TOP:
+ topPanel.add(component);
+ break;
+ case SwingConstants.LEFT:
+ leftPanel.add(component);
+ break;
+ case SwingConstants.BOTTOM:
+ bottomPanel.add(component);
+ break;
+ case SwingConstants.RIGHT:
+ rightPanel.add(component);
+ break;
+ case SwingConstants.CENTER:
+ centerPanel.add(component, BorderLayout.CENTER);
+ break;
+ default:
+ throw new BadPositionException();
+ }
+ pack();
+ }
+
+ @Override
+ public void injectMenuAction(Action action, String menuName) {
+ LOG.info(String.format("injectMenuAction(%s, %s)", action == null ? "Separator" : action
+ .getValue(Action.NAME), menuName));
+ JMenu menu = findOrCreateMenu(menuName);
+ if (action == null) {
+ menu.addSeparator();
+ } else {
+ Boolean isCheckBox = (Boolean) action.getValue(IView.CHECK_BOX_MENU_ITEM);
+ String groupName = (String) action.getValue(IView.RADIO_BUTTON_GROUP_KEY);
+ if (isCheckBox != null && isCheckBox) {
+ menu.add(new JCheckBoxMenuItem(action));
+ } else if (groupName != null) {
+ ButtonGroup group;
+ if (buttonGroupMap.containsKey(groupName)) {
+ group = buttonGroupMap.get(groupName);
+ } else {
+ group = new ButtonGroup();
+ buttonGroupMap.put(groupName, group);
+ }
+ JRadioButtonMenuItem item = new JRadioButtonMenuItem(action);
+ group.add(item);
+ menu.add(item);
+ } else {
+ menu.add(action);
+ }
+ }
+ }
+
+ @Override
+ public void injectSubmenu(String name, Icon icon, String parentMenuName) {
+ LOG.info(String.format("injectSubmenu(%s, icon, %s)", name, parentMenuName));
+ JMenu menu = findOrCreateMenu(parentMenuName);
+ JMenu submenu = new JMenu(name);
+ submenu.setIcon(icon);
+ menu.add(submenu);
+ menuMap.put(name, submenu);
+ }
+
+ @Override
+ public void executeBackgroundTask(final ITask task) {
+ setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ SwingWorker worker = new SwingWorker() {
+
+ @Override
+ protected T doInBackground() throws Exception {
+ return task.doInBackground();
+ }
+
+ @Override
+ protected void done() {
+ try {
+ T result = get();
+ task.complete(result);
+ } catch (Exception e) {
+ LOG.error("Task failed", e);
+ task.failed(e);
+ }
+ setCursor(Cursor.getDefaultCursor());
+ }
+ };
+ worker.execute();
+ }
+
+ class FramePlugin implements IPlugIn {
+
+ private IPlugInPort plugInPort;
+
+ @Override
+ public void connect(IPlugInPort plugInPort) {
+ this.plugInPort = plugInPort;
+ }
+
+ @Override
+ public EnumSet getSubscribedEventTypes() {
+ return EnumSet.of(EventType.FILE_STATUS_CHANGED);
+ }
+
+ @Override
+ public void processMessage(EventType eventType, Object... params) {
+ if (eventType == EventType.FILE_STATUS_CHANGED) {
+ String fileName = (String) params[0];
+ if (fileName == null) {
+ fileName = "Untitled";
+ }
+ String modified = (Boolean) params[1] ? " (modified)" : "";
+ setTitle(String.format("DIYLC %s beta - %s %s", plugInPort
+ .getCurrentVersionNumber(), fileName, modified));
+ }
+ }
+ }
+
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/TemplateDialog.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/TemplateDialog.java
new file mode 100644
index 00000000..385a4184
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/TemplateDialog.java
@@ -0,0 +1,285 @@
+package org.diylc.swing.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.BorderFactory;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import org.apache.log4j.Logger;
+import org.diylc.common.DrawOption;
+import org.diylc.common.IPlugInPort;
+import org.diylc.common.PropertyWrapper;
+import org.diylc.core.IView;
+import org.diylc.presenter.Presenter;
+
+import com.diyfever.gui.miscutils.ConfigurationManager;
+
+public class TemplateDialog extends JDialog {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final Logger LOG = Logger.getLogger(TemplateDialog.class);
+
+ private static final Dimension panelSize = new Dimension(400, 300);
+ public static final String SHOW_TEMPLATES_KEY = "showTemplatesAtStartup";
+
+ private IPlugInPort plugInPort;
+
+ private JList fileList;
+ private JPanel canvasPanel;
+ private Presenter presenter;
+ private List files;
+ private JCheckBox showTemplatesBox;
+ private JPanel mainPanel;
+ private JButton loadButton;
+ private JPanel infoPanel;
+
+ public TemplateDialog(JFrame owner, IPlugInPort plugInPort) {
+ super(owner, "Templates");
+ setModal(true);
+ setResizable(false);
+ this.plugInPort = plugInPort;
+ this.presenter = new Presenter(new IView() {
+
+ @Override
+ public int showConfirmDialog(String message, String title, int optionType,
+ int messageType) {
+ return JOptionPane.showConfirmDialog(TemplateDialog.this, message, title,
+ optionType, messageType);
+ }
+
+ @Override
+ public void showMessage(String message, String title, int messageType) {
+ JOptionPane.showMessageDialog(TemplateDialog.this, message, title, messageType);
+ }
+
+ @Override
+ public boolean editProperties(List properties, Set defaultedProperties) {
+ return false;
+ }
+ });
+ // this.presenter.installPlugin(new IPlugIn() {
+ //
+ // @Override
+ // public void connect(IPlugInPort plugInPort) {
+ // }
+ //
+ // @Override
+ // public EnumSet getSubscribedEventTypes() {
+ // return EnumSet.of(EventType.REPAINT);
+ // }
+ //
+ // @Override
+ // public void processMessage(EventType eventType, Object... params) {
+ // if (eventType == EventType.REPAINT) {
+ // getCanvasPanel().repaint();
+ // }
+ // }
+ // });
+ setContentPane(getMainPanel());
+ pack();
+ setLocationRelativeTo(owner);
+ }
+
+ public JPanel getMainPanel() {
+ if (mainPanel == null) {
+ mainPanel = new JPanel(new GridBagLayout());
+ mainPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+
+ GridBagConstraints c = new GridBagConstraints();
+ c.gridx = 0;
+ c.gridy = 0;
+ c.weightx = 0;
+ c.weighty = 1;
+ c.weightx = 1;
+ c.gridwidth = 2;
+ c.fill = GridBagConstraints.BOTH;
+ c.insets = new Insets(0, 0, 4, 0);
+ mainPanel.add(getInfoPanel(), c);
+
+ c.gridy = 1;
+ c.weightx = 0;
+ c.gridwidth = 1;
+ c.insets = new Insets(0, 0, 0, 0);
+ JScrollPane scrollPane = new JScrollPane(getFileList());
+ mainPanel.add(scrollPane, c);
+
+ c.gridx = 1;
+ c.weightx = 1;
+
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.setBorder(scrollPane.getBorder());
+ panel.add(getCanvasPanel(), BorderLayout.CENTER);
+ mainPanel.add(panel, c);
+
+ c.gridx = 0;
+ c.gridy = 2;
+ c.weightx = 0;
+ c.weighty = 0;
+ c.anchor = GridBagConstraints.LAST_LINE_START;
+ c.fill = GridBagConstraints.NONE;
+ mainPanel.add(getShowTemplatesBox(), c);
+
+ c.gridx = 1;
+ c.anchor = GridBagConstraints.LAST_LINE_END;
+ c.insets = new Insets(4, 0, 0, 0);
+ mainPanel.add(getLoadButton(), c);
+ }
+ return mainPanel;
+ }
+
+ public JButton getLoadButton() {
+ if (loadButton == null) {
+ loadButton = new JButton("Load Template");
+ loadButton.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ plugInPort.loadProject(presenter.getCurrentProject(), true);
+ dispose();
+ }
+ });
+ }
+ return loadButton;
+ }
+
+ public JPanel getInfoPanel() {
+ if (infoPanel == null) {
+ infoPanel = new JPanel();
+ infoPanel.setBackground(Color.decode("#FFFFCC"));
+ infoPanel.setBorder(new JTextField().getBorder());
+ JLabel infoLabel = new JLabel(
+ "Templates are semi-product layouts that are used as a starting point for your projects.
"
+ + "Pick a templete in the left list and click \"Load Template\" to load it or close this dialog to continue.
"
+ + "You can create your own templates by placing a DIY file into diylc/templates directory.");
+ infoLabel.setOpaque(false);
+ infoPanel.add(infoLabel);
+ }
+ return infoPanel;
+ }
+
+ public JList getFileList() {
+ if (fileList == null) {
+ fileList = new JList(getFiles().toArray());
+ fileList.setPreferredSize(new Dimension(128, -1));
+ fileList.setCellRenderer(new DefaultListCellRenderer() {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Component getListCellRendererComponent(JList list, Object value, int index,
+ boolean isSelected, boolean cellHasFocus) {
+ JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index,
+ isSelected, cellHasFocus);
+ if (value instanceof File) {
+ label.setText(((File) value).getName());
+ }
+ return label;
+ }
+
+ });
+ fileList.addListSelectionListener(new ListSelectionListener() {
+
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ File file = (File) fileList.getSelectedValue();
+ if (file != null) {
+ presenter.loadProjectFromFile(file.getAbsolutePath());
+ Dimension dim = presenter.getCanvasDimensions(true);
+ double xFactor = panelSize.getWidth() / dim.getWidth();
+ double yFactor = panelSize.getHeight() / dim.getHeight();
+ presenter.setZoomLevel(Math.min(xFactor, yFactor));
+ getCanvasPanel().repaint();
+ }
+ }
+ });
+ fileList.setSelectedIndex(0);
+ }
+ return fileList;
+ }
+
+ public JCheckBox getShowTemplatesBox() {
+ if (showTemplatesBox == null) {
+ showTemplatesBox = new JCheckBox("Show this dialog at startup");
+ showTemplatesBox.setSelected(true);
+ showTemplatesBox.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ ConfigurationManager.getInstance().writeValue(SHOW_TEMPLATES_KEY,
+ showTemplatesBox.isSelected());
+ }
+ });
+ }
+ return showTemplatesBox;
+ }
+
+ public List getFiles() {
+ if (files == null) {
+ files = new ArrayList();
+ File dir = new File("templates");
+ if (dir.exists()) {
+ for (File f : dir.listFiles()) {
+ if (f.isFile() && f.getName().toLowerCase().endsWith(".diy")) {
+ files.add(f);
+ }
+ }
+ }
+ LOG.debug("Found " + files.size() + " templates");
+ }
+ return files;
+ }
+
+ public JPanel getCanvasPanel() {
+ if (canvasPanel == null) {
+ canvasPanel = new JPanel() {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void paint(Graphics g) {
+ super.paint(g);
+ // Graphics2D g2d = (Graphics2D) g;
+ // AffineTransform transform = g2d.getTransform();
+ presenter.draw((Graphics2D) g, EnumSet.of(DrawOption.ZOOM,
+ DrawOption.ANTIALIASING), null);
+ // g2d.setTransform(transform);
+ // Rectangle rect = canvasPanel.getBounds();
+ // BorderFactory.createEtchedBorder().paintBorder(canvasPanel,
+ // g, 0, 0,
+ // (int) rect.getWidth(), (int) rect.getHeight());
+ }
+ };
+ canvasPanel.setBackground(Color.white);
+ canvasPanel.setPreferredSize(panelSize);
+ }
+ return canvasPanel;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/components/OverwritePromptFileChooser.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/components/OverwritePromptFileChooser.java
new file mode 100644
index 00000000..514d47eb
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/components/OverwritePromptFileChooser.java
@@ -0,0 +1,38 @@
+package org.diylc.swing.gui.components;
+
+import java.io.File;
+
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.filechooser.FileFilter;
+
+public class OverwritePromptFileChooser extends JFileChooser {
+
+ private static final long serialVersionUID = 1L;
+ private String defaultExtension;
+
+ public void approveSelection() {
+ File lSelectedFile;
+ if (getSelectedFile().getAbsolutePath().contains(".")) {
+ lSelectedFile = getSelectedFile();
+ } else {
+ lSelectedFile = new File(getSelectedFile().getAbsoluteFile() + "." + defaultExtension);
+ }
+
+ if (lSelectedFile != null && lSelectedFile.exists()) {
+ int lResponse = JOptionPane.showConfirmDialog(this, lSelectedFile.getAbsolutePath()
+ + " already exists.\nDo you " + "want to replace it?", "Warning",
+ JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
+
+ if (lResponse != JOptionPane.YES_OPTION) {
+ return;
+ }
+ }
+ super.approveSelection();
+ }
+
+ public void setFileFilter(FileFilter filter, String defaultExtension) {
+ this.defaultExtension = defaultExtension;
+ super.setFileFilter(filter);
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/BooleanEditor.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/BooleanEditor.java
new file mode 100644
index 00000000..c8842b26
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/BooleanEditor.java
@@ -0,0 +1,34 @@
+package org.diylc.swing.gui.editor;
+
+import java.awt.Color;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JCheckBox;
+
+import org.diylc.common.PropertyWrapper;
+import org.diylc.utils.Constants;
+
+public class BooleanEditor extends JCheckBox {
+
+ private static final long serialVersionUID = 1L;
+
+ private Color oldBg = getBackground();
+
+ public BooleanEditor(final PropertyWrapper property) {
+ super();
+ setSelected((Boolean) property.getValue());
+ addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ property.setChanged(true);
+ setBackground(oldBg);
+ property.setValue(isSelected());
+ }
+ });
+ if (!property.isUnique()) {
+ setBackground(Constants.MULTI_VALUE_COLOR);
+ }
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/ByteEditor.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/ByteEditor.java
new file mode 100644
index 00000000..05d72207
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/ByteEditor.java
@@ -0,0 +1,36 @@
+package org.diylc.swing.gui.editor;
+
+import java.awt.Color;
+
+import javax.swing.JSlider;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.diylc.common.PropertyWrapper;
+import org.diylc.utils.Constants;
+
+public class ByteEditor extends JSlider {
+
+ private static final long serialVersionUID = 1L;
+
+ private Color oldBg = getBackground();
+
+ public ByteEditor(final PropertyWrapper property) {
+ super();
+ setMinimum(0);
+ setMaximum(127);
+ setValue((Byte) property.getValue());
+ addChangeListener(new ChangeListener() {
+
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ property.setChanged(true);
+ setBackground(oldBg);
+ property.setValue(new Integer(getValue()).byteValue());
+ }
+ });
+ if (!property.isUnique()) {
+ setBackground(Constants.MULTI_VALUE_COLOR);
+ }
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/ColorEditor.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/ColorEditor.java
new file mode 100644
index 00000000..24e16ef2
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/ColorEditor.java
@@ -0,0 +1,52 @@
+package org.diylc.swing.gui.editor;
+
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import javax.swing.BorderFactory;
+import javax.swing.JColorChooser;
+import javax.swing.JLabel;
+import javax.swing.SwingConstants;
+
+import org.diylc.common.PropertyWrapper;
+
+public class ColorEditor extends JLabel {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String title = "Click to edit";
+
+ public ColorEditor(final PropertyWrapper property) {
+ super(property.isUnique() ? title : ("(multi value) " + title));
+ setOpaque(true);
+ setHorizontalAlignment(SwingConstants.CENTER);
+ setBorder(BorderFactory.createEtchedBorder());
+ setBackground((Color) property.getValue());
+ setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ addMouseListener(new MouseAdapter() {
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ Color newColor = JColorChooser.showDialog(ColorEditor.this, "Choose Color",
+ getBackground());
+ if (newColor != null) {
+ property.setChanged(true);
+ property.setValue(newColor);
+ setBackground(newColor);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void setBackground(Color bg) {
+ if (bg.getRed() < 127 || bg.getBlue() < 127 || bg.getGreen() < 127) {
+ setForeground(Color.white);
+ } else {
+ setForeground(Color.black);
+ }
+ super.setBackground(bg);
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/EnumEditor.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/EnumEditor.java
new file mode 100644
index 00000000..d53bf2c6
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/EnumEditor.java
@@ -0,0 +1,41 @@
+package org.diylc.swing.gui.editor;
+
+import java.awt.Color;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JComboBox;
+
+import org.diylc.common.PropertyWrapper;
+import org.diylc.utils.Constants;
+
+public class EnumEditor extends JComboBox {
+
+ private static final long serialVersionUID = 1L;
+
+ private Color oldBg = getBackground();
+
+ private final PropertyWrapper property;
+
+ public EnumEditor(final PropertyWrapper property) {
+ this.property = property;
+ Object[] values = property.getType().getEnumConstants();
+ setModel(new DefaultComboBoxModel(values));
+ setSelectedItem(property.getValue());
+ addItemListener(new ItemListener() {
+
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ if (e.getStateChange() == ItemEvent.SELECTED) {
+ property.setChanged(true);
+ setBackground(oldBg);
+ EnumEditor.this.property.setValue(e.getItem());
+ }
+ }
+ });
+ if (!property.isUnique()) {
+ setBackground(Constants.MULTI_VALUE_COLOR);
+ }
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/FieldEditorFactory.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/FieldEditorFactory.java
new file mode 100644
index 00000000..1eca8238
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/FieldEditorFactory.java
@@ -0,0 +1,61 @@
+package org.diylc.swing.gui.editor;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+
+import org.apache.log4j.Logger;
+import org.diylc.common.PropertyWrapper;
+import org.diylc.core.measures.AbstractMeasure;
+
+public class FieldEditorFactory {
+
+ private static final Logger LOG = Logger.getLogger(FieldEditorFactory.class);
+
+ public static Component createFieldEditor(PropertyWrapper property) {
+ if (property.getType().equals(String.class)) {
+ StringEditor editor = new StringEditor(property);
+ return editor;
+ }
+ if (property.getType().equals(Color.class)) {
+ ColorEditor editor = new ColorEditor(property);
+ return editor;
+ }
+ if (AbstractMeasure.class.isAssignableFrom(property.getType())) {
+ MeasureEditor editor = new MeasureEditor(property);
+ return editor;
+ }
+ if (ImageIcon.class.isAssignableFrom(property.getType())) {
+ ImageEditor editor = new ImageEditor(property);
+ return editor;
+ }
+ if (property.getType().isEnum()) {
+ EnumEditor editor = new EnumEditor(property);
+ return editor;
+ }
+ if (Byte.class.isAssignableFrom(property.getType())
+ || byte.class.isAssignableFrom(property.getType())) {
+ ByteEditor editor = new ByteEditor(property);
+ return editor;
+ }
+ if (Boolean.class.isAssignableFrom(property.getType())
+ || boolean.class.isAssignableFrom(property.getType())) {
+ BooleanEditor editor = new BooleanEditor(property);
+ return editor;
+ }
+ if (Font.class.isAssignableFrom(property.getType())) {
+ FontEditor editor = new FontEditor(property);
+ return editor;
+ }
+ if (Integer.class.isAssignableFrom(property.getType())
+ || int.class.isAssignableFrom(property.getType())) {
+ IntEditor editor = new IntEditor(property);
+ return editor;
+ }
+ LOG.error("Unrecognized parameter type: " + property.getType().getName());
+ return new JLabel("Unrecognized");
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/FontEditor.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/FontEditor.java
new file mode 100644
index 00000000..64471349
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/FontEditor.java
@@ -0,0 +1,42 @@
+package org.diylc.swing.gui.editor;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+import org.diylc.common.PropertyWrapper;
+import org.diylc.utils.Constants;
+
+import com.diyfever.gui.FontChooserComboBox;
+
+public class FontEditor extends FontChooserComboBox {
+
+ private static final long serialVersionUID = 1L;
+
+ private Color oldBg = getBackground();
+
+ private final PropertyWrapper property;
+
+ public FontEditor(final PropertyWrapper property) {
+ this.property = property;
+ setSelectedItem(((Font) property.getValue()).getName());
+ addItemListener(new ItemListener() {
+
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ if (e.getStateChange() == ItemEvent.SELECTED) {
+ property.setChanged(true);
+ setBackground(oldBg);
+ Font oldFont = (Font) FontEditor.this.property.getValue();
+ Font newFont = new Font(getSelectedItem().toString(), oldFont.getStyle(),
+ oldFont.getSize());
+ FontEditor.this.property.setValue(newFont);
+ }
+ }
+ });
+ if (!property.isUnique()) {
+ setBackground(Constants.MULTI_VALUE_COLOR);
+ }
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/ImageEditor.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/ImageEditor.java
new file mode 100644
index 00000000..11bdb574
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/ImageEditor.java
@@ -0,0 +1,46 @@
+package org.diylc.swing.gui.editor;
+
+import java.awt.event.ActionListener;
+import java.io.File;
+
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+
+import org.diylc.common.PropertyWrapper;
+import org.diylc.swing.gui.DialogFactory;
+import org.diylc.swing.plugins.file.FileFilterEnum;
+
+public class ImageEditor extends JButton {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String title = "Click to load image file";
+
+ public ImageEditor(final PropertyWrapper property) {
+ super(property.isUnique() ? title : ("(multi value) " + title));
+ addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent e) {
+ File file = DialogFactory.getInstance().showOpenDialog(
+ FileFilterEnum.IMAGES.getFilter(), null,
+ FileFilterEnum.IMAGES.getExtensions()[0], null);
+ if (file != null) {
+ property.setChanged(true);
+ ImageIcon image = new ImageIcon(file.getAbsolutePath());
+ property.setValue(image);
+ }
+ }
+ });
+ }
+
+ // @Override
+ // public void setBackground(Color bg) {
+ // if (bg.getRed() < 127 || bg.getBlue() < 127 || bg.getGreen() < 127) {
+ // setForeground(Color.white);
+ // } else {
+ // setForeground(Color.black);
+ // }
+ // super.setBackground(bg);
+ // }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/IntEditor.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/IntEditor.java
new file mode 100644
index 00000000..919c7949
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/IntEditor.java
@@ -0,0 +1,35 @@
+package org.diylc.swing.gui.editor;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import org.diylc.common.PropertyWrapper;
+import org.diylc.utils.Constants;
+
+import com.diyfever.gui.DoubleTextField;
+
+public class IntEditor extends DoubleTextField {
+
+ private static final long serialVersionUID = 1L;
+
+ private Color oldBg = getBackground();
+
+ public IntEditor(final PropertyWrapper property) {
+ setLayout(new BorderLayout());
+ setValue((double) (Integer) property.getValue());
+ addPropertyChangeListener(DoubleTextField.VALUE_PROPERTY, new PropertyChangeListener() {
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ property.setChanged(true);
+ setBackground(oldBg);
+ property.setValue(((Double) evt.getNewValue()).intValue());
+ }
+ });
+ if (!property.isUnique()) {
+ setBackground(Constants.MULTI_VALUE_COLOR);
+ }
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/MeasureEditor.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/MeasureEditor.java
new file mode 100644
index 00000000..c16d6bc2
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/MeasureEditor.java
@@ -0,0 +1,97 @@
+package org.diylc.swing.gui.editor;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import javax.swing.JComboBox;
+
+import org.diylc.common.PropertyWrapper;
+import org.diylc.core.measures.AbstractMeasure;
+import org.diylc.core.measures.Unit;
+import org.diylc.utils.Constants;
+
+import com.diyfever.gui.DoubleTextField;
+
+public class MeasureEditor extends Container {
+
+ private static final long serialVersionUID = 1L;
+
+ private Color oldBg;
+ private DoubleTextField valueField;
+ private JComboBox unitBox;
+
+ @SuppressWarnings("unchecked")
+ public MeasureEditor(final PropertyWrapper property) {
+ setLayout(new BorderLayout());
+ AbstractMeasure measure = ((AbstractMeasure) property.getValue());
+ valueField = new DoubleTextField(measure.getValue());
+ oldBg = valueField.getBackground();
+ valueField.addPropertyChangeListener(DoubleTextField.VALUE_PROPERTY,
+ new PropertyChangeListener() {
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ try {
+ AbstractMeasure> newMeasure = (AbstractMeasure>) ((AbstractMeasure) property
+ .getValue()).clone();
+ newMeasure.setValue((Double) evt.getNewValue());
+ property.setValue(newMeasure);
+ property.setChanged(true);
+ valueField.setBackground(oldBg);
+ unitBox.setBackground(oldBg);
+ } catch (CloneNotSupportedException ex) {
+ }
+ }
+ });
+ add(valueField, BorderLayout.CENTER);
+ try {
+ Method method = measure.getUnit().getClass().getMethod("values");
+ unitBox = new JComboBox((Object[]) method.invoke(measure.getUnit()));
+ unitBox.setSelectedItem(measure.getUnit());
+ unitBox.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ try {
+ AbstractMeasure> newMeasure = (AbstractMeasure>) ((AbstractMeasure) property
+ .getValue()).clone();
+ newMeasure.setUnit((Enum extends Unit>) unitBox.getSelectedItem());
+ property.setValue(newMeasure);
+ property.setChanged(true);
+ valueField.setBackground(oldBg);
+ unitBox.setBackground(oldBg);
+ } catch (CloneNotSupportedException ex) {
+ }
+ }
+ });
+ add(unitBox, BorderLayout.EAST);
+
+ if (!property.isUnique()) {
+ valueField.setBackground(Constants.MULTI_VALUE_COLOR);
+ unitBox.setBackground(Constants.MULTI_VALUE_COLOR);
+ }
+ } catch (IllegalArgumentException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (SecurityException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (NoSuchMethodException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/PropertyEditorDialog.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/PropertyEditorDialog.java
new file mode 100644
index 00000000..b8242a71
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/PropertyEditorDialog.java
@@ -0,0 +1,170 @@
+package org.diylc.swing.gui.editor;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.BorderFactory;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import org.apache.log4j.Logger;
+import org.diylc.common.PropertyWrapper;
+import org.diylc.core.ValidationException;
+
+import com.diyfever.gui.ButtonDialog;
+
+public class PropertyEditorDialog extends ButtonDialog {
+
+ private static final Logger LOG = Logger.getLogger(PropertyEditorDialog.class);
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String DEFAULT_BOX_TOOLTIP = "If this box is checked application will use the current value as a
default when creating new components of the same type";
+
+ private List properties;
+ private Set defaultedProperties;
+
+ public PropertyEditorDialog(JFrame owner, List properties, String title) {
+ super(owner, title, new String[] { ButtonDialog.OK, ButtonDialog.CANCEL });
+
+ LOG.debug("Creating property editor for: " + properties);
+
+ this.properties = properties;
+ this.defaultedProperties = new HashSet();
+
+ setMinimumSize(new Dimension(240, 40));
+
+ layoutGui();
+ setLocationRelativeTo(owner);
+ //
+ // setModal(true);
+ //
+ // JPanel holderPanel = new JPanel();
+ // holderPanel.setLayout(new BorderLayout());
+ // holderPanel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
+ //
+ // holderPanel.add(createEditorFields(), BorderLayout.CENTER);
+ // holderPanel.add(createButtonPanel(), BorderLayout.SOUTH);
+ //
+ // setContentPane(holderPanel);
+ //
+ //
+ // pack();
+ // setLocationRelativeTo(owner);
+ }
+
+ @Override
+ protected boolean validateInput(String button) {
+ if (button.equals(ButtonDialog.OK)) {
+ for (PropertyWrapper property : properties) {
+ try {
+ property.getValidator().validate(property.getValue());
+ } catch (ValidationException ve) {
+ JOptionPane.showMessageDialog(PropertyEditorDialog.this, "Input error for \""
+ + property.getName() + "\": " + ve.getMessage(), "Error",
+ JOptionPane.ERROR_MESSAGE);
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private JPanel createEditorFields() {
+ JPanel editorPanel = new JPanel(new GridBagLayout());
+ editorPanel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
+
+ GridBagConstraints gbc = new GridBagConstraints();
+
+ gbc.gridy = 0;
+ gbc.insets = new Insets(2, 2, 2, 2);
+ gbc.anchor = GridBagConstraints.LINE_START;
+
+ for (PropertyWrapper property : properties) {
+ gbc.gridx = 0;
+ gbc.fill = GridBagConstraints.NONE;
+ gbc.weightx = 0;
+
+ editorPanel.add(new JLabel(property.getName() + ": "), gbc);
+
+ gbc.gridx = 1;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ gbc.weightx = 1;
+
+ Component editor = FieldEditorFactory.createFieldEditor(property);
+ editor.addKeyListener(new KeyAdapter() {
+
+ @Override
+ public void keyPressed(KeyEvent e) {
+ if (e.getKeyCode() == KeyEvent.VK_ENTER) {
+ getButton(OK).doClick();
+ } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+ getButton(CANCEL).doClick();
+ }
+ }
+ });
+ editorPanel.add(editor, gbc);
+
+ if (property.isDefaultable()) {
+ gbc.gridx = 2;
+ gbc.fill = GridBagConstraints.NONE;
+ gbc.weightx = 0;
+
+ editorPanel.add(createDefaultCheckBox(property), gbc);
+ }
+
+ gbc.gridy++;
+ }
+
+ return editorPanel;
+ }
+
+ private JCheckBox createDefaultCheckBox(final PropertyWrapper property) {
+ final JCheckBox checkBox = new JCheckBox();
+ checkBox.setToolTipText(DEFAULT_BOX_TOOLTIP);
+ checkBox.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (checkBox.isSelected()) {
+ defaultedProperties.add(property);
+ } else {
+ defaultedProperties.remove(property);
+ }
+ }
+ });
+ return checkBox;
+ }
+
+ public Set getDefaultedProperties() {
+ return defaultedProperties;
+ }
+
+ public static boolean showFor(JFrame owner, List properties, String title) {
+ PropertyEditorDialog editor = new PropertyEditorDialog(owner, properties, title);
+ editor.setVisible(true);
+ if (OK.equals(editor.getSelectedButtonCaption())) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected JComponent getMainComponent() {
+ return createEditorFields();
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/StringEditor.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/StringEditor.java
new file mode 100644
index 00000000..3dbf9e16
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/gui/editor/StringEditor.java
@@ -0,0 +1,50 @@
+package org.diylc.swing.gui.editor;
+
+import java.awt.Color;
+
+import javax.swing.JTextField;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+
+import org.diylc.common.PropertyWrapper;
+import org.diylc.utils.Constants;
+
+public class StringEditor extends JTextField {
+
+ private static final long serialVersionUID = 1L;
+
+ private Color oldBg = getBackground();
+
+ private final PropertyWrapper property;
+
+ public StringEditor(PropertyWrapper property) {
+ super(property.getValue() == null ? "" : (String) property.getValue());
+ this.property = property;
+ getDocument().addDocumentListener(new DocumentListener() {
+
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ textChanged();
+ }
+
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ textChanged();
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ textChanged();
+ }
+ });
+ if (!property.isUnique()) {
+ setBackground(Constants.MULTI_VALUE_COLOR);
+ }
+ }
+
+ private void textChanged() {
+ property.setChanged(true);
+ setBackground(oldBg);
+ property.setValue(getText());
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/autosave/AutoSavePlugin.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/autosave/AutoSavePlugin.java
new file mode 100644
index 00000000..d622272d
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/autosave/AutoSavePlugin.java
@@ -0,0 +1,73 @@
+package org.diylc.swing.plugins.autosave;
+
+import java.io.File;
+import java.util.EnumSet;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.swing.SwingUtilities;
+
+import org.diylc.common.EventType;
+import org.diylc.common.IPlugIn;
+import org.diylc.common.IPlugInPort;
+import org.diylc.core.IView;
+
+import com.diyfever.gui.miscutils.ConfigurationManager;
+
+public class AutoSavePlugin implements IPlugIn {
+
+ private static final String AUTO_SAVE_FILE_NAME = "autoSave.diy";
+
+ private ExecutorService executor;
+
+ private IPlugInPort plugInPort;
+ private IView view;
+
+ public AutoSavePlugin(IView view) {
+ this.view = view;
+ executor = Executors.newSingleThreadExecutor();
+ }
+
+ @Override
+ public void connect(IPlugInPort plugInPort) {
+ this.plugInPort = plugInPort;
+ SwingUtilities.invokeLater(new Runnable() {
+
+ @Override
+ public void run() {
+ boolean wasAbnormal = ConfigurationManager.getInstance().readBoolean(
+ IPlugInPort.ABNORMAL_EXIT_KEY, false);
+ if (wasAbnormal && new File(AUTO_SAVE_FILE_NAME).exists()) {
+ int decision = view
+ .showConfirmDialog(
+ "It appears that aplication was not closed normally in the previous session. Do you want to open the last auto-saved file?",
+ "Auto-Save", IView.YES_NO_OPTION, IView.QUESTION_MESSAGE);
+ if (decision == IView.YES_OPTION) {
+ AutoSavePlugin.this.plugInPort.loadProjectFromFile(AUTO_SAVE_FILE_NAME);
+ }
+ }
+ // Set abnormal flag to true, GUI side of the app must flip to
+ // false when app closes regularly.
+ ConfigurationManager.getInstance().writeValue(IPlugInPort.ABNORMAL_EXIT_KEY, true);
+ }
+ });
+ }
+
+ @Override
+ public EnumSet getSubscribedEventTypes() {
+ return EnumSet.of(EventType.PROJECT_MODIFIED);
+ }
+
+ @Override
+ public void processMessage(EventType eventType, Object... params) {
+ if (eventType == EventType.PROJECT_MODIFIED) {
+ executor.execute(new Runnable() {
+
+ @Override
+ public void run() {
+ plugInPort.saveProjectToFile(AUTO_SAVE_FILE_NAME, true);
+ }
+ });
+ }
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/CanvasGestureListener.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/CanvasGestureListener.java
new file mode 100644
index 00000000..f8273c2a
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/CanvasGestureListener.java
@@ -0,0 +1,29 @@
+package org.diylc.swing.plugins.canvas;
+
+import java.awt.dnd.DragGestureEvent;
+import java.awt.dnd.DragGestureListener;
+
+import org.diylc.common.IPlugInPort;
+
+/**
+ * {@link DragGestureListener} for {@link CanvasPanel}.
+ *
+ * @author Branislav Stojkovic
+ */
+class CanvasGestureListener implements DragGestureListener {
+
+ private IPlugInPort presenter;
+
+ public CanvasGestureListener(IPlugInPort presenter) {
+ super();
+ this.presenter = presenter;
+ }
+
+ @Override
+ public void dragGestureRecognized(DragGestureEvent dge) {
+ presenter.dragStarted(dge.getDragOrigin(), dge.getTriggerEvent().isControlDown(), dge
+ .getTriggerEvent().isShiftDown(), dge.getTriggerEvent().isAltDown());
+ dge.startDrag(presenter.getCursorAt(dge.getDragOrigin()), new EmptyTransferable(),
+ new CanvasSourceListener(presenter));
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/CanvasPanel.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/CanvasPanel.java
new file mode 100644
index 00000000..83e8b9d9
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/CanvasPanel.java
@@ -0,0 +1,210 @@
+package org.diylc.swing.plugins.canvas;
+
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.awt.Image;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.dnd.Autoscroll;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DragSource;
+import java.awt.dnd.DropTarget;
+import java.awt.event.ActionEvent;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.image.VolatileImage;
+import java.util.EnumSet;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.JComponent;
+import javax.swing.KeyStroke;
+
+import org.diylc.common.DrawOption;
+import org.diylc.common.IPlugInPort;
+
+import com.diyfever.gui.miscutils.ConfigurationManager;
+
+/**
+ * GUI class used to draw onto.
+ *
+ * @author Branislav Stojkovic
+ */
+class CanvasPanel extends JComponent implements Autoscroll {
+
+ private static final long serialVersionUID = 1L;
+
+ private IPlugInPort plugInPort;
+
+ private Image bufferImage;
+ private GraphicsConfiguration screenGraphicsConfiguration;
+
+ private static boolean USE_HARDWARE_ACCELLERATION = false;
+
+// static final EnumSet DRAW_OPTIONS = EnumSet.of(DrawOption.GRID,
+// DrawOption.SELECTION, DrawOption.ZOOM, DrawOption.CONTROL_POINTS);
+// static final EnumSet DRAW_OPTIONS_ANTI_ALIASING = EnumSet.of(DrawOption.GRID,
+// DrawOption.SELECTION, DrawOption.ZOOM, DrawOption.ANTIALIASING,
+// DrawOption.CONTROL_POINTS);
+
+ public CanvasPanel(IPlugInPort plugInPort) {
+ super();
+ this.plugInPort = plugInPort;
+ setFocusable(true);
+ initializeListeners();
+ initializeDnD();
+ GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
+ GraphicsDevice[] devices = graphicsEnvironment.getScreenDevices();
+ screenGraphicsConfiguration = devices[0].getDefaultConfiguration();
+
+ initializeActions();
+ }
+
+ public void invalidateCache() {
+ bufferImage = null;
+ }
+
+ private void initializeDnD() {
+ // Initialize drag source recognizer.
+ DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(this,
+ DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK,
+ new CanvasGestureListener(plugInPort));
+ // Initialize drop target.
+ new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE,
+ new CanvasTargetListener(plugInPort), true);
+ }
+
+ private void initializeActions() {
+ getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
+ KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "clearSlot");
+ getActionMap().put("clearSlot", new AbstractAction() {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ CanvasPanel.this.plugInPort.setNewComponentTypeSlot(null);
+ }
+ });
+ }
+
+ protected void createBufferImage() {
+ if (USE_HARDWARE_ACCELLERATION) {
+ bufferImage = screenGraphicsConfiguration.createCompatibleVolatileImage(getWidth(),
+ getHeight());
+ ((VolatileImage) bufferImage).validate(screenGraphicsConfiguration);
+ } else {
+ bufferImage = createImage(getWidth(), getHeight());
+ }
+ }
+
+ @Override
+ public void paint(Graphics g) {
+ if (plugInPort == null) {
+ return;
+ }
+ if (bufferImage == null) {
+ createBufferImage();
+ }
+ Graphics2D g2d = (Graphics2D) bufferImage.getGraphics();
+ g2d.setClip(getVisibleRect());
+ Set drawOptions = EnumSet.of(DrawOption.GRID, DrawOption.SELECTION,
+ DrawOption.ZOOM, DrawOption.CONTROL_POINTS);
+ if (ConfigurationManager.getInstance().readBoolean(IPlugInPort.ANTI_ALIASING_KEY, true)) {
+ drawOptions.add(DrawOption.ANTIALIASING);
+ }
+ if (ConfigurationManager.getInstance().readBoolean(IPlugInPort.OUTLINE_KEY, false)) {
+ drawOptions.add(DrawOption.OUTLINE_MODE);
+ }
+ plugInPort.draw(g2d, drawOptions, null);
+ if (USE_HARDWARE_ACCELLERATION) {
+ VolatileImage volatileImage = (VolatileImage) bufferImage;
+ do {
+ try {
+ if (volatileImage.contentsLost()) {
+ createBufferImage();
+ }
+ // int validation =
+ // volatileImage.validate(screenGraphicsConfiguration);
+ // if (validation == VolatileImage.IMAGE_INCOMPATIBLE) {
+ // createBufferImage();
+ // }
+ g.drawImage(bufferImage, 0, 0, this);
+ } catch (NullPointerException e) {
+ createBufferImage();
+ }
+ } while (volatileImage == null || volatileImage.contentsLost());
+ } else {
+ g.drawImage(bufferImage, 0, 0, this);
+ // bufferImage.flush();
+ }
+ }
+
+ @Override
+ public void update(Graphics g) {
+ paint(g);
+ }
+
+ private void initializeListeners() {
+ addComponentListener(new ComponentAdapter() {
+
+ @Override
+ public void componentResized(ComponentEvent e) {
+ invalidateCache();
+ invalidate();
+ }
+ });
+ // addKeyListener(new KeyAdapter() {
+ //
+ // @Override
+ // public void keyPressed(KeyEvent e) {
+ // if (e.getKeyCode() == KeyEvent.VK_DELETE) {
+ // plugInPort.deleteSelectedComponents();
+ // }
+ // // plugInPort.mouseMoved(getMousePosition(), e.isControlDown(),
+ // // e.isShiftDown(), e
+ // // .isAltDown());
+ // }
+ // });
+ addMouseMotionListener(new MouseAdapter() {
+
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ setCursor(plugInPort.getCursorAt(e.getPoint()));
+ plugInPort.mouseMoved(e.getPoint(), e.isControlDown(), e.isShiftDown(), e
+ .isAltDown());
+ }
+ });
+ addMouseListener(new MouseAdapter() {
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (e.getButton() == MouseEvent.BUTTON1) {
+ plugInPort.mouseClicked(e.getPoint(), e.isControlDown(), e.isShiftDown(), e
+ .isAltDown(), e.getClickCount());
+ }
+ }
+ });
+ }
+
+ // Autoscroll
+
+ @Override
+ public void autoscroll(Point cursorLocn) {
+ scrollRectToVisible(new Rectangle(cursorLocn.x - 15, cursorLocn.y - 15, 30, 30));
+ }
+
+ @Override
+ public Insets getAutoscrollInsets() {
+ Rectangle rect = getVisibleRect();
+ return new Insets(rect.y - 15, rect.x - 15, rect.y + rect.height + 15, rect.x + rect.width
+ + 15);
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/CanvasPlugin.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/CanvasPlugin.java
new file mode 100644
index 00000000..6a7f32d5
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/CanvasPlugin.java
@@ -0,0 +1,319 @@
+package org.diylc.swing.plugins.canvas;
+
+import java.awt.Dimension;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.ClipboardOwner;
+import java.awt.datatransfer.Transferable;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+import java.util.EnumSet;
+
+import javax.swing.JPopupMenu;
+import javax.swing.SwingConstants;
+
+import org.apache.log4j.Logger;
+import org.diylc.common.BadPositionException;
+import org.diylc.common.EventType;
+import org.diylc.common.IPlugIn;
+import org.diylc.common.IPlugInPort;
+import org.diylc.core.measures.Size;
+import org.diylc.core.measures.SizeUnit;
+import org.diylc.presenter.Presenter;
+import org.diylc.swing.ActionFactory;
+import org.diylc.swing.ISwingUI;
+import org.diylc.swing.plugins.edit.ComponentTransferable;
+import org.diylc.swing.plugins.file.ProjectDrawingProvider;
+
+import com.diyfever.gui.miscutils.ConfigurationManager;
+import com.diyfever.gui.miscutils.IConfigListener;
+import com.diyfever.gui.ruler.IRulerListener;
+import com.diyfever.gui.ruler.RulerScrollPane;
+
+public class CanvasPlugin implements IPlugIn, ClipboardOwner {
+
+ private static final Logger LOG = Logger.getLogger(CanvasPlugin.class);
+
+ private RulerScrollPane scrollPane;
+ private CanvasPanel canvasPanel;
+ private JPopupMenu popupMenu;
+
+ private ActionFactory.CutAction cutAction;
+ private ActionFactory.CopyAction copyAction;
+ private ActionFactory.PasteAction pasteAction;
+ private ActionFactory.EditSelectionAction editSelectionAction;
+ private ActionFactory.DeleteSelectionAction deleteSelectionAction;
+ private ActionFactory.GroupAction groupAction;
+ private ActionFactory.UngroupAction ungroupAction;
+ private ActionFactory.SendToBackAction sendToBackAction;
+ private ActionFactory.BringToFrontAction bringToFrontAction;
+
+ private IPlugInPort plugInPort;
+ private ISwingUI swingUI;
+
+ private Clipboard clipboard;
+
+ private double zoomLevel = 1;
+
+ public CanvasPlugin(ISwingUI swingUI) {
+ this.swingUI = swingUI;
+ clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ }
+
+ @Override
+ public void connect(IPlugInPort plugInPort) {
+ this.plugInPort = plugInPort;
+ try {
+ swingUI.injectGUIComponent(getScrollPane(), SwingConstants.CENTER);
+ } catch (BadPositionException e) {
+ LOG.error("Could not install canvas plugin", e);
+ }
+ }
+
+ public CanvasPanel getCanvasPanel() {
+ if (canvasPanel == null) {
+ canvasPanel = new CanvasPanel(plugInPort);
+ canvasPanel.addMouseListener(new MouseAdapter() {
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ if (e.isPopupTrigger()) {
+ // Enable actions.
+ boolean enabled = !plugInPort.getSelectedComponents().isEmpty();
+ getCutAction().setEnabled(enabled);
+ getCopyAction().setEnabled(enabled);
+ try {
+ getPasteAction()
+ .setEnabled(
+ clipboard
+ .isDataFlavorAvailable(ComponentTransferable.listFlavor));
+ } catch (Exception ex) {
+ getPasteAction().setEnabled(false);
+ }
+ getEditSelectionAction().setEnabled(enabled);
+ getDeleteSelectionAction().setEnabled(enabled);
+ getGroupAction().setEnabled(enabled);
+ getUngroupAction().setEnabled(enabled);
+ getSendToBackAction().setEnabled(enabled);
+ getBringToFrontAction().setEnabled(enabled);
+
+ getPopupMenu().show(canvasPanel, e.getX(), e.getY());
+ }
+ }
+ });
+ }
+ return canvasPanel;
+ }
+
+ private RulerScrollPane getScrollPane() {
+ if (scrollPane == null) {
+ scrollPane = new RulerScrollPane(getCanvasPanel(), new ProjectDrawingProvider(
+ plugInPort, true, false), new Size(1d, SizeUnit.cm).convertToPixels(),
+ new Size(1d, SizeUnit.in).convertToPixels());
+ boolean metric = ConfigurationManager.getInstance().readBoolean(Presenter.METRIC_KEY,
+ true);
+ boolean wheelZoom = ConfigurationManager.getInstance().readBoolean(
+ IPlugInPort.WHEEL_ZOOM_KEY, false);
+ ConfigurationManager.getInstance().addConfigListener(IPlugInPort.WHEEL_ZOOM_KEY,
+ new IConfigListener() {
+
+ @Override
+ public void valueChanged(String key, Object value) {
+ scrollPane.setWheelScrollingEnabled(!(Boolean) value);
+ }
+ });
+ scrollPane.setMetric(metric);
+ scrollPane.setWheelScrollingEnabled(!wheelZoom);
+ scrollPane.addUnitListener(new IRulerListener() {
+
+ @Override
+ public void unitsChanged(boolean isMetric) {
+ plugInPort.setMetric(isMetric);
+ }
+ });
+ scrollPane.addMouseWheelListener(new MouseWheelListener() {
+
+ @Override
+ public void mouseWheelMoved(MouseWheelEvent e) {
+ boolean wheelZoom = ConfigurationManager.getInstance().readBoolean(
+ IPlugInPort.WHEEL_ZOOM_KEY, false);
+ if (!wheelZoom) {
+ return;
+ }
+ double d = plugInPort.getZoomLevel();
+ Double[] availableZoomLevels = plugInPort.getAvailableZoomLevels();
+ if (e.getWheelRotation() > 0) {
+ int i = availableZoomLevels.length - 1;
+ while (i > 0 && availableZoomLevels[i] >= d) {
+ i--;
+ }
+ plugInPort.setZoomLevel(availableZoomLevels[i]);
+ } else {
+ int i = 0;
+ while (i < availableZoomLevels.length - 1 && availableZoomLevels[i] <= d) {
+ i++;
+ }
+ plugInPort.setZoomLevel(availableZoomLevels[i]);
+ }
+ }
+ });
+ }
+ return scrollPane;
+ }
+
+ public JPopupMenu getPopupMenu() {
+ if (popupMenu == null) {
+ popupMenu = new JPopupMenu();
+ popupMenu.add(getCutAction());
+ popupMenu.add(getCopyAction());
+ popupMenu.add(getPasteAction());
+ popupMenu.addSeparator();
+ popupMenu.add(getEditSelectionAction());
+ popupMenu.add(getDeleteSelectionAction());
+ popupMenu.add(getGroupAction());
+ popupMenu.add(getUngroupAction());
+ popupMenu.add(getSendToBackAction());
+ popupMenu.add(getBringToFrontAction());
+ popupMenu.addSeparator();
+ popupMenu.add(ActionFactory.getInstance().createEditProjectAction(plugInPort));
+ }
+ return popupMenu;
+ }
+
+ public ActionFactory.CutAction getCutAction() {
+ if (cutAction == null) {
+ cutAction = ActionFactory.getInstance().createCutAction(plugInPort, clipboard, this);
+ }
+ return cutAction;
+ }
+
+ public ActionFactory.CopyAction getCopyAction() {
+ if (copyAction == null) {
+ copyAction = ActionFactory.getInstance().createCopyAction(plugInPort, clipboard, this);
+ }
+ return copyAction;
+ }
+
+ public ActionFactory.PasteAction getPasteAction() {
+ if (pasteAction == null) {
+ pasteAction = ActionFactory.getInstance().createPasteAction(plugInPort, clipboard);
+ }
+ return pasteAction;
+ }
+
+ public ActionFactory.EditSelectionAction getEditSelectionAction() {
+ if (editSelectionAction == null) {
+ editSelectionAction = ActionFactory.getInstance().createEditSelectionAction(plugInPort);
+ }
+ return editSelectionAction;
+ }
+
+ public ActionFactory.DeleteSelectionAction getDeleteSelectionAction() {
+ if (deleteSelectionAction == null) {
+ deleteSelectionAction = ActionFactory.getInstance().createDeleteSelectionAction(
+ plugInPort);
+ }
+ return deleteSelectionAction;
+ }
+
+ public ActionFactory.GroupAction getGroupAction() {
+ if (groupAction == null) {
+ groupAction = ActionFactory.getInstance().createGroupAction(plugInPort);
+ }
+ return groupAction;
+ }
+
+ public ActionFactory.UngroupAction getUngroupAction() {
+ if (ungroupAction == null) {
+ ungroupAction = ActionFactory.getInstance().createUngroupAction(plugInPort);
+ }
+ return ungroupAction;
+ }
+
+ public ActionFactory.SendToBackAction getSendToBackAction() {
+ if (sendToBackAction == null) {
+ sendToBackAction = ActionFactory.getInstance().createSendToBackAction(plugInPort);
+ }
+ return sendToBackAction;
+ }
+
+ public ActionFactory.BringToFrontAction getBringToFrontAction() {
+ if (bringToFrontAction == null) {
+ bringToFrontAction = ActionFactory.getInstance().createBringToFrontAction(plugInPort);
+ }
+ return bringToFrontAction;
+ }
+
+ @Override
+ public EnumSet getSubscribedEventTypes() {
+ return EnumSet.of(EventType.PROJECT_LOADED, EventType.ZOOM_CHANGED, EventType.REPAINT);
+ }
+
+ @Override
+ public void processMessage(final EventType eventType, Object... params) {
+ // SwingUtilities.invokeLater(new Runnable() {
+ //
+ // @Override
+ // public void run() {
+ // LOG.debug("event: " + eventType);
+ switch (eventType) {
+ case PROJECT_LOADED:
+ refreshSize();
+ if ((Boolean) params[1]) {
+ // Scroll to the center.
+ Rectangle visibleRect = canvasPanel.getVisibleRect();
+ visibleRect.setLocation((canvasPanel.getWidth() - visibleRect.width) / 2,
+ (canvasPanel.getHeight() - visibleRect.height) / 2);
+ canvasPanel.scrollRectToVisible(visibleRect);
+ canvasPanel.revalidate();
+ }
+ break;
+ case ZOOM_CHANGED:
+ Rectangle visibleRect = canvasPanel.getVisibleRect();
+ refreshSize();
+ // Try to set the visible area to be centered with the previous
+ // one.
+ double zoomFactor = (Double) params[0] / zoomLevel;
+ visibleRect.setBounds((int) (visibleRect.x * zoomFactor),
+ (int) (visibleRect.y * zoomFactor), visibleRect.width, visibleRect.height);
+ canvasPanel.scrollRectToVisible(visibleRect);
+ canvasPanel.revalidate();
+
+ zoomLevel = (Double) params[0];
+ break;
+ case REPAINT:
+ canvasPanel.repaint();
+ break;
+ }
+ // }
+ // });
+ }
+
+ private void refreshSize() {
+ Dimension d = plugInPort.getCanvasDimensions(true);
+ canvasPanel.setSize(d);
+ canvasPanel.setPreferredSize(d);
+ getScrollPane().setZoomLevel(plugInPort.getZoomLevel());
+ }
+
+ /**
+ * Causes ruler scroll pane to refresh by sending a fake mouse moved message
+ * to the canvasPanel.
+ */
+ public void refresh() {
+ MouseEvent event = new MouseEvent(canvasPanel, MouseEvent.MOUSE_MOVED, System
+ .currentTimeMillis(), 0, 1, 1,// canvasPanel.getWidth() / 2,
+ // canvasPanel.getHeight() / 2,
+ 0, false);
+ canvasPanel.dispatchEvent(event);
+ }
+
+ @Override
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ // TODO Auto-generated method stub
+
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/CanvasSourceListener.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/CanvasSourceListener.java
new file mode 100644
index 00000000..61a98185
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/CanvasSourceListener.java
@@ -0,0 +1,50 @@
+package org.diylc.swing.plugins.canvas;
+
+import java.awt.Point;
+import java.awt.dnd.DragSourceDragEvent;
+import java.awt.dnd.DragSourceDropEvent;
+import java.awt.dnd.DragSourceEvent;
+import java.awt.dnd.DragSourceListener;
+
+import org.diylc.common.IPlugInPort;
+
+
+/**
+ * {@link DragSourceListener} for {@link CanvasPanel}.
+ *
+ * @author Branislav Stojkovic
+ */
+class CanvasSourceListener implements DragSourceListener {
+
+ private IPlugInPort presenter;
+
+ public CanvasSourceListener(IPlugInPort presenter) {
+ super();
+ this.presenter = presenter;
+ }
+
+ @Override
+ public void dragDropEnd(DragSourceDropEvent dsde) {
+ presenter.dragEnded(null);
+ }
+
+ @Override
+ public void dragEnter(DragSourceDragEvent dsde) {
+ }
+
+ @Override
+ public void dragExit(DragSourceEvent dse) {
+ }
+
+ @Override
+ public void dragOver(DragSourceDragEvent dsde) {
+ Point p = dsde.getDragSourceContext().getComponent().getMousePosition();
+ if (p != null) {
+ dsde.getDragSourceContext().getComponent().firePropertyChange("dragPoint", p.x, p.y);
+ }
+ }
+
+ @Override
+ public void dropActionChanged(DragSourceDragEvent dsde) {
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/CanvasTargetListener.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/CanvasTargetListener.java
new file mode 100644
index 00000000..a96f6273
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/CanvasTargetListener.java
@@ -0,0 +1,95 @@
+package org.diylc.swing.plugins.canvas;
+
+import java.awt.Point;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.dnd.DropTargetEvent;
+import java.awt.dnd.DropTargetListener;
+
+import org.diylc.common.IPlugInPort;
+
+
+/**
+ * {@link DropTargetListener} for {@link CanvasPanel}.
+ *
+ * @author Branislav Stojkovic
+ */
+class CanvasTargetListener implements DropTargetListener {
+
+ private IPlugInPort presenter;
+
+ // Cached values
+ private Point currentPoint = null;
+ private boolean lastAccept;
+
+ public CanvasTargetListener(IPlugInPort presenter) {
+ super();
+ this.presenter = presenter;
+ }
+
+ @Override
+ public void dragEnter(DropTargetDragEvent dtde) {
+ }
+
+ @Override
+ public void dragExit(DropTargetEvent dte) {
+ }
+
+ @Override
+ public void dragOver(DropTargetDragEvent dtde) {
+ // If dragOver was previously called for this location use cached value.
+ if (dtde.getLocation().equals(currentPoint)) {
+ if (lastAccept) {
+ dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+ } else {
+ dtde.rejectDrag();
+ }
+ return;
+ }
+ try {
+ // Transferable t = dtde.getTransferable();
+ // if
+ // (t.isDataFlavorSupported(SelectionTransferable.selectionFlavor))
+ // {
+ // if (currentPoint != null) {
+ // List selection = (List) t
+ // .getTransferData(SelectionTransferable.selectionFlavor);
+ // Point startPoint = (Point) t
+ // .getTransferData(PointTransferable.pointFlavor);
+ // presenter.selectionMoved(dtde.getLocation().x
+ // - currentPoint.x, dtde.getLocation().y
+ // - currentPoint.y);
+ // }
+ // } else if
+ // (t.isDataFlavorSupported(PointTransferable.pointFlavor)) {
+ // Point startPoint = (Point) t
+ // .getTransferData(PointTransferable.pointFlavor);
+ // Point endPoint = dtde.getLocation();
+ // presenter.setSelectionRect(Utils.createRectangle(startPoint,
+ // endPoint));
+ // }
+ currentPoint = dtde.getLocation();
+ if (presenter.dragOver(currentPoint)) {
+ dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+ lastAccept = true;
+ } else {
+ dtde.rejectDrag();
+ lastAccept = false;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ dtde.rejectDrag();
+ lastAccept = false;
+ }
+ }
+
+ @Override
+ public void drop(DropTargetDropEvent dtde) {
+ presenter.dragEnded(dtde.getLocation());
+ }
+
+ @Override
+ public void dropActionChanged(DropTargetDragEvent dtde) {
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/EmptyTransferable.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/EmptyTransferable.java
new file mode 100644
index 00000000..ccddb7b1
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/EmptyTransferable.java
@@ -0,0 +1,24 @@
+package org.diylc.swing.plugins.canvas;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+
+public class EmptyTransferable implements Transferable {
+
+ @Override
+ public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
+ throw new UnsupportedFlavorException(flavor);
+ }
+
+ @Override
+ public DataFlavor[] getTransferDataFlavors() {
+ return new DataFlavor[] {};
+ }
+
+ @Override
+ public boolean isDataFlavorSupported(DataFlavor flavor) {
+ return false;
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/PointTransferable.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/PointTransferable.java
new file mode 100644
index 00000000..7c854f82
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/PointTransferable.java
@@ -0,0 +1,38 @@
+package org.diylc.swing.plugins.canvas;
+
+import java.awt.Point;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+
+public class PointTransferable implements Transferable {
+
+ public static final DataFlavor pointFlavor = new DataFlavor(Point.class, "Java Point");
+
+ private Point startPoint;
+
+ public PointTransferable(Point startPoint) {
+ super();
+ this.startPoint = startPoint;
+ }
+
+ @Override
+ public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
+ if (flavor.equals(pointFlavor)) {
+ return startPoint;
+ }
+ throw new UnsupportedFlavorException(flavor);
+ }
+
+ @Override
+ public DataFlavor[] getTransferDataFlavors() {
+ return new DataFlavor[] { pointFlavor };
+ }
+
+ @Override
+ public boolean isDataFlavorSupported(DataFlavor flavor) {
+ return flavor.equals(pointFlavor);
+ }
+
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/SelectionTransferable.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/SelectionTransferable.java
new file mode 100644
index 00000000..5901d2fc
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/canvas/SelectionTransferable.java
@@ -0,0 +1,47 @@
+package org.diylc.swing.plugins.canvas;
+
+import java.awt.Point;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+import java.util.List;
+
+import org.diylc.core.IDIYComponent;
+
+
+public class SelectionTransferable implements Transferable {
+
+ public static final DataFlavor selectionFlavor = new DataFlavor(List.class,
+ "Component Selection");
+
+ private Point startPoint;
+ private List> selection;
+
+ public SelectionTransferable(List> selection, Point startPoint) {
+ super();
+ this.selection = selection;
+ this.startPoint = startPoint;
+ }
+
+ @Override
+ public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
+ if (flavor.equals(selectionFlavor)) {
+ return selection;
+ }
+ if (flavor.equals(PointTransferable.pointFlavor)) {
+ return startPoint;
+ }
+ throw new UnsupportedFlavorException(flavor);
+ }
+
+ @Override
+ public DataFlavor[] getTransferDataFlavors() {
+ return new DataFlavor[] { selectionFlavor, PointTransferable.pointFlavor };
+ }
+
+ @Override
+ public boolean isDataFlavorSupported(DataFlavor flavor) {
+ return flavor.equals(selectionFlavor) || flavor.equals(selectionFlavor);
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/config/ConfigPlugin.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/config/ConfigPlugin.java
new file mode 100644
index 00000000..d12360df
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/config/ConfigPlugin.java
@@ -0,0 +1,90 @@
+package org.diylc.swing.plugins.config;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.EnumSet;
+
+import org.apache.log4j.Logger;
+import org.diylc.common.EventType;
+import org.diylc.common.IPlugIn;
+import org.diylc.common.IPlugInPort;
+import org.diylc.core.Theme;
+import org.diylc.images.IconLoader;
+import org.diylc.swing.ActionFactory;
+import org.diylc.swing.ISwingUI;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.DomDriver;
+
+/**
+ * Controls configuration menu.
+ *
+ * @author Branislav Stojkovic
+ */
+public class ConfigPlugin implements IPlugIn {
+
+ private static final Logger LOG = Logger.getLogger(ConfigPlugin.class);
+
+ private static final String CONFIG_MENU = "Config";
+ private static final String THEME_MENU = "Theme";
+
+ private ISwingUI swingUI;
+
+ public ConfigPlugin(ISwingUI swingUI) {
+ super();
+ this.swingUI = swingUI;
+ }
+
+ @Override
+ public void connect(IPlugInPort plugInPort) {
+ swingUI.injectMenuAction(ActionFactory.getInstance().createConfigAction(plugInPort,
+ "Anti-Aliasing", IPlugInPort.ANTI_ALIASING_KEY, true), CONFIG_MENU);
+ swingUI.injectMenuAction(ActionFactory.getInstance().createConfigAction(plugInPort,
+ "Auto-Create Pads", IPlugInPort.AUTO_PADS_KEY, false), CONFIG_MENU);
+ swingUI.injectMenuAction(ActionFactory.getInstance().createConfigAction(plugInPort,
+ "Auto-Edit Mode", IPlugInPort.AUTO_EDIT_KEY, false), CONFIG_MENU);
+ swingUI.injectMenuAction(ActionFactory.getInstance().createConfigAction(plugInPort,
+ "Continuous Creation", IPlugInPort.CONTINUOUS_CREATION_KEY, false), CONFIG_MENU);
+ swingUI.injectMenuAction(ActionFactory.getInstance().createConfigAction(plugInPort,
+ "Export Grid", IPlugInPort.EXPORT_GRID_KEY, false), CONFIG_MENU);
+ swingUI.injectMenuAction(ActionFactory.getInstance().createConfigAction(plugInPort,
+ "Hi-Quality Rendering", IPlugInPort.HI_QUALITY_RENDER_KEY, false), CONFIG_MENU);
+ swingUI.injectMenuAction(ActionFactory.getInstance().createConfigAction(plugInPort,
+ "Mouse Wheel Zoom", IPlugInPort.WHEEL_ZOOM_KEY, false), CONFIG_MENU);
+ swingUI.injectMenuAction(ActionFactory.getInstance().createConfigAction(plugInPort,
+ "Outline Mode", IPlugInPort.OUTLINE_KEY, false), CONFIG_MENU);
+ swingUI.injectMenuAction(ActionFactory.getInstance().createConfigAction(plugInPort,
+ "Snap to Grid", IPlugInPort.SNAP_TO_GRID_KEY, true), CONFIG_MENU);
+ swingUI.injectMenuAction(ActionFactory.getInstance().createConfigAction(plugInPort,
+ "Sticky Points", IPlugInPort.STICKY_POINTS_KEY, true), CONFIG_MENU);
+
+ File themeDir = new File("themes");
+ if (themeDir.exists()) {
+ XStream xStream = new XStream(new DomDriver());
+ swingUI.injectSubmenu(THEME_MENU, IconLoader.Pens.getIcon(), CONFIG_MENU);
+ for (File file : themeDir.listFiles()) {
+ if (file.getName().toLowerCase().endsWith(".xml")) {
+ try {
+ InputStream in = new FileInputStream(file);
+ Theme theme = (Theme) xStream.fromXML(in);
+ LOG.debug("Found theme: " + theme.getName());
+ swingUI.injectMenuAction(ActionFactory.getInstance().createThemeAction(
+ plugInPort, theme), THEME_MENU);
+ } catch (Exception e) {
+ LOG.error("Could not load theme file " + file.getName(), e);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public EnumSet getSubscribedEventTypes() {
+ return EnumSet.noneOf(EventType.class);
+ }
+
+ @Override
+ public void processMessage(EventType eventType, Object... params) {
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/edit/ComponentTransferable.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/edit/ComponentTransferable.java
new file mode 100644
index 00000000..e5506739
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/edit/ComponentTransferable.java
@@ -0,0 +1,51 @@
+package org.diylc.swing.plugins.edit;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.diylc.core.IDIYComponent;
+
+/**
+ * Represents component selection as a {@link List} of {@link IDIYComponent}
+ * objects. Implements {@link Transferable}, so it is suitable for clipboard
+ * usage.
+ *
+ * @author Branislav Stojkovic
+ */
+public class ComponentTransferable extends ArrayList> implements Transferable {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final DataFlavor listFlavor = new DataFlavor(ComponentTransferable.class, "application/diylc");
+
+ public ComponentTransferable() {
+ super();
+ }
+
+ public ComponentTransferable(Collection> selectedComponents) {
+ super(selectedComponents);
+ }
+
+ @Override
+ public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
+ if (flavor.equals(listFlavor)) {
+ return this;
+ }
+ throw new UnsupportedFlavorException(flavor);
+ }
+
+ @Override
+ public DataFlavor[] getTransferDataFlavors() {
+ return new DataFlavor[] { listFlavor };
+ }
+
+ @Override
+ public boolean isDataFlavorSupported(DataFlavor flavor) {
+ return flavor.equals(listFlavor);
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/edit/EditMenuPlugin.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/edit/EditMenuPlugin.java
new file mode 100644
index 00000000..9be4addb
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/edit/EditMenuPlugin.java
@@ -0,0 +1,224 @@
+package org.diylc.swing.plugins.edit;
+
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.ClipboardOwner;
+import java.awt.datatransfer.FlavorEvent;
+import java.awt.datatransfer.FlavorListener;
+import java.awt.datatransfer.Transferable;
+import java.util.EnumSet;
+
+import org.diylc.common.EventType;
+import org.diylc.common.IPlugIn;
+import org.diylc.common.IPlugInPort;
+import org.diylc.core.Project;
+import org.diylc.images.IconLoader;
+import org.diylc.swing.ActionFactory;
+import org.diylc.swing.ISwingUI;
+
+import com.diyfever.gui.undo.IUndoListener;
+import com.diyfever.gui.undo.UndoHandler;
+
+public class EditMenuPlugin implements IPlugIn, ClipboardOwner {
+
+ private static final String EDIT_TITLE = "Edit";
+ private static final String RENUMBER_TITLE = "Renumber Selection";
+
+ private IPlugInPort plugInPort;
+ private ISwingUI swingUI;
+
+ private Clipboard clipboard;
+
+ private ActionFactory.CutAction cutAction;
+ private ActionFactory.CopyAction copyAction;
+ private ActionFactory.PasteAction pasteAction;
+ private ActionFactory.EditSelectionAction editSelectionAction;
+ private ActionFactory.DeleteSelectionAction deleteSelectionAction;
+ private ActionFactory.GroupAction groupAction;
+ private ActionFactory.UngroupAction ungroupAction;
+ private ActionFactory.SendToBackAction sendToBackAction;
+ private ActionFactory.BringToFrontAction bringToFrontAction;
+ private ActionFactory.RenumberAction renumberXAxisAction;
+ private ActionFactory.RenumberAction renumberYAxisAction;
+
+ private UndoHandler undoHandler;
+
+ public EditMenuPlugin(ISwingUI swingUI) {
+ this.swingUI = swingUI;
+ // SecurityManager securityManager = System.getSecurityManager();
+ // if (securityManager != null) {
+ // try {
+ // securityManager.checkSystemClipboardAccess();
+ // } catch (Exception e) {
+ // e.printStackTrace();
+ // }
+ // }
+ clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ undoHandler = new UndoHandler(new IUndoListener() {
+
+ @Override
+ public void actionPerformed(Project currentState) {
+ plugInPort.loadProject(currentState, false);
+ }
+ });
+ clipboard.addFlavorListener(new FlavorListener() {
+
+ @Override
+ public void flavorsChanged(FlavorEvent e) {
+ refreshActions();
+ }
+ });
+ }
+
+ public ActionFactory.CutAction getCutAction() {
+ if (cutAction == null) {
+ cutAction = ActionFactory.getInstance().createCutAction(plugInPort, clipboard, this);
+ }
+ return cutAction;
+ }
+
+ public ActionFactory.CopyAction getCopyAction() {
+ if (copyAction == null) {
+ copyAction = ActionFactory.getInstance().createCopyAction(plugInPort, clipboard, this);
+ }
+ return copyAction;
+ }
+
+ public ActionFactory.PasteAction getPasteAction() {
+ if (pasteAction == null) {
+ pasteAction = ActionFactory.getInstance().createPasteAction(plugInPort, clipboard);
+ }
+ return pasteAction;
+ }
+
+ public ActionFactory.EditSelectionAction getEditSelectionAction() {
+ if (editSelectionAction == null) {
+ editSelectionAction = ActionFactory.getInstance().createEditSelectionAction(plugInPort);
+ }
+ return editSelectionAction;
+ }
+
+ public ActionFactory.DeleteSelectionAction getDeleteSelectionAction() {
+ if (deleteSelectionAction == null) {
+ deleteSelectionAction = ActionFactory.getInstance().createDeleteSelectionAction(
+ plugInPort);
+ }
+ return deleteSelectionAction;
+ }
+
+ public ActionFactory.GroupAction getGroupAction() {
+ if (groupAction == null) {
+ groupAction = ActionFactory.getInstance().createGroupAction(plugInPort);
+ }
+ return groupAction;
+ }
+
+ public ActionFactory.UngroupAction getUngroupAction() {
+ if (ungroupAction == null) {
+ ungroupAction = ActionFactory.getInstance().createUngroupAction(plugInPort);
+ }
+ return ungroupAction;
+ }
+
+ public ActionFactory.SendToBackAction getSendToBackAction() {
+ if (sendToBackAction == null) {
+ sendToBackAction = ActionFactory.getInstance().createSendToBackAction(plugInPort);
+ }
+ return sendToBackAction;
+ }
+
+ public ActionFactory.BringToFrontAction getBringToFrontAction() {
+ if (bringToFrontAction == null) {
+ bringToFrontAction = ActionFactory.getInstance().createBringToFrontAction(plugInPort);
+ }
+ return bringToFrontAction;
+ }
+
+ public ActionFactory.RenumberAction getRenumberXAxisAction() {
+ if (renumberXAxisAction == null) {
+ renumberXAxisAction = ActionFactory.getInstance()
+ .createRenumberAction(plugInPort, true);
+ }
+ return renumberXAxisAction;
+ }
+
+ public ActionFactory.RenumberAction getRenumberYAxisAction() {
+ if (renumberYAxisAction == null) {
+ renumberYAxisAction = ActionFactory.getInstance().createRenumberAction(plugInPort,
+ false);
+ }
+ return renumberYAxisAction;
+ }
+
+ @Override
+ public void connect(IPlugInPort plugInPort) {
+ this.plugInPort = plugInPort;
+
+ ActionFactory actionFactory = ActionFactory.getInstance();
+
+ swingUI.injectMenuAction(undoHandler.getUndoAction(), EDIT_TITLE);
+ swingUI.injectMenuAction(undoHandler.getRedoAction(), EDIT_TITLE);
+ swingUI.injectMenuAction(null, EDIT_TITLE);
+ swingUI.injectMenuAction(getCutAction(), EDIT_TITLE);
+ swingUI.injectMenuAction(getCopyAction(), EDIT_TITLE);
+ swingUI.injectMenuAction(getPasteAction(), EDIT_TITLE);
+ swingUI.injectMenuAction(null, EDIT_TITLE);
+ swingUI.injectMenuAction(actionFactory.createSelectAllAction(plugInPort), EDIT_TITLE);
+ swingUI.injectMenuAction(getEditSelectionAction(), EDIT_TITLE);
+ swingUI.injectMenuAction(getDeleteSelectionAction(), EDIT_TITLE);
+ swingUI.injectMenuAction(getGroupAction(), EDIT_TITLE);
+ swingUI.injectMenuAction(getUngroupAction(), EDIT_TITLE);
+ swingUI.injectMenuAction(getSendToBackAction(), EDIT_TITLE);
+ swingUI.injectMenuAction(getBringToFrontAction(), EDIT_TITLE);
+ swingUI.injectMenuAction(null, EDIT_TITLE);
+ swingUI.injectSubmenu(RENUMBER_TITLE, IconLoader.Sort.getIcon(), EDIT_TITLE);
+ swingUI.injectMenuAction(getRenumberXAxisAction(), RENUMBER_TITLE);
+ swingUI.injectMenuAction(getRenumberYAxisAction(), RENUMBER_TITLE);
+ swingUI.injectMenuAction(null, EDIT_TITLE);
+ swingUI.injectMenuAction(actionFactory.createEditProjectAction(plugInPort), EDIT_TITLE);
+
+ refreshActions();
+ }
+
+ @Override
+ public EnumSet getSubscribedEventTypes() {
+ return EnumSet.of(EventType.SELECTION_CHANGED, EventType.PROJECT_MODIFIED);
+ }
+
+ @Override
+ public void processMessage(EventType eventType, Object... params) {
+ switch (eventType) {
+ case SELECTION_CHANGED:
+ refreshActions();
+ break;
+ case PROJECT_MODIFIED:
+ undoHandler.stateChanged((Project) params[0], (Project) params[1], (String) params[2]);
+ break;
+ }
+ }
+
+ private void refreshActions() {
+ boolean enabled = !plugInPort.getSelectedComponents().isEmpty();
+ getCutAction().setEnabled(enabled);
+ getCopyAction().setEnabled(enabled);
+ try {
+ getPasteAction().setEnabled(
+ clipboard.isDataFlavorAvailable(ComponentTransferable.listFlavor));
+ } catch (Exception e) {
+ getPasteAction().setEnabled(false);
+ }
+ getEditSelectionAction().setEnabled(enabled);
+ getDeleteSelectionAction().setEnabled(enabled);
+ getGroupAction().setEnabled(enabled);
+ getUngroupAction().setEnabled(enabled);
+ getSendToBackAction().setEnabled(enabled);
+ getBringToFrontAction().setEnabled(enabled);
+ }
+
+ // ClipboardOwner
+
+ @Override
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ refreshActions();
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/file/BomDialog.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/file/BomDialog.java
new file mode 100644
index 00000000..b3b51d4c
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/file/BomDialog.java
@@ -0,0 +1,180 @@
+package org.diylc.swing.plugins.file;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+import org.diylc.images.IconLoader;
+import org.diylc.swing.gui.DialogFactory;
+
+import com.diyfever.gui.export.TableExporter;
+import com.diyfever.gui.objecttable.ObjectListTable;
+
+public class BomDialog extends JDialog {
+
+ private static final long serialVersionUID = 1L;
+
+ private ObjectListTable table;
+ private JPanel toolbar;
+
+ public BomDialog(JFrame parent, List bom) {
+ super(parent, "Bill of Materials");
+ setContentPane(createMainPanel());
+ getTable().setData(bom);
+ pack();
+ setLocationRelativeTo(parent);
+ }
+
+ @Override
+ public void setVisible(boolean b) {
+ super.setVisible(b);
+ if (b) {
+ getTable().autoFit(Arrays.asList(3));
+ }
+ }
+
+ private JPanel createMainPanel() {
+ JPanel mainPanel = new JPanel();
+ mainPanel.setLayout(new BorderLayout());
+ mainPanel.add(getToolbar(), BorderLayout.NORTH);
+ mainPanel.add(new JScrollPane(getTable()), BorderLayout.CENTER);
+ return mainPanel;
+ }
+
+ private ObjectListTable getTable() {
+ if (table == null) {
+ try {
+ table = new ObjectListTable(BomEntry.class, new String[] { "getName",
+ "getValue", "getQuantity", "getNotes/setNotes" }, null);
+ } catch (SecurityException e) {
+ e.printStackTrace();
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ }
+ }
+ return table;
+ }
+
+ private JPanel getToolbar() {
+ if (toolbar == null) {
+ toolbar = new JPanel();
+ toolbar.add(new JButton(new SaveToExcelAction()));
+ toolbar.add(new JButton(new SaveToCSVAction()));
+ toolbar.add(new JButton(new SaveToPNGAction()));
+ toolbar.add(new JButton(new SaveToHTMLAction()));
+ }
+ return toolbar;
+ }
+
+ class SaveToExcelAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ public SaveToExcelAction() {
+ super();
+ putValue(Action.NAME, "Save to Excel");
+ putValue(Action.SMALL_ICON, IconLoader.Excel.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ File file = DialogFactory.getInstance().showSaveDialog(
+ FileFilterEnum.EXCEL.getFilter(), null,
+ FileFilterEnum.EXCEL.getExtensions()[0], null);
+ if (file != null) {
+ try {
+ TableExporter.getInstance().exportToExcel(getTable(), file);
+ } catch (IOException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ }
+ }
+ }
+
+ class SaveToCSVAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ public SaveToCSVAction() {
+ super();
+ putValue(Action.NAME, "Save to CSV");
+ putValue(Action.SMALL_ICON, IconLoader.CSV.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ File file = DialogFactory.getInstance().showSaveDialog(FileFilterEnum.CSV.getFilter(),
+ null, FileFilterEnum.CSV.getExtensions()[0], null);
+ if (file != null) {
+ try {
+ TableExporter.getInstance().exportToCSV(getTable(), file);
+ } catch (IOException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ }
+ }
+ }
+
+ class SaveToHTMLAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ public SaveToHTMLAction() {
+ super();
+ putValue(Action.NAME, "Save to HTML");
+ putValue(Action.SMALL_ICON, IconLoader.HTML.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ File file = DialogFactory.getInstance().showSaveDialog(FileFilterEnum.HTML.getFilter(),
+ null, FileFilterEnum.HTML.getExtensions()[0], null);
+ if (file != null) {
+ try {
+ TableExporter.getInstance().exportToHTML(getTable(), file);
+ } catch (IOException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ }
+ }
+ }
+
+ class SaveToPNGAction extends AbstractAction {
+
+ private static final long serialVersionUID = 1L;
+
+ public SaveToPNGAction() {
+ super();
+ putValue(Action.NAME, "Save to PNG");
+ putValue(Action.SMALL_ICON, IconLoader.Image.getIcon());
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ File file = DialogFactory.getInstance().showSaveDialog(FileFilterEnum.PNG.getFilter(),
+ null, FileFilterEnum.PNG.getExtensions()[0], null);
+ if (file != null) {
+ try {
+ TableExporter.getInstance().exportToPNG(getTable(), file);
+ } catch (IOException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ }
+ }
+ }
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/file/BomEntry.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/file/BomEntry.java
new file mode 100644
index 00000000..550a21d0
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/file/BomEntry.java
@@ -0,0 +1,89 @@
+package org.diylc.swing.plugins.file;
+
+public class BomEntry {
+
+ private String name;
+ private String value;
+ private Integer quantity;
+ private String notes;
+
+ public BomEntry(String name, String value, int quantity) {
+ super();
+ this.name = name;
+ this.value = value;
+ this.quantity = quantity;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public Integer getQuantity() {
+ return quantity;
+ }
+
+ public void setQuantity(Integer quantity) {
+ this.quantity = quantity;
+ }
+
+ public String getNotes() {
+ return notes;
+ }
+
+ public void setNotes(String notes) {
+ this.notes = notes;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ result = prime * result + ((notes == null) ? 0 : notes.hashCode());
+ result = prime * result + quantity;
+ 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;
+ BomEntry other = (BomEntry) obj;
+ if (name == null) {
+ if (other.name != null)
+ return false;
+ } else if (!name.equals(other.name))
+ return false;
+ if (notes == null) {
+ if (other.notes != null)
+ return false;
+ } else if (!notes.equals(other.notes))
+ return false;
+ if (quantity != other.quantity)
+ return false;
+ if (value == null) {
+ if (other.value != null)
+ return false;
+ } else if (!value.equals(other.value))
+ return false;
+ return true;
+ }
+
+}
diff --git a/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/file/BomMaker.java b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/file/BomMaker.java
new file mode 100644
index 00000000..1c3d7d73
--- /dev/null
+++ b/ diy-layout-creator --username bancika@gmail.com/diylc-core/src/org/diylc/swing/plugins/file/BomMaker.java
@@ -0,0 +1,55 @@
+package org.diylc.swing.plugins.file;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.diylc.core.IDIYComponent;
+
+public class BomMaker {
+
+ private static BomMaker instance;
+
+ public static BomMaker getInstance() {
+ if (instance == null) {
+ instance = new BomMaker();
+ }
+ return instance;
+ }
+
+ private BomMaker() {
+ }
+
+ public List createBom(List> components) {
+ Map entryMap = new HashMap