diff --git a/diylc/diylc-core/src/org/diylc/common/ComponentType.java b/diylc/diylc-core/src/org/diylc/common/ComponentType.java index 715526bb..8d66ade9 100644 --- a/diylc/diylc-core/src/org/diylc/common/ComponentType.java +++ b/diylc/diylc-core/src/org/diylc/common/ComponentType.java @@ -1,148 +1,150 @@ /* + * + * DIY Layout Creator (DIYLC). Copyright (c) 2009-2018 held jointly by the individual authors. + * + * This file is part of DIYLC. + * + * DIYLC is free software: you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * DIYLC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with DIYLC. If not, see + * . + */ +package org.diylc.common; - DIY Layout Creator (DIYLC). - Copyright (c) 2009-2018 held jointly by the individual authors. +import javax.swing.Icon; - This file is part of DIYLC. +import org.diylc.core.CreationMethod; +import org.diylc.core.IDIYComponent; +import org.diylc.core.annotations.BomPolicy; +import org.diylc.core.annotations.KeywordPolicy; - DIYLC is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. +/** + * Entity class used to describe a component type. + * + * @author Branislav Stojkovic + * + * @see IDIYComponent + */ +public class ComponentType { - DIYLC is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + private String name; + private String description; + private CreationMethod creationMethod; + private String category; + private String namePrefix; + private String author; + private Icon icon; + private Class> instanceClass; + private double zOrder; + private boolean flexibleZOrder; + private boolean stretchable; + private BomPolicy bomPolicy; + private boolean autoEdit; + private IComponentTransformer transformer; + private KeywordPolicy keywordPolicy; + private String keywordTag; + private boolean continuity; - You should have received a copy of the GNU General Public License - along with DIYLC. If not, see . + public ComponentType(String name, String description, CreationMethod creationMethod, String category, + String namePrefix, String author, Icon icon, Class> instanceClass, double zOrder, + boolean flexibleZOrder, boolean stretchable, BomPolicy bomPolicy, boolean autoEdit, + IComponentTransformer transformer, KeywordPolicy keywordPolicy, String keywordTag, boolean continuity) { + super(); + this.name = name; + this.description = description; + this.creationMethod = creationMethod; + this.category = category; + this.namePrefix = namePrefix; + this.author = author; + this.icon = icon; + this.instanceClass = instanceClass; + this.zOrder = zOrder; + this.flexibleZOrder = flexibleZOrder; + this.stretchable = stretchable; + this.bomPolicy = bomPolicy; + this.autoEdit = autoEdit; + this.transformer = transformer; + this.keywordPolicy = keywordPolicy; + this.keywordTag = keywordTag; + this.continuity = continuity; + } -*/ -package org.diylc.common; - -import javax.swing.Icon; - -import org.diylc.core.CreationMethod; -import org.diylc.core.IDIYComponent; -import org.diylc.core.annotations.BomPolicy; -import org.diylc.core.annotations.KeywordPolicy; - -/** - * 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> instanceClass; - private double zOrder; - private boolean flexibleZOrder; - private boolean stretchable; - private BomPolicy bomPolicy; - private boolean autoEdit; - private IComponentTransformer transformer; - private KeywordPolicy keywordPolicy; - private String keywordTag; - - public ComponentType(String name, String description, CreationMethod creationMethod, String category, - String namePrefix, String author, Icon icon, Class> instanceClass, double zOrder, - boolean flexibleZOrder, boolean stretchable, BomPolicy bomPolicy, boolean autoEdit, IComponentTransformer transformer, - KeywordPolicy keywordPolicy, String keywordTag) { - super(); - this.name = name; - this.description = description; - this.creationMethod = creationMethod; - this.category = category; - this.namePrefix = namePrefix; - this.author = author; - this.icon = icon; - this.instanceClass = instanceClass; - this.zOrder = zOrder; - this.flexibleZOrder = flexibleZOrder; - this.stretchable = stretchable; - this.bomPolicy = bomPolicy; - this.autoEdit = autoEdit; - this.transformer = transformer; - this.keywordPolicy = keywordPolicy; - this.keywordTag = keywordTag; - } - - 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> getInstanceClass() { - return instanceClass; - } - - public double getZOrder() { - return zOrder; - } - - public boolean isFlexibleZOrder() { - return flexibleZOrder; - } - - public boolean isStretchable() { - return stretchable; - } - - public BomPolicy getBomPolicy() { - return bomPolicy; - } - - public boolean isAutoEdit() { - return autoEdit; - } - - public IComponentTransformer getTransformer() { - return transformer; - } - - public KeywordPolicy getKeywordPolicy() { - return keywordPolicy; - } - - public String getKeywordTag() { - return keywordTag; - } - - @Override - public String toString() { - return name; - } -} + 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> getInstanceClass() { + return instanceClass; + } + + public double getZOrder() { + return zOrder; + } + + public boolean isFlexibleZOrder() { + return flexibleZOrder; + } + + public boolean isStretchable() { + return stretchable; + } + + public BomPolicy getBomPolicy() { + return bomPolicy; + } + + public boolean isAutoEdit() { + return autoEdit; + } + + public IComponentTransformer getTransformer() { + return transformer; + } + + public KeywordPolicy getKeywordPolicy() { + return keywordPolicy; + } + + public String getKeywordTag() { + return keywordTag; + } + + public boolean isContinuity() { + return continuity; + } + + @Override + public String toString() { + return name; + } +} diff --git a/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java b/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java index dc51c980..f25c8d5d 100644 --- a/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java +++ b/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java @@ -70,7 +70,8 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP public static final String RECENT_COMPONENTS_KEY = "recentComponents"; public static final String RECENT_FILES_KEY = "recentFiles"; public static final String SHOW_RULERS_KEY = "showRulers"; - public static final String SHOW_GRID_KEY = "showGrid"; + public static final String SHOW_GRID_KEY = "showGrid"; + public static final String HIGHLIGHT_CONTINUITY_AREA = "highlightContinuityArea"; public static final int DND_TOGGLE_STICKY = 0x1; public static final int DND_TOGGLE_SNAP = 0x40000000; diff --git a/diylc/diylc-core/src/org/diylc/components/AbstractCurvedComponent.java b/diylc/diylc-core/src/org/diylc/components/AbstractCurvedComponent.java index abe220e6..1d5b040f 100644 --- a/diylc/diylc-core/src/org/diylc/components/AbstractCurvedComponent.java +++ b/diylc/diylc-core/src/org/diylc/components/AbstractCurvedComponent.java @@ -61,7 +61,7 @@ public abstract class AbstractCurvedComponent extends AbstractTransparentComp * @param componentState * @param theme */ - protected abstract void drawCurve(CubicCurve2D curve, Graphics2D g2d, ComponentState componentState); + protected abstract void drawCurve(CubicCurve2D curve, Graphics2D g2d, ComponentState componentState, IDrawingObserver drawingObserver); /** * @return default color. @@ -110,7 +110,7 @@ public abstract class AbstractCurvedComponent extends AbstractTransparentComp // g2d.setColor(color); // g2d.setStroke(new BasicStroke(thickness - 2)); // g2d.draw(path); - drawCurve(curve, g2d, componentState); + drawCurve(curve, g2d, componentState, drawingObserver); g2d.setComposite(oldComposite); } diff --git a/diylc/diylc-core/src/org/diylc/components/AbstractLeadedComponent.java b/diylc/diylc-core/src/org/diylc/components/AbstractLeadedComponent.java index 42c10cd9..8805dfa2 100644 --- a/diylc/diylc-core/src/org/diylc/components/AbstractLeadedComponent.java +++ b/diylc/diylc-core/src/org/diylc/components/AbstractLeadedComponent.java @@ -32,7 +32,8 @@ import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.Rectangle2D; - +import java.util.Observable; + import org.diylc.appframework.miscutils.ConfigurationManager; import org.diylc.common.Display; import org.diylc.common.IPlugInPort; @@ -84,6 +85,10 @@ public abstract class AbstractLeadedComponent extends AbstractTransparentComp } catch (NullPointerException e) { // This will happen if components do not have any shape. } + } + + protected boolean IsCopperArea() { + return false; } @Override @@ -93,7 +98,7 @@ public abstract class AbstractLeadedComponent extends AbstractTransparentComp Shape shape = getBodyShape(); // If there's no body, just draw the line connecting the ending points. if (shape == null) { - drawLead(g2d, componentState); + drawLead(g2d, componentState, drawingObserver, IsCopperArea()); } else if (supportsStandingMode() && length.convertToPixels() > points[0].distance(points[1])) { // When ending points are too close draw the component in standing // mode. @@ -124,7 +129,7 @@ public abstract class AbstractLeadedComponent extends AbstractTransparentComp g2d.setColor(finalBorderColor); g2d.draw(body); if (!outlineMode) { - drawLead(g2d, componentState); + drawLead(g2d, componentState, drawingObserver, false); } } else { // Normal mode with component body in the center and two lead parts. @@ -293,12 +298,17 @@ public abstract class AbstractLeadedComponent extends AbstractTransparentComp g2d.drawLine(points[1].x, points[1].y, endX, endY); } - private void drawLead(Graphics2D g2d, ComponentState componentState) { + private void drawLead(Graphics2D g2d, ComponentState componentState, IDrawingObserver observer, boolean isCopperArea) { + if (isCopperArea) + observer.startTrackingContinuityArea(true); g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(getLeadThickness())); Color leadColor = shouldShadeLeads() ? getLeadColorForPainting(componentState).darker() : getLeadColorForPainting(componentState); g2d.setColor(leadColor); - g2d.drawLine(points[0].x, points[0].y, points[1].x, points[1].y); + g2d.drawLine(points[0].x, points[0].y, points[1].x, points[1].y); + + if (isCopperArea) + observer.stopTrackingContinuityArea(); if (shouldShadeLeads()) { g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(getLeadThickness() - 2)); diff --git a/diylc/diylc-core/src/org/diylc/core/IDrawingObserver.java b/diylc/diylc-core/src/org/diylc/core/IDrawingObserver.java index 4a476b7d..0ee8ac6a 100644 --- a/diylc/diylc-core/src/org/diylc/core/IDrawingObserver.java +++ b/diylc/diylc-core/src/org/diylc/core/IDrawingObserver.java @@ -31,5 +31,9 @@ public interface IDrawingObserver { void stopTracking(); - void startTracking(); + void startTracking(); + + void stopTrackingContinuityArea(); + + void startTrackingContinuityArea(boolean positive); } diff --git a/diylc/diylc-core/src/org/diylc/core/annotations/ComponentDescriptor.java b/diylc/diylc-core/src/org/diylc/core/annotations/ComponentDescriptor.java index 4ba7121d..53b0b567 100644 --- a/diylc/diylc-core/src/org/diylc/core/annotations/ComponentDescriptor.java +++ b/diylc/diylc-core/src/org/diylc/core/annotations/ComponentDescriptor.java @@ -116,6 +116,13 @@ public @interface ComponentDescriptor { * * @return */ - String keywordTag() default ""; + String keywordTag() default ""; + + /** + * For components that show continuity between the two ending points, e.d. wires, jumpers, etc. + * + * @return + */ + boolean continuity() default false; } diff --git a/diylc/diylc-core/src/org/diylc/presenter/ComponentArea.java b/diylc/diylc-core/src/org/diylc/presenter/ComponentArea.java new file mode 100644 index 00000000..56297194 --- /dev/null +++ b/diylc/diylc-core/src/org/diylc/presenter/ComponentArea.java @@ -0,0 +1,51 @@ +/* + + DIY Layout Creator (DIYLC). + Copyright (c) 2009-2018 held jointly by the individual authors. + + This file is part of DIYLC. + + DIYLC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DIYLC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DIYLC. If not, see . + +*/ +package org.diylc.presenter; + +import java.awt.geom.Area; +import java.util.List; + +public class ComponentArea { + + private Area outlineArea; + private List continuityPositiveAreas; + private List continuityNegativeAreas; + + public ComponentArea(Area outlineArea, List continuityPositiveAreas, List continuityNegativeAreas) { + super(); + this.outlineArea = outlineArea; + this.continuityPositiveAreas = continuityPositiveAreas; + this.continuityNegativeAreas = continuityNegativeAreas; + } + + public Area getOutlineArea() { + return outlineArea; + } + + public List getContinuityPositiveAreas() { + return continuityPositiveAreas; + } + + public List getContinuityNegativeAreas() { + return continuityNegativeAreas; + } +} diff --git a/diylc/diylc-core/src/org/diylc/presenter/ComponentProcessor.java b/diylc/diylc-core/src/org/diylc/presenter/ComponentProcessor.java index 383192ff..e241c18d 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/ComponentProcessor.java +++ b/diylc/diylc-core/src/org/diylc/presenter/ComponentProcessor.java @@ -97,7 +97,8 @@ public class ComponentProcessor { boolean autoEdit; IComponentTransformer transformer; KeywordPolicy keywordPolicy; - String keywordTag; + String keywordTag; + boolean continuity; if (clazz.isAnnotationPresent(ComponentDescriptor.class)) { ComponentDescriptor annotation = clazz.getAnnotation(ComponentDescriptor.class); name = annotation.name(); @@ -113,7 +114,8 @@ public class ComponentProcessor { autoEdit = annotation.autoEdit(); transformer = getComponentTransformer(annotation.transformer()); keywordPolicy = annotation.keywordPolicy(); - keywordTag = annotation.keywordTag(); + keywordTag = annotation.keywordTag(); + continuity = annotation.continuity(); } else { // default name = clazz.getSimpleName(); description = ""; @@ -128,7 +130,8 @@ public class ComponentProcessor { autoEdit = true; transformer = null; keywordPolicy = KeywordPolicy.NEVER_SHOW; - keywordTag = null; + keywordTag = null; + continuity = false; } icon = null; // Draw component icon. @@ -148,7 +151,7 @@ public class ComponentProcessor { } ComponentType componentType = new ComponentType(name, description, creationMethod, category, namePrefix, author, icon, clazz, zOrder, - flexibleZOrder, stretchable, bomPolicy, autoEdit, transformer, keywordPolicy, keywordTag); + flexibleZOrder, stretchable, bomPolicy, autoEdit, transformer, keywordPolicy, keywordTag, continuity); componentTypeMap.put(clazz.getName(), componentType); return componentType; } diff --git a/diylc/diylc-core/src/org/diylc/presenter/DrawingManager.java b/diylc/diylc-core/src/org/diylc/presenter/DrawingManager.java index 526bd5fb..e4d3ef3d 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/DrawingManager.java +++ b/diylc/diylc-core/src/org/diylc/presenter/DrawingManager.java @@ -1,403 +1,591 @@ /* + * + * DIY Layout Creator (DIYLC). Copyright (c) 2009-2018 held jointly by the individual authors. + * + * This file is part of DIYLC. + * + * DIYLC is free software: you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * DIYLC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with DIYLC. If not, see + * . + */ +package org.diylc.presenter; - DIY Layout Creator (DIYLC). - Copyright (c) 2009-2018 held jointly by the individual authors. +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.awt.geom.Path2D; +import java.awt.geom.PathIterator; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; - This file is part of DIYLC. +import org.apache.log4j.Logger; +import org.diylc.appframework.miscutils.ConfigurationManager; +import org.diylc.appframework.simplemq.MessageDispatcher; +import org.diylc.common.ComponentType; +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; - DIYLC is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. +/** + * 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 { - DIYLC is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + private static final Logger LOG = Logger.getLogger(DrawingManager.class); - You should have received a copy of the GNU General Public License - along with DIYLC. If not, see . + public static int CONTROL_POINT_SIZE = 7; -*/ -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.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.apache.log4j.Logger; -import org.diylc.appframework.miscutils.ConfigurationManager; -import org.diylc.appframework.simplemq.MessageDispatcher; -import org.diylc.common.DrawOption; -import org.diylc.common.EventType; -import org.diylc.common.GridType; -import org.diylc.common.IComponentFiler; -import org.diylc.common.IPlugInPort; -import org.diylc.common.ObjectCache; -import org.diylc.core.ComponentState; -import org.diylc.core.IDIYComponent; -import org.diylc.core.Project; -import org.diylc.core.Theme; -import org.diylc.core.VisibilityPolicy; -import org.diylc.utils.Constants; - -/** - * Utility that deals with painting {@link Project} on the {@link Graphics2D} and keeps areas taken - * by each drawn component. - * - * @author Branislav Stojkovic - */ -public class DrawingManager { - - private static final Logger LOG = Logger.getLogger(DrawingManager.class); - - public static int CONTROL_POINT_SIZE = 7; - - public static final String ZOOM_KEY = "zoom"; - - public static String DEBUG_COMPONENT_AREAS = "org.diylc.debugComponentAreas"; - - public static Color CONTROL_POINT_COLOR = Color.blue; - public static Color SELECTED_CONTROL_POINT_COLOR = Color.green; - - private Theme theme = (Theme) ConfigurationManager.getInstance().readObject(IPlugInPort.THEME_KEY, - Constants.DEFAULT_THEME); - - // Keeps Area object of each drawn component. - private Map, Area> componentAreaMap; - // Maps components to the last state they are drawn in. Also, used to - // determine which components are invalidated when they are not in the map. - private Map, ComponentState> lastDrawnStateMap; - - private Composite slotComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f); - private Composite lockedComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f); - private List> failedComponents = new ArrayList>(); - - private double zoomLevel = 1d;// ConfigurationManager.getInstance().readDouble(ZOOM_KEY, - // 1d); - - private MessageDispatcher messageDispatcher; - - private boolean debugComponentAreas; - - public DrawingManager(MessageDispatcher messageDispatcher) { - super(); - this.messageDispatcher = messageDispatcher; - componentAreaMap = new HashMap, Area>(); - lastDrawnStateMap = new HashMap, ComponentState>(); - String debugComponentAreasStr = System.getProperty(DEBUG_COMPONENT_AREAS); - debugComponentAreas = debugComponentAreasStr != null && debugComponentAreasStr.equalsIgnoreCase("true"); - } - - /** - * Paints the project onto the canvas and returns the list of components that failed to draw. - * - * @param g2d - * @param project - * @param drawOptions - * @param filter - * @param selectionRect - * @param selectedComponents - * @param lockedComponents - * @param groupedComponents - * @param controlPointSlot - * @param componentSlot - * @param dragInProgress - * @return - */ - public List> drawProject(Graphics2D g2d, Project project, Set drawOptions, - IComponentFiler filter, Rectangle selectionRect, Collection> selectedComponents, - Set> lockedComponents, Set> groupedComponents, List controlPointSlot, - List> componentSlot, boolean dragInProgress) { - failedComponents.clear(); - if (project == null) { - return failedComponents; - } - - double zoom = 1d; - if (drawOptions.contains(DrawOption.ZOOM)) { - zoom = zoomLevel; - } else { - zoom = 1 / Constants.PIXEL_SIZE; - } - - G2DWrapper g2dWrapper = new G2DWrapper(g2d, zoom); - - if (drawOptions.contains(DrawOption.ANTIALIASING)) { - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - } else { - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); - g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); - } - if (ConfigurationManager.getInstance().readBoolean(IPlugInPort.HI_QUALITY_RENDER_KEY, false)) { - g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); - g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); - g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); - g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE); - g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - // g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, - // RenderingHints.VALUE_STROKE_PURE); - } else { - g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED); - g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_SPEED); - g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED); - g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE); - g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); - } - - // AffineTransform initialTx = g2d.getTransform(); - Dimension d = getCanvasDimensions(project, zoom, true); - - g2dWrapper.setColor(theme.getBgColor()); - g2dWrapper.fillRect(0, 0, d.width, d.height); - g2d.clip(new Rectangle(new Point(0, 0), d)); - - GridType gridType = GridType.LINES; - if (drawOptions.contains(DrawOption.GRID) && gridType != GridType.NONE) { - double zoomStep = project.getGridSpacing().convertToPixels() * zoom; - if (gridType == GridType.CROSSHAIR) { - g2d.setStroke(new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[] { - (float) zoomStep / 2, (float) zoomStep / 2}, (float) zoomStep / 4)); - } else if (gridType == GridType.DOT) { - g2d.setStroke(new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[] {1f, - (float) zoomStep - 1}, 0f)); - } - - g2dWrapper.setColor(theme.getGridColor()); - for (double i = zoomStep; i < d.width; i += zoomStep) { - g2dWrapper.drawLine((int) i, 0, (int) i, d.height - 1); - } - for (double j = zoomStep; j < d.height; j += zoomStep) { - g2dWrapper.drawLine(0, (int) j, d.width - 1, (int) j); - } - } - - if (Math.abs(1.0 - zoom) > 1e-4) { - g2dWrapper.scale(zoom, zoom); - } - - // Composite mainComposite = g2d.getComposite(); - // Composite alphaComposite = - // AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f); - - // g2dWrapper.resetTx(); - - // componentAreaMap.clear(); - for (IDIYComponent component : project.getComponents()) { - // Do not draw the component if it's filtered out. - if (filter != null && !filter.testComponent(component)) { - continue; - } - ComponentState state = ComponentState.NORMAL; - if (drawOptions.contains(DrawOption.SELECTION) && selectedComponents.contains(component)) { - if (dragInProgress) { - state = ComponentState.DRAGGING; - } else { - state = ComponentState.SELECTED; - } - } - // Do not track the area if component is not invalidated and was - // drawn in the same state. - boolean trackArea = lastDrawnStateMap.get(component) != state; - - synchronized (g2d) { - g2dWrapper.startedDrawingComponent(); - if (!trackArea) { - g2dWrapper.stopTracking(); - } - // Draw locked components in a new composite. - if (lockedComponents.contains(component)) { - g2d.setComposite(lockedComposite); - } - // Draw the component through the g2dWrapper. - try { - component.draw(g2dWrapper, state, drawOptions.contains(DrawOption.OUTLINE_MODE), project, g2dWrapper); - } catch (Exception e) { - LOG.error("Error drawing " + component.getName(), e); - failedComponents.add(component); - } - Area area = g2dWrapper.finishedDrawingComponent(); - if (trackArea && area != null && !area.isEmpty()) { - componentAreaMap.put(component, area); - lastDrawnStateMap.put(component, state); - } - } - } - - // Draw control points. - if (drawOptions.contains(DrawOption.CONTROL_POINTS)) { - // Draw unselected points first to make sure they are below. - if (dragInProgress || drawOptions.contains(DrawOption.OUTLINE_MODE)) { - for (IDIYComponent component : project.getComponents()) { - for (int i = 0; i < component.getControlPointCount(); i++) { - VisibilityPolicy visibilityPolicy = component.getControlPointVisibilityPolicy(i); - if ((groupedComponents.contains(component) - && (visibilityPolicy == VisibilityPolicy.ALWAYS || (selectedComponents.contains(component) && visibilityPolicy == VisibilityPolicy.WHEN_SELECTED)) || (!groupedComponents - .contains(component) && !selectedComponents.contains(component) && component - .getControlPointVisibilityPolicy(i) == VisibilityPolicy.ALWAYS))) { - g2dWrapper.setColor(CONTROL_POINT_COLOR); - Point controlPoint = component.getControlPoint(i); - int pointSize = CONTROL_POINT_SIZE - 2; - g2dWrapper.fillOval(controlPoint.x - pointSize / 2, controlPoint.y - pointSize / 2, pointSize, pointSize); - } - } - } - } - // Then draw the selected ones. - for (IDIYComponent component : selectedComponents) { - for (int i = 0; i < component.getControlPointCount(); i++) { - if (!groupedComponents.contains(component) - && (component.getControlPointVisibilityPolicy(i) == VisibilityPolicy.WHEN_SELECTED || component - .getControlPointVisibilityPolicy(i) == VisibilityPolicy.ALWAYS)) { - - Point controlPoint = component.getControlPoint(i); - int pointSize = CONTROL_POINT_SIZE; - - g2dWrapper.setColor(SELECTED_CONTROL_POINT_COLOR.darker()); - g2dWrapper.fillOval(controlPoint.x - pointSize / 2, controlPoint.y - pointSize / 2, pointSize, pointSize); - g2dWrapper.setColor(SELECTED_CONTROL_POINT_COLOR); - g2dWrapper.fillOval(controlPoint.x - CONTROL_POINT_SIZE / 2 + 1, controlPoint.y - CONTROL_POINT_SIZE / 2 - + 1, CONTROL_POINT_SIZE - 2, CONTROL_POINT_SIZE - 2); - } - } - } - } - - // Draw component slot in a separate composite. - if (componentSlot != null) { - g2dWrapper.startedDrawingComponent(); - g2dWrapper.setComposite(slotComposite); - for (IDIYComponent component : componentSlot) { - try { - - component.draw(g2dWrapper, ComponentState.NORMAL, drawOptions.contains(DrawOption.OUTLINE_MODE), project, - g2dWrapper); - - } catch (Exception e) { - LOG.error("Error drawing " + component.getName(), e); - failedComponents.add(component); - } - } - g2dWrapper.finishedDrawingComponent(); - } - - // Draw control points of the component in the slot. - if (controlPointSlot != null) { - for (Point point : controlPointSlot) { - if (point != null) { - g2dWrapper.setColor(SELECTED_CONTROL_POINT_COLOR.darker()); - g2dWrapper.fillOval(point.x - CONTROL_POINT_SIZE / 2, point.y - CONTROL_POINT_SIZE / 2, CONTROL_POINT_SIZE, - CONTROL_POINT_SIZE); - g2dWrapper.setColor(SELECTED_CONTROL_POINT_COLOR); - g2dWrapper.fillOval(point.x - CONTROL_POINT_SIZE / 2 + 1, point.y - CONTROL_POINT_SIZE / 2 + 1, - CONTROL_POINT_SIZE - 2, CONTROL_POINT_SIZE - 2); - } - } - } - - // Go back to the original transformation and zoom in to draw the - // selection rectangle and other similar elements. - // g2d.setTransform(initialTx); - // if ((drawOptions.contains(DrawOption.ZOOM)) && (Math.abs(1.0 - - // zoomLevel) > 1e-4)) { - // g2d.scale(zoomLevel, zoomLevel); - // } - - // At the end draw selection rectangle if needed. - if (drawOptions.contains(DrawOption.SELECTION) && (selectionRect != null)) { - g2d.setColor(Color.white); - g2d.draw(selectionRect); - g2d.setColor(Color.black); - g2d.setStroke(Constants.DASHED_STROKE); - g2d.draw(selectionRect); - } - - // Draw component area for test - if (debugComponentAreas) { - g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1)); - g2d.setColor(Color.red); - for (Area area : componentAreaMap.values()) { - g2d.draw(area); - } - } - - return failedComponents; - } - - public double getZoomLevel() { - return zoomLevel; - } - - public void setZoomLevel(double zoomLevel) { - this.zoomLevel = zoomLevel; - fireZoomChanged(); - // ConfigurationManager.getInstance().writeValue(ZOOM_KEY, zoomLevel); - } - - public void invalidateComponent(IDIYComponent component) { - componentAreaMap.remove(component); - lastDrawnStateMap.remove(component); - } - - public Area getComponentArea(IDIYComponent component) { - return componentAreaMap.get(component); - } - - public void clearComponentAreaMap() { - componentAreaMap.clear(); - lastDrawnStateMap.clear(); - } - - public List> findComponentsAt(Point point, Project project) { - List> components = new ArrayList>(); - for (int i = 0; i < project.getComponents().size(); i++) { - Area area = componentAreaMap.get(project.getComponents().get(i)); - if (area != null && area.contains(point)) { - components.add(0, project.getComponents().get(i)); - } - } - return components; - } - - public Dimension getCanvasDimensions(Project project, Double zoomLevel, boolean useZoom) { - double width = project.getWidth().convertToPixels(); - double height = project.getHeight().convertToPixels(); - if (useZoom) { - width *= zoomLevel; - height *= zoomLevel; - } else { - width /= Constants.PIXEL_SIZE; - height /= Constants.PIXEL_SIZE; - } - return new Dimension((int) width, (int) height); - } - - public void fireZoomChanged() { - messageDispatcher.dispatchMessage(EventType.ZOOM_CHANGED, zoomLevel); - messageDispatcher.dispatchMessage(EventType.REPAINT); - } - - public Theme getTheme() { - return theme; - } - - public void setTheme(Theme theme) { - this.theme = theme; - ConfigurationManager.getInstance().writeValue(IPlugInPort.THEME_KEY, theme); - messageDispatcher.dispatchMessage(EventType.REPAINT); - } -} + public static final String ZOOM_KEY = "zoom"; + + public static String DEBUG_COMPONENT_AREAS = "org.diylc.debugComponentAreas"; + public static String DEBUG_CONTINUITY_AREAS = "org.diylc.debugContinuityAreas"; + + 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, ComponentArea> 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 Area continuityArea; + + 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; + private boolean debugContinuityAreas; + + public DrawingManager(MessageDispatcher messageDispatcher) { + super(); + this.messageDispatcher = messageDispatcher; + componentAreaMap = new HashMap, ComponentArea>(); + lastDrawnStateMap = new HashMap, ComponentState>(); + String debugComponentAreasStr = System.getProperty(DEBUG_COMPONENT_AREAS); + debugComponentAreas = debugComponentAreasStr != null && debugComponentAreasStr.equalsIgnoreCase("true"); + + String debugContinuityAreasStr = System.getProperty(DEBUG_CONTINUITY_AREAS); + debugContinuityAreas = debugContinuityAreasStr != null && debugContinuityAreasStr.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, Collection> selectedComponents, + Set> lockedComponents, Set> groupedComponents, List controlPointSlot, + List> componentSlot, boolean dragInProgress) { + failedComponents.clear(); + if (project == null) { + return failedComponents; + } + + double zoom = 1d; + if (drawOptions.contains(DrawOption.ZOOM)) { + zoom = zoomLevel; + } else { + zoom = 1 / Constants.PIXEL_SIZE; + } + + G2DWrapper g2dWrapper = new G2DWrapper(g2d, zoom); + + if (drawOptions.contains(DrawOption.ANTIALIASING)) { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + } else { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); + } + if (ConfigurationManager.getInstance().readBoolean(IPlugInPort.HI_QUALITY_RENDER_KEY, false)) { + g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE); + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + // g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, + // RenderingHints.VALUE_STROKE_PURE); + } else { + g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED); + g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_SPEED); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED); + g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE); + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + } + + // AffineTransform initialTx = g2d.getTransform(); + Dimension d = getCanvasDimensions(project, zoom, true); + + g2dWrapper.setColor(theme.getBgColor()); + g2dWrapper.fillRect(0, 0, d.width, d.height); + g2d.clip(new Rectangle(new Point(0, 0), d)); + + GridType gridType = GridType.LINES; + if (drawOptions.contains(DrawOption.GRID) && gridType != GridType.NONE) { + double zoomStep = project.getGridSpacing().convertToPixels() * zoom; + if (gridType == GridType.CROSSHAIR) { + g2d.setStroke(new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[] { + (float) zoomStep / 2, (float) zoomStep / 2}, (float) zoomStep / 4)); + } else if (gridType == GridType.DOT) { + g2d.setStroke(new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[] {1f, + (float) zoomStep - 1}, 0f)); + } + + g2dWrapper.setColor(theme.getGridColor()); + for (double i = zoomStep; i < d.width; i += zoomStep) { + g2dWrapper.drawLine((int) i, 0, (int) i, d.height - 1); + } + for (double j = zoomStep; j < d.height; j += zoomStep) { + g2dWrapper.drawLine(0, (int) j, d.width - 1, (int) j); + } + } + + if (Math.abs(1.0 - zoom) > 1e-4) { + g2dWrapper.scale(zoom, zoom); + } + + // Composite mainComposite = g2d.getComposite(); + // Composite alphaComposite = + // AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f); + + // g2dWrapper.resetTx(); + + // componentAreaMap.clear(); + for (IDIYComponent component : project.getComponents()) { + // Do not draw the component if it's filtered out. + if (filter != null && !filter.testComponent(component)) { + continue; + } + ComponentState state = ComponentState.NORMAL; + if (drawOptions.contains(DrawOption.SELECTION) && selectedComponents.contains(component)) { + if (dragInProgress) { + state = ComponentState.DRAGGING; + } else { + state = ComponentState.SELECTED; + } + } + // Do not track the area if component is not invalidated and was + // drawn in the same state. + boolean trackArea = lastDrawnStateMap.get(component) != state; + + synchronized (g2d) { + g2dWrapper.startedDrawingComponent(); + if (!trackArea) { + g2dWrapper.stopTracking(); + } + // Draw locked components in a new composite. + if (lockedComponents.contains(component)) { + g2d.setComposite(lockedComposite); + } + // Draw the component through the g2dWrapper. + try { + component.draw(g2dWrapper, state, drawOptions.contains(DrawOption.OUTLINE_MODE), project, g2dWrapper); + } catch (Exception e) { + LOG.error("Error drawing " + component.getName(), e); + failedComponents.add(component); + } + ComponentArea area = g2dWrapper.finishedDrawingComponent(); + if (trackArea && area != null && !area.getOutlineArea().isEmpty()) { + componentAreaMap.put(component, area); + lastDrawnStateMap.put(component, state); + } + } + } + + // Draw control points. + if (drawOptions.contains(DrawOption.CONTROL_POINTS)) { + // Draw unselected points first to make sure they are below. + if (dragInProgress || drawOptions.contains(DrawOption.OUTLINE_MODE)) { + for (IDIYComponent component : project.getComponents()) { + for (int i = 0; i < component.getControlPointCount(); i++) { + VisibilityPolicy visibilityPolicy = component.getControlPointVisibilityPolicy(i); + if ((groupedComponents.contains(component) + && (visibilityPolicy == VisibilityPolicy.ALWAYS || (selectedComponents.contains(component) && visibilityPolicy == VisibilityPolicy.WHEN_SELECTED)) || (!groupedComponents + .contains(component) && !selectedComponents.contains(component) && component + .getControlPointVisibilityPolicy(i) == VisibilityPolicy.ALWAYS))) { + g2dWrapper.setColor(CONTROL_POINT_COLOR); + Point controlPoint = component.getControlPoint(i); + int pointSize = CONTROL_POINT_SIZE - 2; + g2dWrapper.fillOval(controlPoint.x - pointSize / 2, controlPoint.y - pointSize / 2, pointSize, pointSize); + } + } + } + } + // Then draw the selected ones. + for (IDIYComponent component : selectedComponents) { + for (int i = 0; i < component.getControlPointCount(); i++) { + if (!groupedComponents.contains(component) + && (component.getControlPointVisibilityPolicy(i) == VisibilityPolicy.WHEN_SELECTED || component + .getControlPointVisibilityPolicy(i) == VisibilityPolicy.ALWAYS)) { + + Point controlPoint = component.getControlPoint(i); + int pointSize = CONTROL_POINT_SIZE; + + g2dWrapper.setColor(SELECTED_CONTROL_POINT_COLOR.darker()); + g2dWrapper.fillOval(controlPoint.x - pointSize / 2, controlPoint.y - pointSize / 2, pointSize, pointSize); + g2dWrapper.setColor(SELECTED_CONTROL_POINT_COLOR); + g2dWrapper.fillOval(controlPoint.x - CONTROL_POINT_SIZE / 2 + 1, controlPoint.y - CONTROL_POINT_SIZE / 2 + + 1, CONTROL_POINT_SIZE - 2, CONTROL_POINT_SIZE - 2); + } + } + } + } + + // Draw component slot in a separate composite. + if (componentSlot != null) { + g2dWrapper.startedDrawingComponent(); + g2dWrapper.setComposite(slotComposite); + for (IDIYComponent component : componentSlot) { + try { + + component.draw(g2dWrapper, ComponentState.NORMAL, drawOptions.contains(DrawOption.OUTLINE_MODE), project, + g2dWrapper); + + } catch (Exception e) { + LOG.error("Error drawing " + component.getName(), e); + failedComponents.add(component); + } + } + g2dWrapper.finishedDrawingComponent(); + } + + // Draw control points of the component in the slot. + if (controlPointSlot != null) { + for (Point point : controlPointSlot) { + if (point != null) { + g2dWrapper.setColor(SELECTED_CONTROL_POINT_COLOR.darker()); + g2dWrapper.fillOval(point.x - CONTROL_POINT_SIZE / 2, point.y - CONTROL_POINT_SIZE / 2, CONTROL_POINT_SIZE, + CONTROL_POINT_SIZE); + g2dWrapper.setColor(SELECTED_CONTROL_POINT_COLOR); + g2dWrapper.fillOval(point.x - CONTROL_POINT_SIZE / 2 + 1, point.y - CONTROL_POINT_SIZE / 2 + 1, + CONTROL_POINT_SIZE - 2, CONTROL_POINT_SIZE - 2); + } + } + } + + // Go back to the original transformation and zoom in to draw the + // selection rectangle and other similar elements. + // g2d.setTransform(initialTx); + // if ((drawOptions.contains(DrawOption.ZOOM)) && (Math.abs(1.0 - + // zoomLevel) > 1e-4)) { + // g2d.scale(zoomLevel, zoomLevel); + // } + + // At the end draw selection rectangle if needed. + if (drawOptions.contains(DrawOption.SELECTION) && (selectionRect != null)) { + g2d.setColor(Color.white); + g2d.draw(selectionRect); + g2d.setColor(Color.black); + g2d.setStroke(Constants.DASHED_STROKE); + g2d.draw(selectionRect); + } + + // Draw component area for test + if (debugComponentAreas) { + g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1)); + g2d.setColor(Color.red); + for (ComponentArea area : componentAreaMap.values()) { + g2d.draw(area.getOutlineArea()); + } + } + + // Draw continuity area for test + if (debugContinuityAreas) { + g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1)); + g2d.setColor(Color.green); + for (ComponentArea area : componentAreaMap.values()) { + for (Area a : area.getContinuityPositiveAreas()) + g2d.draw(a); + } + g2d.setColor(Color.blue); + for (ComponentArea area : componentAreaMap.values()) { + for (Area a : area.getContinuityNegativeAreas()) + g2d.draw(a); + } + } + + if (continuityArea != null && (ConfigurationManager.getInstance().readBoolean(IPlugInPort.HIGHLIGHT_CONTINUITY_AREA, false))) + { + Composite oldComposite = g2d.getComposite(); + g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f)); + g2d.setColor(Color.green); + g2d.fill(continuityArea); + g2d.setComposite(oldComposite); + } + + 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 ComponentArea getComponentArea(IDIYComponent component) { + return componentAreaMap.get(component); + } + + public void clearComponentAreaMap() { + componentAreaMap.clear(); + lastDrawnStateMap.clear(); + } + + public List> findComponentsAt(Point point, Project project) { + List> components = new ArrayList>(); + for (int i = 0; i < project.getComponents().size(); i++) { + ComponentArea area = componentAreaMap.get(project.getComponents().get(i)); + if (area != null && area.getOutlineArea().contains(point)) { + components.add(0, project.getComponents().get(i)); + } + } + return components; + } + + public Dimension getCanvasDimensions(Project project, Double zoomLevel, boolean useZoom) { + double width = project.getWidth().convertToPixels(); + double height = project.getHeight().convertToPixels(); + if (useZoom) { + width *= zoomLevel; + height *= zoomLevel; + } else { + width /= Constants.PIXEL_SIZE; + height /= Constants.PIXEL_SIZE; + } + return new Dimension((int) width, (int) height); + } + + public void fireZoomChanged() { + messageDispatcher.dispatchMessage(EventType.ZOOM_CHANGED, zoomLevel); + messageDispatcher.dispatchMessage(EventType.REPAINT); + } + + public Theme getTheme() { + return theme; + } + + public void setTheme(Theme theme) { + this.theme = theme; + ConfigurationManager.getInstance().writeValue(IPlugInPort.THEME_KEY, theme); + messageDispatcher.dispatchMessage(EventType.REPAINT); + } + + @SuppressWarnings("unchecked") + public void findContinuityAreaAtPoint(Project project, Point p) { + // Find all individual continuity areas for all components + List preliminaryAreas = new ArrayList(); + List checkBreakout = new ArrayList(); + List connections = new ArrayList(); + for (IDIYComponent c : project.getComponents()) { + ComponentArea a = getComponentArea(c); + + ComponentType type = + ComponentProcessor.getInstance().extractComponentTypeFrom((Class>) c.getClass()); + if (type.isContinuity()) { + connections.add(new Connection(c.getControlPoint(0), c.getControlPoint(c.getControlPointCount() - 1))); + } + + if (a == null || a.getOutlineArea() == null) + continue; + if (a.getContinuityPositiveAreas() != null) + for (Area a1 : a.getContinuityPositiveAreas()) { + preliminaryAreas.add(a1); + checkBreakout.add(false); + } + if (a.getContinuityNegativeAreas() != null) { + for (Area na : a.getContinuityNegativeAreas()) + for (int i = 0; i < preliminaryAreas.size(); i++) { + Area a1 = preliminaryAreas.get(i); + if (a1.intersects(na.getBounds2D())) { + a1.subtract(na); + checkBreakout.set(i, true); + } + } + } + } + + // Check if we need to break some areas out in case they are interrupted + List areas = new ArrayList(); + for (int i = 0; i < preliminaryAreas.size(); i++) { + Area a = preliminaryAreas.get(i); + if (checkBreakout.get(i)) + areas.addAll(tryBreakout(a)); + else + areas.add(a); + } + + crunchAreas(areas, connections); + + for (Area a : areas) { + if (a.contains(p)) { + continuityArea = a; + return; + } + } + + continuityArea = null; + } + + private boolean crunchAreas(List areas, List connections) { + boolean isChanged = false; + + List newAreas = new ArrayList(); + List consumed = new ArrayList(); + for (int i = 0; i < areas.size(); i++) { + consumed.add(false); + } + for (int i = 0; i < areas.size(); i++) { + for (int j = i + 1; j < areas.size(); j++) { + if (consumed.get(j)) + continue; + Area a1 = areas.get(i); + Area a2 = areas.get(j); + Area intersection = new Area(a1); + intersection.intersect(a2); + // if the two areas intersect, make a union and consume the second area + if (!intersection.isEmpty()) { + a1.add(a2); + consumed.set(j, true); + } else { // maybe there's a connection between them + for (Connection p : connections) { + if ((a1.contains(p.pointA) && a2.contains(p.pointB)) || (a1.contains(p.pointB) && a2.contains(p.pointA))) { + a1.add(a2); + consumed.set(j, true); + break; + } + } + } + } + } + for (int i = 0; i < areas.size(); i++) + if (!consumed.get(i)) + newAreas.add(areas.get(i)); + else + isChanged = true; + + if (isChanged) { + areas.clear(); + areas.addAll(newAreas); + crunchAreas(areas, connections); + } + + return isChanged; + } + + private List tryBreakout(Area a) { + List toReturn = new ArrayList(); + Path2D p = null; + PathIterator pathIterator = a.getPathIterator(null); + while (!pathIterator.isDone()) { + double[] coord = new double[6]; + int type = pathIterator.currentSegment(coord); + switch (type) { + case PathIterator.SEG_MOVETO: + if (p != null) { + Area partArea = new Area(p); + toReturn.add(partArea); + } + p = new Path2D.Double(); + p.moveTo(coord[0], coord[1]); + break; + case PathIterator.SEG_LINETO: + p.lineTo(coord[0], coord[1]); + break; + case PathIterator.SEG_CUBICTO: + p.curveTo(coord[0], coord[1], coord[2], coord[3], coord[4], coord[5]); + break; + case PathIterator.SEG_QUADTO: + p.quadTo(coord[0], coord[1], coord[2], coord[3]); + break; + } + pathIterator.next(); + } + if (p != null) { + Area partArea = new Area(p); + toReturn.add(partArea); + } + + return toReturn; + } + + class Connection { + + private Point pointA; + private Point pointB; + + public Connection(Point pointA, Point pointB) { + super(); + this.pointA = pointA; + this.pointB = pointB; + } + + public Point getPointA() { + return pointA; + } + + public Point getPointB() { + return pointB; + } + } +} diff --git a/diylc/diylc-core/src/org/diylc/presenter/G2DWrapper.java b/diylc/diylc-core/src/org/diylc/presenter/G2DWrapper.java index 5697efbf..0d127147 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/G2DWrapper.java +++ b/diylc/diylc-core/src/org/diylc/presenter/G2DWrapper.java @@ -1,719 +1,746 @@ /* + * + * DIY Layout Creator (DIYLC). Copyright (c) 2009-2018 held jointly by the individual authors. + * + * This file is part of DIYLC. + * + * DIYLC is free software: you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * DIYLC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with DIYLC. If not, see + * . + */ +package org.diylc.presenter; - DIY Layout Creator (DIYLC). - Copyright (c) 2009-2018 held jointly by the individual authors. +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Composite; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.Image; +import java.awt.Paint; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.RenderingHints.Key; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.geom.AffineTransform; +import java.awt.geom.Arc2D; +import java.awt.geom.Area; +import java.awt.geom.CubicCurve2D; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.awt.geom.NoninvertibleTransformException; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.awt.geom.QuadCurve2D; +import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.awt.image.ImageObserver; +import java.awt.image.RenderedImage; +import java.awt.image.renderable.RenderableImage; +import java.text.AttributedCharacterIterator; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; - This file is part of DIYLC. +import org.diylc.common.ObjectCache; +import org.diylc.core.IDrawingObserver; - DIYLC is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. +/** + * {@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 { - DIYLC is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + public static int LINE_SENSITIVITY_MARGIN = 2; + public static int CURVE_SENSITIVITY = 6; - You should have received a copy of the GNU General Public License - along with DIYLC. If not, see . + private boolean drawingComponent = false; + private boolean trackingAllowed = true; + private boolean trackingContinuityAllowed = false; + private boolean trackingContinuityPositive = true; -*/ -package org.diylc.presenter; - -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Composite; -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.GraphicsConfiguration; -import java.awt.Image; -import java.awt.Paint; -import java.awt.Polygon; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.Shape; -import java.awt.Stroke; -import java.awt.RenderingHints.Key; -import java.awt.font.FontRenderContext; -import java.awt.font.GlyphVector; -import java.awt.geom.AffineTransform; -import java.awt.geom.Arc2D; -import java.awt.geom.Area; -import java.awt.geom.CubicCurve2D; -import java.awt.geom.Ellipse2D; -import java.awt.geom.Line2D; -import java.awt.geom.NoninvertibleTransformException; -import java.awt.geom.PathIterator; -import java.awt.geom.Point2D; -import java.awt.geom.QuadCurve2D; -import java.awt.geom.Rectangle2D; -import java.awt.geom.RoundRectangle2D; -import java.awt.image.BufferedImage; -import java.awt.image.BufferedImageOp; -import java.awt.image.ImageObserver; -import java.awt.image.RenderedImage; -import java.awt.image.renderable.RenderableImage; -import java.text.AttributedCharacterIterator; -import java.util.Map; - -import org.diylc.common.ObjectCache; -import org.diylc.core.IDrawingObserver; - -/** - * {@link Graphics2D} wrapper that keeps track of all drawing actions and creates an {@link Area} - * that corresponds to drawn objects. Before each component is drawn, - * {@link #startedDrawingComponent()} should be called. After the component is drawn, area may be - * retrieved using {@link #finishedDrawingComponent()}. Graphics configuration (color, font, etc) is - * reset between each two components. - * - * @author Branislav Stojkovic - */ -class G2DWrapper extends Graphics2D implements IDrawingObserver { - - public static int LINE_SENSITIVITY_MARGIN = 2; - public static int CURVE_SENSITIVITY = 6; - - private boolean drawingComponent = false; - private boolean trackingAllowed = true; - - private Graphics2D canvasGraphics; - private Stroke originalStroke; - private Color originalColor; - private Composite originalComposite; - private AffineTransform originalTx; - private Font originalFont; - private AffineTransform currentTx; - private AffineTransform initialTx; - private Area currentArea; - private Shape lastShape; - - private double zoom; - - /** - * Creates a wrapper around specified {@link Graphics2D} object. - * - * @param canvasGraphics - */ - public G2DWrapper(Graphics2D canvasGraphics, double zoom) { - super(); - this.canvasGraphics = canvasGraphics; - this.zoom = zoom; - currentArea = new Area(); - currentTx = new AffineTransform(); - } - - /** - * Clears out the current area and caches canvas settings. - */ - public void startedDrawingComponent() { - drawingComponent = true; - currentArea = new Area(); - originalStroke = canvasGraphics.getStroke(); - originalColor = canvasGraphics.getColor(); - originalTx = canvasGraphics.getTransform(); - originalComposite = canvasGraphics.getComposite(); - originalFont = canvasGraphics.getFont(); - currentTx = new AffineTransform(); - initialTx = canvasGraphics.getTransform(); - lastShape = null; - startTracking(); - } - - /** - * Reverts {@link Graphics2D} settings and returns area drawn by component in the meantime. - * - * @return - */ - public Area finishedDrawingComponent() { - drawingComponent = false; - canvasGraphics.setStroke(originalStroke); - canvasGraphics.setColor(originalColor); - canvasGraphics.setTransform(originalTx); - canvasGraphics.setComposite(originalComposite); - canvasGraphics.setFont(originalFont); - return currentArea; - } - - @Override - public void startTracking() { - this.trackingAllowed = true; - } - - @Override - public void stopTracking() { - this.trackingAllowed = false; - } - - /** - * Appends shape interior to the current component area. - * - * @param s - */ - private void appendShape(Shape s) { - if (!drawingComponent || !trackingAllowed) { - return; - } - Rectangle2D bounds = s.getBounds2D(); - // Only cache the shape if it's not 1D. - if (bounds.getWidth() > 1 && bounds.getHeight() > 1) { - Area area = new Area(s); - area.transform(currentTx); - currentArea.add(area); - lastShape = s; - } - } - - /** - * Appends shape outline to the current component area. - * - * @param s - */ - private void appendShapeOutline(Shape s) { - // Do not add shape outline if the same shape has been filled recently. - if (!drawingComponent || !trackingAllowed || s.equals(lastShape)) { - return; - } - lastShape = s; - PathIterator pathIterator = s.getPathIterator(null); - double thickness; - if (getStroke() instanceof BasicStroke) { - thickness = ((BasicStroke) getStroke()).getLineWidth() + 2 * LINE_SENSITIVITY_MARGIN; - } else { - thickness = 2 * LINE_SENSITIVITY_MARGIN; - } - Point2D prevPoint = null; - while (!pathIterator.isDone()) { - double[] coord = new double[6]; - int type = pathIterator.currentSegment(coord); - switch (type) { - case PathIterator.SEG_MOVETO: - prevPoint = new Point2D.Double(coord[0], coord[1]); - break; - case PathIterator.SEG_LINETO: - // Represent straight line with a rectangle. - Point2D nextPoint = new Point2D.Double(coord[0], coord[1]); - Double theta = Math.atan2(nextPoint.getY() - prevPoint.getY(), nextPoint.getX() - prevPoint.getX()); - double width = - Math.sqrt(Math.pow(nextPoint.getX() - prevPoint.getX(), 2) - + Math.pow(nextPoint.getY() - prevPoint.getY(), 2)); - double midX = (prevPoint.getX() + nextPoint.getX()) / 2; - double midY = (prevPoint.getY() + nextPoint.getY()) / 2; - Rectangle2D rect = new Rectangle2D.Double(midX - width / 2, midY - thickness / 2, width, thickness); - Area area = new Area(rect); - area.transform(AffineTransform.getRotateInstance(theta, midX, midY)); - // area.transform(currentTx); - appendShape(area); - // Set the prev point to line end - prevPoint = nextPoint; - break; - case PathIterator.SEG_CUBICTO: - CubicCurve2D cubicCurve = - new CubicCurve2D.Double(prevPoint.getX(), prevPoint.getY(), coord[0], coord[1], coord[2], coord[3], - coord[4], coord[5]); - addCubicCurveArea(cubicCurve); - prevPoint = new Point2D.Double(coord[4], coord[5]); - break; - case PathIterator.SEG_QUADTO: - QuadCurve2D quadCurve = - new QuadCurve2D.Double(prevPoint.getX(), prevPoint.getY(), coord[0], coord[1], coord[2], coord[3]); - addQuadCurveArea(quadCurve); - prevPoint = new Point2D.Double(coord[3], coord[3]); - break; - } - pathIterator.next(); - } - } - - /** - * Adds a cubic curve to the component area. It uses subdivision to approximate the curve with a - * series of straight lines. - * - * @param curve - */ - private void addCubicCurveArea(CubicCurve2D curve) { - // When end points are close enough, approximate the curve with a - // straight line. - if (curve.getFlatness() < CURVE_SENSITIVITY) { - appendShapeOutline(new Line2D.Double(curve.getP1(), curve.getP2())); - } else { - CubicCurve2D leftSubcurve = new CubicCurve2D.Double(); - CubicCurve2D rightSubcurve = new CubicCurve2D.Double(); - curve.subdivide(leftSubcurve, rightSubcurve); - addCubicCurveArea(leftSubcurve); - addCubicCurveArea(rightSubcurve); - } - } - - /** - * Adds a quad curve to the component area. It uses subdivision to approximate the curve with a - * series of straight lines. - * - * @param curve - */ - private void addQuadCurveArea(QuadCurve2D curve) { - // When end points are close enough, approximate the curve with a - // straight line. - if (curve.getFlatness() < CURVE_SENSITIVITY) { - appendShapeOutline(new Line2D.Double(curve.getP1(), curve.getP2())); - } else { - QuadCurve2D leftSubcurve = new QuadCurve2D.Double(); - QuadCurve2D rightSubcurve = new QuadCurve2D.Double(); - curve.subdivide(leftSubcurve, rightSubcurve); - addQuadCurveArea(leftSubcurve); - addQuadCurveArea(rightSubcurve); - } - } - - @Override - public void addRenderingHints(Map hints) { - canvasGraphics.addRenderingHints(hints); - } - - @Override - public void clip(Shape s) { - canvasGraphics.clip(s); - } - - @Override - public void draw(Shape s) { - canvasGraphics.draw(s); - if (drawingComponent && trackingAllowed) { - appendShapeOutline(s); - } - } - - @Override - public void drawGlyphVector(GlyphVector g, float x, float y) { - canvasGraphics.drawGlyphVector(g, x, y); - } - - @Override - public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) { - // FIXME: process map - return canvasGraphics.drawImage(img, xform, obs); - } - - @Override - public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) { - canvasGraphics.drawImage(img, op, x, y); - // FIXME: process map - } - - @Override - public void drawRenderableImage(RenderableImage img, AffineTransform xform) { - canvasGraphics.drawRenderableImage(img, xform); - // FIXME: process map - } - - @Override - public void drawRenderedImage(RenderedImage img, AffineTransform xform) { - canvasGraphics.drawRenderedImage(img, xform); - // FIXME: process map - } - - @Override - public void drawString(String str, int x, int y) { - canvasGraphics.drawString(str, x, y); - if (drawingComponent && trackingAllowed) { - FontMetrics fontMetrics = canvasGraphics.getFontMetrics(); - Rectangle2D rect = fontMetrics.getStringBounds(str, canvasGraphics); - Point2D point = new Point2D.Double(x, y); - // currentTx.transform(point, point); - Rectangle2D finalRec = - new Rectangle2D.Double(rect.getX() + point.getX(), rect.getY() + point.getY(), rect.getWidth(), - rect.getHeight()); - appendShape(finalRec); - } - } - - @Override - public void drawString(String str, float x, float y) { - canvasGraphics.drawString(str, x, y); - if (drawingComponent && trackingAllowed) { - FontMetrics fontMetrics = canvasGraphics.getFontMetrics(); - Rectangle2D rect = fontMetrics.getStringBounds(str, canvasGraphics); - appendShape(new Rectangle2D.Double(rect.getX() + x, rect.getY() + y, rect.getWidth(), rect.getHeight())); - } - } - - @Override - public void drawString(AttributedCharacterIterator iterator, int x, int y) { - canvasGraphics.drawString(iterator, x, y); - // FIXME: process map - } - - @Override - public void drawString(AttributedCharacterIterator iterator, float x, float y) { - canvasGraphics.drawString(iterator, x, y); - // FIXME: process map - } - - @Override - public void fill(Shape s) { - canvasGraphics.fill(s); - appendShape(s); - } - - @Override - public Color getBackground() { - return canvasGraphics.getBackground(); - } - - @Override - public Composite getComposite() { - return canvasGraphics.getComposite(); - } - - @Override - public GraphicsConfiguration getDeviceConfiguration() { - return canvasGraphics.getDeviceConfiguration(); - } - - @Override - public FontRenderContext getFontRenderContext() { - return canvasGraphics.getFontRenderContext(); - } - - @Override - public Paint getPaint() { - return canvasGraphics.getPaint(); - } - - @Override - public Object getRenderingHint(Key hintKey) { - return canvasGraphics.getRenderingHint(hintKey); - } - - @Override - public RenderingHints getRenderingHints() { - return canvasGraphics.getRenderingHints(); - } - - @Override - public Stroke getStroke() { - return canvasGraphics.getStroke(); - } - - @Override - public AffineTransform getTransform() { - return canvasGraphics.getTransform(); - } - - @Override - public boolean hit(Rectangle rect, Shape s, boolean onStroke) { - return canvasGraphics.hit(rect, s, onStroke); - } - - @Override - public void rotate(double theta) { - canvasGraphics.rotate(theta); - currentTx.rotate(theta); - } - - @Override - public void rotate(double theta, double x, double y) { - canvasGraphics.rotate(theta, x, y); - currentTx.rotate(theta, x, y); - } - - @Override - public void scale(double sx, double sy) { - canvasGraphics.scale(sx, sy); - currentTx.scale(sx, sy); - } - - @Override - public void setBackground(Color color) { - canvasGraphics.setBackground(color); - // FIXME: fix map - } - - @Override - public void setComposite(Composite comp) { - canvasGraphics.setComposite(comp); - // FIXME: check this. - } - - @Override - public void setPaint(Paint paint) { - canvasGraphics.setPaint(paint); - // FIXME: check this - } - - @Override - public void setRenderingHint(Key hintKey, Object hintValue) { - canvasGraphics.setRenderingHint(hintKey, hintValue); - // FIXME: check this - } - - @Override - public void setRenderingHints(Map hints) { - canvasGraphics.setRenderingHints(hints); - // FIXME: check this - } - - @Override - public void setStroke(Stroke s) { - if (this.zoom > 1 && s.getClass() == BasicStroke.class) { - BasicStroke bs = (BasicStroke) s; - // make thin lines even thinner to compensate for zoom factor - if (bs.getLineWidth() <= 2) - s = ObjectCache.getInstance().fetchBasicStroke((float) (bs.getLineWidth() / zoom)); - } - canvasGraphics.setStroke(s); - } - - @Override - public void setTransform(AffineTransform Tx) { - canvasGraphics.setTransform(Tx); - currentTx = new AffineTransform(Tx); - try { - // Invert the tx that was set before we started drawing the component. - // We're left only component tx. - AffineTransform inverseInitialTx = initialTx.createInverse(); - Point2D p = new Point2D.Double(currentTx.getTranslateX(), currentTx.getTranslateY()); - inverseInitialTx.transform(p, p); - currentTx.concatenate(inverseInitialTx); - double[] matrix = new double[6]; - currentTx.getMatrix(matrix); - currentTx.setTransform(matrix[0], matrix[1], matrix[2], matrix[3], p.getX(), p.getY()); - } catch (NoninvertibleTransformException e) { - } - } - - @Override - public void shear(double shx, double shy) { - canvasGraphics.shear(shx, shy); - currentTx.shear(shx, shy); - } - - @Override - public void transform(AffineTransform Tx) { - canvasGraphics.transform(Tx); - currentTx.concatenate(Tx); - } - - @Override - public void translate(int x, int y) { - canvasGraphics.translate(x, y); - currentTx.translate(x, y); - } - - @Override - public void translate(double tx, double ty) { - canvasGraphics.translate(tx, ty); - currentTx.translate(tx, ty); - } - - @Override - public void clearRect(int x, int y, int width, int height) { - canvasGraphics.clearRect(x, y, width, height); - } - - @Override - public void clipRect(int x, int y, int width, int height) { - canvasGraphics.clipRect(x, y, width, height); - } - - @Override - public void copyArea(int x, int y, int width, int height, int dx, int dy) { - canvasGraphics.copyArea(x, y, width, height, dx, dy); - } - - @Override - public Graphics create() { - return canvasGraphics.create(); - } - - @Override - public void dispose() { - canvasGraphics.dispose(); - } - - @Override - public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) { - canvasGraphics.drawArc(x, y, width, height, startAngle, arcAngle); - } - - @Override - public boolean drawImage(Image img, int x, int y, ImageObserver observer) { - boolean result = canvasGraphics.drawImage(img, x, y, observer); - appendShape(new Rectangle2D.Double(x, y, img.getWidth(observer), img.getHeight(observer))); - return result; - } - - @Override - public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) { - // FIXME: map - return canvasGraphics.drawImage(img, x, y, bgcolor, observer); - } - - @Override - public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) { - // FIXME: map - return canvasGraphics.drawImage(img, x, y, width, height, observer); - } - - @Override - public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) { - // FIXME: map - return canvasGraphics.drawImage(img, x, y, width, height, bgcolor, observer); - } - - @Override - public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, - ImageObserver observer) { - // FIXME: map - return canvasGraphics.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer); - } - - @Override - public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, - Color bgcolor, ImageObserver observer) { - // FIXME: map - return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer); - } - - @Override - public void drawLine(int x1, int y1, int x2, int y2) { - canvasGraphics.drawLine(x1, y1, x2, y2); - if (drawingComponent && trackingAllowed) { - appendShapeOutline(new Line2D.Double(x1, y1, x2, y2)); - } - } - - @Override - public void drawOval(int x, int y, int width, int height) { - canvasGraphics.drawOval(x, y, width, height); - if (drawingComponent && trackingAllowed) { - appendShapeOutline(new Ellipse2D.Double(x, y, width, height)); - } - } - - @Override - public void drawPolygon(int[] points, int[] points2, int points3) { - canvasGraphics.drawPolygon(points, points2, points3); - if (drawingComponent && trackingAllowed) { - appendShapeOutline(new Polygon(points, points2, points3)); - } - } - - @Override - public void drawPolyline(int[] points, int[] points2, int points3) { - canvasGraphics.drawPolyline(points, points2, points3); - } - - @Override - public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { - canvasGraphics.drawRoundRect(x, y, width, height, arcWidth, arcHeight); - if (drawingComponent && trackingAllowed) { - appendShapeOutline(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight)); - } - } - - @Override - public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) { - canvasGraphics.fillArc(x, y, width, height, startAngle, arcAngle); - if (drawingComponent && trackingAllowed) { - appendShape(new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.PIE)); - } - } - - @Override - public void fillOval(int x, int y, int width, int height) { - canvasGraphics.fillOval(x, y, width, height); - if (drawingComponent && trackingAllowed) { - appendShape(new Ellipse2D.Double(x, y, width, height)); - } - } - - @Override - public void fillPolygon(int[] points, int[] points2, int points3) { - canvasGraphics.fillPolygon(points, points2, points3); - if (drawingComponent && trackingAllowed) { - appendShape(new Polygon(points, points2, points3)); - } - } - - @Override - public void drawRect(int x, int y, int width, int height) { - canvasGraphics.drawRect(x, y, width, height); - if (drawingComponent && trackingAllowed) { - appendShapeOutline(new Rectangle(x, y, width, height)); - } - } - - @Override - public void fillRect(int x, int y, int width, int height) { - canvasGraphics.fillRect(x, y, width, height); - if (drawingComponent && trackingAllowed) { - appendShape(new Rectangle(x, y, width, height)); - } - } - - @Override - public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { - canvasGraphics.fillRoundRect(x, y, width, height, arcWidth, arcHeight); - if (drawingComponent && trackingAllowed) { - appendShape(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight)); - } - } - - @Override - public Shape getClip() { - return canvasGraphics.getClip(); - } - - @Override - public Rectangle getClipBounds() { - return canvasGraphics.getClipBounds(); - } - - @Override - public Color getColor() { - return canvasGraphics.getColor(); - } - - @Override - public Font getFont() { - return canvasGraphics.getFont(); - } - - @Override - public FontMetrics getFontMetrics(Font f) { - return canvasGraphics.getFontMetrics(f); - } - - @Override - public void setClip(Shape clip) { - canvasGraphics.setClip(clip); - } - - @Override - public void setClip(int x, int y, int width, int height) { - canvasGraphics.setClip(x, y, width, height); - } - - @Override - public void setColor(Color c) { - canvasGraphics.setColor(c); - } - - @Override - public void setFont(Font font) { - canvasGraphics.setFont(font); - } - - @Override - public void setPaintMode() { - canvasGraphics.setPaintMode(); - } - - @Override - public void setXORMode(Color c1) { - canvasGraphics.setXORMode(c1); - } -} + private Graphics2D canvasGraphics; + private Stroke originalStroke; + private Color originalColor; + private Composite originalComposite; + private AffineTransform originalTx; + private Font originalFont; + private AffineTransform currentTx; + private AffineTransform initialTx; + private Area currentArea; + private List currentContinuityPositiveAreas; + private List currentContinuityNegativeAreas; + private Shape lastShape; + + private double zoom; + + /** + * Creates a wrapper around specified {@link Graphics2D} object. + * + * @param canvasGraphics + */ + public G2DWrapper(Graphics2D canvasGraphics, double zoom) { + super(); + this.canvasGraphics = canvasGraphics; + this.zoom = zoom; + currentArea = new Area(); + currentContinuityPositiveAreas = new ArrayList(); + currentContinuityNegativeAreas = new ArrayList(); + currentTx = new AffineTransform(); + } + + /** + * Clears out the current area and caches canvas settings. + */ + public void startedDrawingComponent() { + drawingComponent = true; + currentArea = new Area(); + currentContinuityPositiveAreas = new ArrayList(); + currentContinuityNegativeAreas = new ArrayList(); + originalStroke = canvasGraphics.getStroke(); + originalColor = canvasGraphics.getColor(); + originalTx = canvasGraphics.getTransform(); + originalComposite = canvasGraphics.getComposite(); + originalFont = canvasGraphics.getFont(); + currentTx = new AffineTransform(); + initialTx = canvasGraphics.getTransform(); + lastShape = null; + startTracking(); + } + + /** + * Reverts {@link Graphics2D} settings and returns area drawn by component in the meantime. + * + * @return + */ + public ComponentArea finishedDrawingComponent() { + drawingComponent = false; + canvasGraphics.setStroke(originalStroke); + canvasGraphics.setColor(originalColor); + canvasGraphics.setTransform(originalTx); + canvasGraphics.setComposite(originalComposite); + canvasGraphics.setFont(originalFont); + return new ComponentArea(currentArea, currentContinuityPositiveAreas, currentContinuityNegativeAreas); + } + + @Override + public void startTracking() { + this.trackingAllowed = true; + } + + @Override + public void stopTracking() { + this.trackingAllowed = false; + } + + @Override + public void startTrackingContinuityArea(boolean positive) { + this.trackingContinuityAllowed = true; + this.trackingContinuityPositive = positive; + } + + @Override + public void stopTrackingContinuityArea() { + this.trackingContinuityAllowed = false; + } + + + /** + * Appends shape interior to the current component area. + * + * @param s + */ + private void appendShape(Shape s) { + if (!drawingComponent || (!trackingAllowed && !trackingContinuityAllowed)) { + 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); + + if (trackingAllowed) + currentArea.add(area); + + if (trackingContinuityAllowed) { + if (trackingContinuityPositive) + currentContinuityPositiveAreas.add(area); + else + currentContinuityNegativeAreas.add(area); + } + lastShape = s; + } + } + + /** + * Appends shape outline to the current component area. + * + * @param s + */ + private void appendShapeOutline(Shape s) { + // Do not add shape outline if the same shape has been filled recently. + if (!drawingComponent || !trackingAllowed || s.equals(lastShape)) { + return; + } + lastShape = s; + PathIterator pathIterator = s.getPathIterator(null); + double thickness; + if (getStroke() instanceof BasicStroke) { + thickness = ((BasicStroke) getStroke()).getLineWidth() + 2 * LINE_SENSITIVITY_MARGIN; + } else { + thickness = 2 * LINE_SENSITIVITY_MARGIN; + } + Point2D prevPoint = null; + while (!pathIterator.isDone()) { + double[] coord = new double[6]; + int type = pathIterator.currentSegment(coord); + switch (type) { + case PathIterator.SEG_MOVETO: + prevPoint = new Point2D.Double(coord[0], coord[1]); + break; + case PathIterator.SEG_LINETO: + // Represent straight line with a rectangle. + Point2D nextPoint = new Point2D.Double(coord[0], coord[1]); + Double theta = Math.atan2(nextPoint.getY() - prevPoint.getY(), nextPoint.getX() - prevPoint.getX()); + double width = + Math.sqrt(Math.pow(nextPoint.getX() - prevPoint.getX(), 2) + + Math.pow(nextPoint.getY() - prevPoint.getY(), 2)); + double midX = (prevPoint.getX() + nextPoint.getX()) / 2; + double midY = (prevPoint.getY() + nextPoint.getY()) / 2; + Rectangle2D rect = new Rectangle2D.Double(midX - width / 2, midY - thickness / 2, width, thickness); + Area area = new Area(rect); + area.transform(AffineTransform.getRotateInstance(theta, midX, midY)); + // area.transform(currentTx); + appendShape(area); + // Set the prev point to line end + prevPoint = nextPoint; + break; + case PathIterator.SEG_CUBICTO: + CubicCurve2D cubicCurve = + new CubicCurve2D.Double(prevPoint.getX(), prevPoint.getY(), coord[0], coord[1], coord[2], coord[3], + coord[4], coord[5]); + addCubicCurveArea(cubicCurve); + prevPoint = new Point2D.Double(coord[4], coord[5]); + break; + case PathIterator.SEG_QUADTO: + QuadCurve2D quadCurve = + new QuadCurve2D.Double(prevPoint.getX(), prevPoint.getY(), coord[0], coord[1], coord[2], coord[3]); + addQuadCurveArea(quadCurve); + prevPoint = new Point2D.Double(coord[3], coord[3]); + break; + } + pathIterator.next(); + } + } + + /** + * Adds a cubic curve to the component area. It uses subdivision to approximate the curve with a + * series of straight lines. + * + * @param curve + */ + private void addCubicCurveArea(CubicCurve2D curve) { + // When end points are close enough, approximate the curve with a + // straight line. + if (curve.getFlatness() < CURVE_SENSITIVITY) { + appendShapeOutline(new Line2D.Double(curve.getP1(), curve.getP2())); + } else { + CubicCurve2D leftSubcurve = new CubicCurve2D.Double(); + CubicCurve2D rightSubcurve = new CubicCurve2D.Double(); + curve.subdivide(leftSubcurve, rightSubcurve); + addCubicCurveArea(leftSubcurve); + addCubicCurveArea(rightSubcurve); + } + } + + /** + * Adds a quad curve to the component area. It uses subdivision to approximate the curve with a + * series of straight lines. + * + * @param curve + */ + private void addQuadCurveArea(QuadCurve2D curve) { + // When end points are close enough, approximate the curve with a + // straight line. + if (curve.getFlatness() < CURVE_SENSITIVITY) { + appendShapeOutline(new Line2D.Double(curve.getP1(), curve.getP2())); + } else { + QuadCurve2D leftSubcurve = new QuadCurve2D.Double(); + QuadCurve2D rightSubcurve = new QuadCurve2D.Double(); + curve.subdivide(leftSubcurve, rightSubcurve); + addQuadCurveArea(leftSubcurve); + addQuadCurveArea(rightSubcurve); + } + } + + @Override + public void addRenderingHints(Map hints) { + canvasGraphics.addRenderingHints(hints); + } + + @Override + public void clip(Shape s) { + canvasGraphics.clip(s); + } + + @Override + public void draw(Shape s) { + canvasGraphics.draw(s); + if (drawingComponent && trackingAllowed) { + appendShapeOutline(s); + } + } + + @Override + public void drawGlyphVector(GlyphVector g, float x, float y) { + canvasGraphics.drawGlyphVector(g, x, y); + } + + @Override + public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) { + // FIXME: process map + return canvasGraphics.drawImage(img, xform, obs); + } + + @Override + public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) { + canvasGraphics.drawImage(img, op, x, y); + // FIXME: process map + } + + @Override + public void drawRenderableImage(RenderableImage img, AffineTransform xform) { + canvasGraphics.drawRenderableImage(img, xform); + // FIXME: process map + } + + @Override + public void drawRenderedImage(RenderedImage img, AffineTransform xform) { + canvasGraphics.drawRenderedImage(img, xform); + // FIXME: process map + } + + @Override + public void drawString(String str, int x, int y) { + canvasGraphics.drawString(str, x, y); + if (drawingComponent && trackingAllowed) { + FontMetrics fontMetrics = canvasGraphics.getFontMetrics(); + Rectangle2D rect = fontMetrics.getStringBounds(str, canvasGraphics); + Point2D point = new Point2D.Double(x, y); + // currentTx.transform(point, point); + Rectangle2D finalRec = + new Rectangle2D.Double(rect.getX() + point.getX(), rect.getY() + point.getY(), rect.getWidth(), + rect.getHeight()); + appendShape(finalRec); + } + } + + @Override + public void drawString(String str, float x, float y) { + canvasGraphics.drawString(str, x, y); + if (drawingComponent && trackingAllowed) { + FontMetrics fontMetrics = canvasGraphics.getFontMetrics(); + Rectangle2D rect = fontMetrics.getStringBounds(str, canvasGraphics); + appendShape(new Rectangle2D.Double(rect.getX() + x, rect.getY() + y, rect.getWidth(), rect.getHeight())); + } + } + + @Override + public void drawString(AttributedCharacterIterator iterator, int x, int y) { + canvasGraphics.drawString(iterator, x, y); + // FIXME: process map + } + + @Override + public void drawString(AttributedCharacterIterator iterator, float x, float y) { + canvasGraphics.drawString(iterator, x, y); + // FIXME: process map + } + + @Override + public void fill(Shape s) { + canvasGraphics.fill(s); + appendShape(s); + } + + @Override + public Color getBackground() { + return canvasGraphics.getBackground(); + } + + @Override + public Composite getComposite() { + return canvasGraphics.getComposite(); + } + + @Override + public GraphicsConfiguration getDeviceConfiguration() { + return canvasGraphics.getDeviceConfiguration(); + } + + @Override + public FontRenderContext getFontRenderContext() { + return canvasGraphics.getFontRenderContext(); + } + + @Override + public Paint getPaint() { + return canvasGraphics.getPaint(); + } + + @Override + public Object getRenderingHint(Key hintKey) { + return canvasGraphics.getRenderingHint(hintKey); + } + + @Override + public RenderingHints getRenderingHints() { + return canvasGraphics.getRenderingHints(); + } + + @Override + public Stroke getStroke() { + return canvasGraphics.getStroke(); + } + + @Override + public AffineTransform getTransform() { + return canvasGraphics.getTransform(); + } + + @Override + public boolean hit(Rectangle rect, Shape s, boolean onStroke) { + return canvasGraphics.hit(rect, s, onStroke); + } + + @Override + public void rotate(double theta) { + canvasGraphics.rotate(theta); + currentTx.rotate(theta); + } + + @Override + public void rotate(double theta, double x, double y) { + canvasGraphics.rotate(theta, x, y); + currentTx.rotate(theta, x, y); + } + + @Override + public void scale(double sx, double sy) { + canvasGraphics.scale(sx, sy); + currentTx.scale(sx, sy); + } + + @Override + public void setBackground(Color color) { + canvasGraphics.setBackground(color); + // FIXME: fix map + } + + @Override + public void setComposite(Composite comp) { + canvasGraphics.setComposite(comp); + // FIXME: check this. + } + + @Override + public void setPaint(Paint paint) { + canvasGraphics.setPaint(paint); + // FIXME: check this + } + + @Override + public void setRenderingHint(Key hintKey, Object hintValue) { + canvasGraphics.setRenderingHint(hintKey, hintValue); + // FIXME: check this + } + + @Override + public void setRenderingHints(Map hints) { + canvasGraphics.setRenderingHints(hints); + // FIXME: check this + } + + @Override + public void setStroke(Stroke s) { + if (this.zoom > 1 && s.getClass() == BasicStroke.class) { + BasicStroke bs = (BasicStroke) s; + // make thin lines even thinner to compensate for zoom factor + if (bs.getLineWidth() <= 2) + s = ObjectCache.getInstance().fetchBasicStroke((float) (bs.getLineWidth() / zoom)); + } + canvasGraphics.setStroke(s); + } + + @Override + public void setTransform(AffineTransform Tx) { + canvasGraphics.setTransform(Tx); + currentTx = new AffineTransform(Tx); + try { + // Invert the tx that was set before we started drawing the component. + // We're left only component tx. + AffineTransform inverseInitialTx = initialTx.createInverse(); + Point2D p = new Point2D.Double(currentTx.getTranslateX(), currentTx.getTranslateY()); + inverseInitialTx.transform(p, p); + currentTx.concatenate(inverseInitialTx); + double[] matrix = new double[6]; + currentTx.getMatrix(matrix); + currentTx.setTransform(matrix[0], matrix[1], matrix[2], matrix[3], p.getX(), p.getY()); + } catch (NoninvertibleTransformException e) { + } + } + + @Override + public void shear(double shx, double shy) { + canvasGraphics.shear(shx, shy); + currentTx.shear(shx, shy); + } + + @Override + public void transform(AffineTransform Tx) { + canvasGraphics.transform(Tx); + currentTx.concatenate(Tx); + } + + @Override + public void translate(int x, int y) { + canvasGraphics.translate(x, y); + currentTx.translate(x, y); + } + + @Override + public void translate(double tx, double ty) { + canvasGraphics.translate(tx, ty); + currentTx.translate(tx, ty); + } + + @Override + public void clearRect(int x, int y, int width, int height) { + canvasGraphics.clearRect(x, y, width, height); + } + + @Override + public void clipRect(int x, int y, int width, int height) { + canvasGraphics.clipRect(x, y, width, height); + } + + @Override + public void copyArea(int x, int y, int width, int height, int dx, int dy) { + canvasGraphics.copyArea(x, y, width, height, dx, dy); + } + + @Override + public Graphics create() { + return canvasGraphics.create(); + } + + @Override + public void dispose() { + canvasGraphics.dispose(); + } + + @Override + public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) { + canvasGraphics.drawArc(x, y, width, height, startAngle, arcAngle); + } + + @Override + public boolean drawImage(Image img, int x, int y, ImageObserver observer) { + boolean result = canvasGraphics.drawImage(img, x, y, observer); + appendShape(new Rectangle2D.Double(x, y, img.getWidth(observer), img.getHeight(observer))); + return result; + } + + @Override + public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) { + // FIXME: map + return canvasGraphics.drawImage(img, x, y, bgcolor, observer); + } + + @Override + public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) { + // FIXME: map + return canvasGraphics.drawImage(img, x, y, width, height, observer); + } + + @Override + public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) { + // FIXME: map + return canvasGraphics.drawImage(img, x, y, width, height, bgcolor, observer); + } + + @Override + public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, + ImageObserver observer) { + // FIXME: map + return canvasGraphics.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer); + } + + @Override + public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, + Color bgcolor, ImageObserver observer) { + // FIXME: map + return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer); + } + + @Override + public void drawLine(int x1, int y1, int x2, int y2) { + canvasGraphics.drawLine(x1, y1, x2, y2); + if (drawingComponent && (trackingAllowed || trackingContinuityAllowed)) { + 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 || trackingContinuityAllowed)) { + 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 || trackingContinuityAllowed)) { + 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 || trackingContinuityAllowed)) { + 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 || trackingContinuityAllowed)) { + 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 || trackingContinuityAllowed)) { + 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 || trackingContinuityAllowed)) { + 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 || trackingContinuityAllowed)) { + 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 || trackingContinuityAllowed)) { + 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 || trackingContinuityAllowed)) { + 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/diylc/diylc-core/src/org/diylc/presenter/InstantiationManager.java b/diylc/diylc-core/src/org/diylc/presenter/InstantiationManager.java index 6b44885a..7ef31690 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/InstantiationManager.java +++ b/diylc/diylc-core/src/org/diylc/presenter/InstantiationManager.java @@ -63,7 +63,7 @@ public class InstantiationManager { private static final ComponentType clipboardType = new ComponentType("Clipboard contents", "Components from the clipboard", CreationMethod.SINGLE_CLICK, "Multi", "", "", null, null, 0, false, false, null, - false, null, KeywordPolicy.NEVER_SHOW, null); + false, null, KeywordPolicy.NEVER_SHOW, null, false); public InstantiationManager() {} diff --git a/diylc/diylc-core/src/org/diylc/presenter/Presenter.java b/diylc/diylc-core/src/org/diylc/presenter/Presenter.java index eba2a4d8..d53ea4fc 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/Presenter.java +++ b/diylc/diylc-core/src/org/diylc/presenter/Presenter.java @@ -21,63 +21,63 @@ */ package org.diylc.presenter; -import java.awt.Cursor; -import java.awt.Dimension; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.dnd.DnDConstants; -import java.awt.geom.AffineTransform; -import java.awt.geom.Area; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.swing.JOptionPane; - -import org.apache.log4j.Logger; -import org.diylc.appframework.miscutils.ConfigurationManager; -import org.diylc.appframework.miscutils.Utils; -import org.diylc.appframework.simplemq.MessageDispatcher; -import org.diylc.appframework.update.Version; -import org.diylc.appframework.update.VersionNumber; -import org.diylc.common.ComponentType; -import org.diylc.common.DrawOption; -import org.diylc.common.EventType; -import org.diylc.common.IComponentFiler; -import org.diylc.common.IComponentTransformer; -import org.diylc.common.IKeyProcessor; -import org.diylc.common.IPlugIn; -import org.diylc.common.IPlugInPort; -import org.diylc.common.PropertyWrapper; -import org.diylc.core.ExpansionMode; -import org.diylc.core.IDIYComponent; -import org.diylc.core.IView; -import org.diylc.core.Project; -import org.diylc.core.Template; -import org.diylc.core.Theme; -import org.diylc.core.annotations.IAutoCreator; -import org.diylc.core.measures.Size; -import org.diylc.core.measures.SizeUnit; -import org.diylc.utils.Constants; - -import com.thoughtworks.xstream.XStream; -import com.thoughtworks.xstream.io.xml.DomDriver; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.dnd.DnDConstants; +import java.awt.geom.AffineTransform; +import java.awt.geom.Area; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.swing.JOptionPane; + +import org.apache.log4j.Logger; +import org.diylc.appframework.miscutils.ConfigurationManager; +import org.diylc.appframework.miscutils.Utils; +import org.diylc.appframework.simplemq.MessageDispatcher; +import org.diylc.appframework.update.Version; +import org.diylc.appframework.update.VersionNumber; +import org.diylc.common.ComponentType; +import org.diylc.common.DrawOption; +import org.diylc.common.EventType; +import org.diylc.common.IComponentFiler; +import org.diylc.common.IComponentTransformer; +import org.diylc.common.IKeyProcessor; +import org.diylc.common.IPlugIn; +import org.diylc.common.IPlugInPort; +import org.diylc.common.PropertyWrapper; +import org.diylc.core.ExpansionMode; +import org.diylc.core.IDIYComponent; +import org.diylc.core.IView; +import org.diylc.core.Project; +import org.diylc.core.Template; +import org.diylc.core.Theme; +import org.diylc.core.annotations.IAutoCreator; +import org.diylc.core.measures.Size; +import org.diylc.core.measures.SizeUnit; +import org.diylc.utils.Constants; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.DomDriver; /** * The main presenter class, contains core app logic and drawing routines. @@ -132,7 +132,7 @@ public class Presenter implements IPlugInPort { private ProjectFileManager projectFileManager; private InstantiationManager instantiationManager; - private Rectangle selectionRect; + private Rectangle selectionRect; private final IView view; @@ -148,7 +148,7 @@ public class Presenter implements IPlugInPort { private Point previousDragPoint = null; private Project preDragProject = null; private int dragAction; - private Point previousScaledPoint; + private Point previousScaledPoint; public Presenter(IView view) { super(); @@ -212,8 +212,8 @@ public class Presenter implements IPlugInPort { } for (IDIYComponent component : currentProject.getComponents()) { if (!isComponentLocked(component) && isComponentVisible(component)) { - Area area = drawingManager.getComponentArea(component); - if (area != null && area.contains(scaledPoint)) { + ComponentArea area = drawingManager.getComponentArea(component); + if (area != null && area.getOutlineArea() != null && area.getOutlineArea().contains(scaledPoint)) { return Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); } } @@ -599,7 +599,10 @@ public class Presenter implements IPlugInPort { messageDispatcher.dispatchMessage(EventType.PROJECT_MODIFIED, oldProject, currentProject.clone(), "Add " + componentTypeSlot.getName()); projectFileManager.notifyFileChange(); - } + } + } else if (ConfigurationManager.getInstance().readBoolean(HIGHLIGHT_CONTINUITY_AREA, false)) { + drawingManager.findContinuityAreaAtPoint(currentProject, scaledPoint); + messageDispatcher.dispatchMessage(EventType.REPAINT); } else { List> newSelection = new ArrayList>(selectedComponents); List> components = findComponentsAtScaled(scaledPoint); @@ -867,11 +870,11 @@ public class Presenter implements IPlugInPort { Area totalArea = new Area(); for (IDIYComponent c : selectedComponents) { - Area compArea = drawingManager.getComponentArea(c); - if (compArea != null) - totalArea.add(compArea); + ComponentArea compArea = drawingManager.getComponentArea(c); + if (compArea != null && compArea.getOutlineArea() != null) + totalArea.add(compArea.getOutlineArea()); else - LOG.info("Area is null"); + LOG.debug("Area is null"); } if (drawingManager.getZoomLevel() != 1 && applyZoom) { totalArea @@ -1397,8 +1400,8 @@ public class Presenter implements IPlugInPort { List> newSelection = new ArrayList>(); for (IDIYComponent component : currentProject.getComponents()) { if (!isComponentLocked(component) && isComponentVisible(component)) { - Area area = drawingManager.getComponentArea(component); - if ((area != null) && (selectionRect != null) && area.intersects(selectionRect)) { + ComponentArea area = drawingManager.getComponentArea(component); + if ((area != null && area.getOutlineArea() != null) && (selectionRect != null) && area.getOutlineArea().intersects(selectionRect)) { newSelection.addAll(findAllGroupedComponents(component)); } } @@ -1801,16 +1804,16 @@ public class Presenter implements IPlugInPort { for (IDIYComponent component : getCurrentProject().getComponents()) { // Skip already selected components or ones that cannot be stuck to // other components. - Area area = drawingManager.getComponentArea(component); - if (newSelection.contains(component) || !component.isControlPointSticky(0) || area == null) + ComponentArea area = drawingManager.getComponentArea(component); + if (newSelection.contains(component) || !component.isControlPointSticky(0) || area == null || area.getOutlineArea() == null) continue; boolean matches = false; for (IDIYComponent selectedComponent : this.selectedComponents) { - Area selectedArea = drawingManager.getComponentArea(selectedComponent); - if (selectedArea == null) + ComponentArea selectedArea = drawingManager.getComponentArea(selectedComponent); + if (selectedArea == null || selectedArea.getOutlineArea() == null) continue; - Area intersection = new Area(area); - intersection.intersect(selectedArea); + Area intersection = new Area(area.getOutlineArea()); + intersection.intersect(selectedArea.getOutlineArea()); if (!intersection.isEmpty()) { matches = true; break; diff --git a/diylc/diylc-library/lib/diylc-core.jar b/diylc/diylc-library/lib/diylc-core.jar index a5a4c34f..493dad85 100644 Binary files a/diylc/diylc-library/lib/diylc-core.jar and b/diylc/diylc-library/lib/diylc-core.jar differ diff --git a/diylc/diylc-library/src/org/diylc/components/boards/TerminalStrip.java b/diylc/diylc-library/src/org/diylc/components/boards/TerminalStrip.java index ea6e6f97..d1f64211 100644 --- a/diylc/diylc-library/src/org/diylc/components/boards/TerminalStrip.java +++ b/diylc/diylc-library/src/org/diylc/components/boards/TerminalStrip.java @@ -376,8 +376,10 @@ public class TerminalStrip extends AbstractTransparentComponent { g2d.setColor(finalBorderColor); g2d.draw(mainArea); - g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : TERMINAL_COLOR); - g2d.fill(terminalArea); + g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : TERMINAL_COLOR); + drawingObserver.startTrackingContinuityArea(true); + g2d.fill(terminalArea); + drawingObserver.stopTrackingContinuityArea(); Color finalTerminalBorderColor; if (outlineMode) { diff --git a/diylc/diylc-library/src/org/diylc/components/boards/TriPadBoard.java b/diylc/diylc-library/src/org/diylc/components/boards/TriPadBoard.java index 3fee6f1b..ab77cd15 100644 --- a/diylc/diylc-library/src/org/diylc/components/boards/TriPadBoard.java +++ b/diylc/diylc-library/src/org/diylc/components/boards/TriPadBoard.java @@ -94,9 +94,11 @@ public class TriPadBoard extends AbstractBoard { spacesToDraw = (remainingSpace - stripSize) / spacing; } - g2d.setColor(stripColor); + g2d.setColor(stripColor); + drawingObserver.startTrackingContinuityArea(true); g2d.fillRect(p.x + spacing - stripSize / 2, p.y - stripSize / 2, spacing * (spacesToDraw - 1) + stripSize, - stripSize); + stripSize); + drawingObserver.stopTrackingContinuityArea(); g2d.setColor(stripColor.darker()); g2d.drawRect(p.x + spacing - stripSize / 2, p.y - stripSize / 2, spacing * (spacesToDraw - 1) + stripSize, @@ -130,9 +132,11 @@ public class TriPadBoard extends AbstractBoard { spacesToDraw = (remainingSpace - stripSize) / spacing; } - g2d.setColor(stripColor); + g2d.setColor(stripColor); + drawingObserver.startTrackingContinuityArea(true); g2d.fillRect(p.x - stripSize / 2, p.y + spacing - stripSize / 2, stripSize, spacing * (spacesToDraw - 1) - + stripSize); + + stripSize); + drawingObserver.stopTrackingContinuityArea(); g2d.setColor(stripColor.darker()); g2d.drawRect(p.x - stripSize / 2, p.y + spacing - stripSize / 2, stripSize, spacing * (spacesToDraw - 1) + stripSize); diff --git a/diylc/diylc-library/src/org/diylc/components/boards/VeroBoard.java b/diylc/diylc-library/src/org/diylc/components/boards/VeroBoard.java index 392a34c5..18e32707 100644 --- a/diylc/diylc-library/src/org/diylc/components/boards/VeroBoard.java +++ b/diylc/diylc-library/src/org/diylc/components/boards/VeroBoard.java @@ -84,8 +84,10 @@ public class VeroBoard extends AbstractBoard { while (p.y < secondPoint.y - spacing) { p.x = firstPoint.x; p.y += spacing; - g2d.setColor(stripColor); - g2d.fillRect(p.x + spacing / 2, p.y - stripSize / 2, secondPoint.x - spacing - p.x, stripSize); + g2d.setColor(stripColor); + drawingObserver.startTrackingContinuityArea(true); + g2d.fillRect(p.x + spacing / 2, p.y - stripSize / 2, secondPoint.x - spacing - p.x, stripSize); + drawingObserver.stopTrackingContinuityArea(); g2d.setColor(stripColor.darker()); g2d.drawRect(p.x + spacing / 2, p.y - stripSize / 2, secondPoint.x - spacing - p.x, stripSize); while (p.x < secondPoint.x - spacing - holeSize) { @@ -100,8 +102,10 @@ public class VeroBoard extends AbstractBoard { while (p.x < secondPoint.x - spacing) { p.x += spacing; p.y = firstPoint.y; - g2d.setColor(stripColor); - g2d.fillRect(p.x - stripSize / 2, p.y + spacing / 2, stripSize, secondPoint.y - spacing - p.y); + g2d.setColor(stripColor); + drawingObserver.startTrackingContinuityArea(true); + g2d.fillRect(p.x - stripSize / 2, p.y + spacing / 2, stripSize, secondPoint.y - spacing - p.y); + drawingObserver.stopTrackingContinuityArea(); g2d.setColor(stripColor.darker()); g2d.drawRect(p.x - stripSize / 2, p.y + spacing / 2, stripSize, secondPoint.y - spacing - p.y); while (p.y < secondPoint.y - spacing - holeSize) { diff --git a/diylc/diylc-library/src/org/diylc/components/connectivity/CurvedTrace.java b/diylc/diylc-library/src/org/diylc/components/connectivity/CurvedTrace.java index 9337c90e..9d5f8388 100644 --- a/diylc/diylc-library/src/org/diylc/components/connectivity/CurvedTrace.java +++ b/diylc/diylc-library/src/org/diylc/components/connectivity/CurvedTrace.java @@ -24,13 +24,14 @@ package org.diylc.components.connectivity; import java.awt.Color; import java.awt.Graphics2D; import java.awt.geom.CubicCurve2D; - + import org.diylc.common.SimpleComponentTransformer; import org.diylc.common.ObjectCache; import org.diylc.common.PCBLayer; import org.diylc.components.AbstractCurvedComponent; import org.diylc.core.ComponentState; import org.diylc.core.IDIYComponent; +import org.diylc.core.IDrawingObserver; import org.diylc.core.annotations.BomPolicy; import org.diylc.core.annotations.ComponentDescriptor; import org.diylc.core.annotations.EditableProperty; @@ -58,14 +59,16 @@ public class CurvedTrace extends AbstractCurvedComponent { } @Override - protected void drawCurve(CubicCurve2D curve, Graphics2D g2d, ComponentState componentState) { + protected void drawCurve(CubicCurve2D curve, Graphics2D g2d, ComponentState componentState, IDrawingObserver drawingObserver) { float thickness = (float) size.convertToPixels(); g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(thickness)); Color curveColor = componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING ? SELECTION_COLOR : color; - g2d.setColor(curveColor); - g2d.draw(curve); + g2d.setColor(curveColor); + drawingObserver.startTrackingContinuityArea(true); + g2d.draw(curve); + drawingObserver.stopTrackingContinuityArea(); } @EditableProperty(name = "Width") diff --git a/diylc/diylc-library/src/org/diylc/components/connectivity/HookupWire.java b/diylc/diylc-library/src/org/diylc/components/connectivity/HookupWire.java index 972e9322..b1d6dd9c 100644 --- a/diylc/diylc-library/src/org/diylc/components/connectivity/HookupWire.java +++ b/diylc/diylc-library/src/org/diylc/components/connectivity/HookupWire.java @@ -24,12 +24,13 @@ package org.diylc.components.connectivity; import java.awt.Color; import java.awt.Graphics2D; import java.awt.geom.CubicCurve2D; - + import org.diylc.common.ObjectCache; import org.diylc.common.SimpleComponentTransformer; import org.diylc.components.AbstractCurvedComponent; import org.diylc.core.ComponentState; import org.diylc.core.IDIYComponent; +import org.diylc.core.IDrawingObserver; import org.diylc.core.annotations.BomPolicy; import org.diylc.core.annotations.ComponentDescriptor; import org.diylc.core.annotations.EditableProperty; @@ -38,7 +39,7 @@ import org.diylc.utils.Constants; @ComponentDescriptor(name = "Hookup Wire", author = "Branislav Stojkovic", category = "Connectivity", instanceNamePrefix = "W", description = "Flexible wire with two control points", zOrder = IDIYComponent.COMPONENT, flexibleZOrder = true, bomPolicy = BomPolicy.NEVER_SHOW, autoEdit = false, - transformer = SimpleComponentTransformer.class) + transformer = SimpleComponentTransformer.class, continuity = true) public class HookupWire extends AbstractCurvedComponent { private static final long serialVersionUID = 1L; @@ -54,7 +55,7 @@ public class HookupWire extends AbstractCurvedComponent { } @Override - protected void drawCurve(CubicCurve2D curve, Graphics2D g2d, ComponentState componentState) { + protected void drawCurve(CubicCurve2D curve, Graphics2D g2d, ComponentState componentState, IDrawingObserver drawingObserver) { int thickness = (int) (Math.pow(Math.E, -1.12436 - 0.11594 * gauge.getValue()) * Constants.PIXELS_PER_INCH * (1 + 2 * INSULATION_THICKNESS_PCT)); Color curveColor = diff --git a/diylc/diylc-library/src/org/diylc/components/connectivity/Jumper.java b/diylc/diylc-library/src/org/diylc/components/connectivity/Jumper.java index 8e59b0a8..95670911 100644 --- a/diylc/diylc-library/src/org/diylc/components/connectivity/Jumper.java +++ b/diylc/diylc-library/src/org/diylc/components/connectivity/Jumper.java @@ -40,7 +40,7 @@ import org.diylc.core.measures.Size; @ComponentDescriptor(name = "Jumper", author = "Branislav Stojkovic", category = "Connectivity", creationMethod = CreationMethod.POINT_BY_POINT, instanceNamePrefix = "J", description = "", zOrder = IDIYComponent.COMPONENT, bomPolicy = BomPolicy.NEVER_SHOW, autoEdit = false, - transformer = SimpleComponentTransformer.class) + transformer = SimpleComponentTransformer.class, continuity = true) public class Jumper extends AbstractLeadedComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/org/diylc/components/connectivity/TraceCut.java b/diylc/diylc-library/src/org/diylc/components/connectivity/TraceCut.java index 9222254f..415533f3 100644 --- a/diylc/diylc-library/src/org/diylc/components/connectivity/TraceCut.java +++ b/diylc/diylc-library/src/org/diylc/components/connectivity/TraceCut.java @@ -77,16 +77,20 @@ public class TraceCut extends AbstractComponent { if (getCutBetweenHoles()) { int holeSpacing = getClosestOdd(getHoleSpacing().convertToPixels()); g2d.setColor(componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING ? SELECTION_COLOR - : boardColor); - g2d.fillRect(point.x - holeSpacing / 2 - cutWidth / 2, point.y - size / 2 - 1, cutWidth, size + 2); + : boardColor); + drawingObserver.startTrackingContinuityArea(false); + g2d.fillRect(point.x - holeSpacing / 2 - cutWidth / 2, point.y - size / 2 - 1, cutWidth, size + 2); + drawingObserver.stopTrackingContinuityArea(); } else { g2d.setColor(componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING ? SELECTION_COLOR - : boardColor); - g2d.fillRoundRect(point.x - size / 2, point.y - size / 2, size, size, size, size); + : boardColor); + drawingObserver.startTrackingContinuityArea(false); + g2d.fillRoundRect(point.x - size / 2, point.y - size / 2, size, size, size, size); + drawingObserver.stopTrackingContinuityArea(); g2d.setColor(Constants.CANVAS_COLOR); - int holeSize = getClosestOdd((int) HOLE_SIZE.convertToPixels()); - g2d.fillOval(point.x - holeSize / 2, point.y - holeSize / 2, holeSize, holeSize); + int holeSize = getClosestOdd((int) HOLE_SIZE.convertToPixels()); + g2d.fillOval(point.x - holeSize / 2, point.y - holeSize / 2, holeSize, holeSize); g2d.setColor(boardColor.darker()); g2d.drawOval(point.x - holeSize / 2, point.y - holeSize / 2, holeSize, holeSize); } diff --git a/diylc/diylc-swing/DIYLCStarter.launch b/diylc/diylc-swing/DIYLCStarter.launch index 6d1af050..e44ede70 100644 --- a/diylc/diylc-swing/DIYLCStarter.launch +++ b/diylc/diylc-swing/DIYLCStarter.launch @@ -11,5 +11,5 @@ - + diff --git a/diylc/diylc-swing/lib/diylc-core.jar b/diylc/diylc-swing/lib/diylc-core.jar index a5a4c34f..493dad85 100644 Binary files a/diylc/diylc-swing/lib/diylc-core.jar and b/diylc/diylc-swing/lib/diylc-core.jar differ diff --git a/diylc/diylc-swing/library/main.jar b/diylc/diylc-swing/library/main.jar index 52047902..a2e31ed0 100644 Binary files a/diylc/diylc-swing/library/main.jar and b/diylc/diylc-swing/library/main.jar differ diff --git a/diylc/diylc-swing/src/org/diylc/swing/plugins/config/ConfigPlugin.java b/diylc/diylc-swing/src/org/diylc/swing/plugins/config/ConfigPlugin.java index 70f44385..d939a0cf 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/plugins/config/ConfigPlugin.java +++ b/diylc/diylc-swing/src/org/diylc/swing/plugins/config/ConfigPlugin.java @@ -99,7 +99,10 @@ public class ConfigPlugin implements IPlugIn { CONFIG_MENU); swingUI.injectMenuAction( ActionFactory.getInstance() - .createConfigAction(plugInPort, "Sticky Points", IPlugInPort.STICKY_POINTS_KEY, true), CONFIG_MENU); + .createConfigAction(plugInPort, "Sticky Points", IPlugInPort.STICKY_POINTS_KEY, true), CONFIG_MENU); + swingUI.injectMenuAction( + ActionFactory.getInstance() + .createConfigAction(plugInPort, "Highlighted Connected Areas", IPlugInPort.HIGHLIGHT_CONTINUITY_AREA, false), CONFIG_MENU); File themeDir = new File("themes"); if (themeDir.exists()) { diff --git a/diylc/diylc-swing/update.xml b/diylc/diylc-swing/update.xml index 36ce7f7f..dc43667b 100644 --- a/diylc/diylc-swing/update.xml +++ b/diylc/diylc-swing/update.xml @@ -1874,6 +1874,10 @@ 2018-12-11 00:00:00.000 CET + + NEW_FEATURE + Highlight connected areas through copper traces, leads, jumpers, etc... + NEW_FEATURE Holding "shift" key switches mouse wheel scrolling function from vertical to horizontal