diff --git a/diylc/diylc-core/src/main/java/org/diylc/common/EventType.java b/diylc/diylc-core/src/main/java/org/diylc/common/EventType.java
index cfdcb2702..22e61de2b 100644
--- a/diylc/diylc-core/src/main/java/org/diylc/common/EventType.java
+++ b/diylc/diylc-core/src/main/java/org/diylc/common/EventType.java
@@ -21,7 +21,6 @@
*/
package org.diylc.common;
-import java.awt.Rectangle;
import java.awt.geom.Point2D;
import org.diylc.core.Project;
diff --git a/diylc/diylc-core/src/main/java/org/diylc/common/IPlugInPort.java b/diylc/diylc-core/src/main/java/org/diylc/common/IPlugInPort.java
index 53b55416a..cdb94cf87 100644
--- a/diylc/diylc-core/src/main/java/org/diylc/common/IPlugInPort.java
+++ b/diylc/diylc-core/src/main/java/org/diylc/common/IPlugInPort.java
@@ -41,6 +41,7 @@ import org.diylc.core.IDIYComponent;
import org.diylc.core.Project;
import org.diylc.core.Template;
import org.diylc.core.Theme;
+import org.diylc.core.measures.ResizeDimensions;
import org.diylc.core.measures.Size;
import org.diylc.filter.IComponentFilter;
import org.diylc.test.ITestProcessor;
@@ -49,9 +50,9 @@ import org.diylc.test.ITestProcessor;
* Interface for communication between plug-ins and the application. Plug-ins may acquire
* information or make changes through this interface. Application events are dispatched to plug-ins
* through {@link MessageDispatcher}
- *
+ *
* @author Branislav Stojkovic
- *
+ *
* @see IPlugIn
* @see MessageDispatcher
* @see IMessageListener
@@ -86,18 +87,19 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP
public static final String RULER_IN_SUBDIVISION_KEY = "rulerInSubdivision";
public static final String CACHING_ENABLED_KEY = "cachingEnabled";
public static final String FONT_SCALE_FACTOR_KEY = "fontScaleFactor";
-
+ public static final String SHOW_RESIZE_DIMENSIONS_TOOLTIP_KEY = "showResizeDimensionsTooltip";
+
public static final String RULER_IN_SUBDIVISION_2 = "base of 2";
public static final String RULER_IN_SUBDIVISION_10 = "base of 10";
public static final String RULER_IN_SUBDIVISION_DEFAULT = RULER_IN_SUBDIVISION_10;
-
+
public static final String SNAP_TO_NONE = "None";
public static final String SNAP_TO_GRID = "Grid";
public static final String SNAP_TO_COMPONENTS = "Components";
public static final String SNAP_TO_DEFAULT = SNAP_TO_GRID;
-
+
public static final String LOCKED_ALPHA = "lockedAlpha";
-
+
public static final String LANGUAGE = "language";
public static final String LANGUAGE_DEFAULT = "English";
@@ -110,25 +112,25 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP
public static final int NOBUTTON = 0;
public static final int BUTTON1 = 1;
public static final int BUTTON2 = 2;
- public static final int BUTTON3 = 3;
+ public static final int BUTTON3 = 3;
/**
* Returns size of the canvas that takes project dimensions into account as well as zoom level.
* Each dimension is calculated as the product of the actual size and the number of pixels per
* unit. If useZoom is set to true, the result is scaled by zoom factor.
- *
+ *
* @param useZoom
* @param includeExtraSpace
- *
+ *
* @return canvas dimensions
*/
Dimension getCanvasDimensions(boolean useZoom, boolean includeExtraSpace);
/**
* Returns an instance of {@link Cursor} that should be used at the specified location.
- *
+ *
* Note: point coordinates are scaled for zoom factor.
- *
+ *
* @param point
* @param ctrlDown
* @param shiftDown
@@ -139,14 +141,14 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP
/**
* Returns an instance of currently loaded project.
- *
+ *
* @return
*/
Project getCurrentProject();
/**
* Loads specified {@link Project}.
- *
+ *
* @param project
* @param freshStart
* @param filename file name when loading from a file
@@ -160,22 +162,22 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP
/**
* Loads a project from the specified file.
- *
+ *
* @param fileName
*/
void loadProjectFromFile(String fileName);
/**
* Saves the current project into the specified file.
- *
+ *
* @param fileName
* @param isBackup
*/
void saveProjectToFile(String fileName, boolean isBackup);
-
+
/**
* Exports the current project into one or more files - one per layer and potentially drill layer.
- *
+ *
* @param fileNameBase
* @param g2d
*/
@@ -193,7 +195,7 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP
/**
* Shows a user dialog if there are changes to confirm that it's safe to proceed.
- *
+ *
* @return true, if file actions (new, open, close) can be taken
*/
boolean allowFileAction();
@@ -202,7 +204,7 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP
* Returns all available {@link ComponentType}s classified by category. Result is a {@link Map}
* between category name to a {@link List} of all {@link ComponentType}s that share that category
* name.
- *
+ *
* @return
*/
Map> getComponentTypes();
@@ -210,7 +212,7 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP
/**
* Draws project on the provided {@link Graphics2D}. If the provided filter is not null, it will
* be used to filter the components that are shown.
- *
+ *
* @param g2d
* @param drawOptions specific drawing options
* @param filter
@@ -224,35 +226,35 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP
/**
* Returns current zoom level where zoomLevel = 1.0d means 100%.
- *
+ *
* @return current zoom level
*/
double getZoomLevel();
/**
* Changes current zoom level where zoomLevel = 1.0d means 100%
- *
+ *
* @param zoomLevel new zoom leve
*/
void setZoomLevel(double zoomLevel);
/**
* Returns current version number.
- *
+ *
* @return
*/
VersionNumber getCurrentVersionNumber();
-
+
/**
- * Returns few most recent versions.
- *
+ * Returns few most recent versions.
+ *
* @return
*/
List getRecentUpdates();
/**
* Adds a list of components to the project.
- *
+ *
* @param componentTransferable
* @param autoGroup
* @param assignNewNames
@@ -271,7 +273,7 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP
/**
* Sets default value for the specified property name for currently selected component types.
- *
+ *
* @param propertyName display name for property
* @param value new default value, must not be null
*/
@@ -279,7 +281,7 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP
/**
* Sets default value for the specified property name for projects.
- *
+ *
* @param clazz class to set defaults to
* @param propertyName display name for property
* @param value new default value, must not be null
@@ -293,6 +295,15 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP
*/
List getMutualSelectionProperties();
+ /**
+ * Available only during resizing, returns the dimensions of the component being resized.
+ * Otherwise returns null.
+ *
+ * @return ResizeDimensions with width, height (in mm or in depending on configuration),
+ * and length (if component implements IHaveLength), or null if not resizing
+ */
+ ResizeDimensions getResizeDimensions();
+
void editSelection();
/**
diff --git a/diylc/diylc-core/src/main/java/org/diylc/components/AbstractCurvedComponent.java b/diylc/diylc-core/src/main/java/org/diylc/components/AbstractCurvedComponent.java
index 8a3b80630..1a1fbb25b 100644
--- a/diylc/diylc-core/src/main/java/org/diylc/components/AbstractCurvedComponent.java
+++ b/diylc/diylc-core/src/main/java/org/diylc/components/AbstractCurvedComponent.java
@@ -37,16 +37,14 @@ import com.thoughtworks.xstream.annotations.XStreamOmitField;
import org.diylc.common.IPlugInPort;
import org.diylc.common.LineStyle;
import org.diylc.common.ObjectCache;
-import org.diylc.core.ComponentState;
-import org.diylc.core.IDrawingObserver;
-import org.diylc.core.Project;
-import org.diylc.core.VisibilityPolicy;
+import org.diylc.core.*;
import org.diylc.core.annotations.EditableProperty;
import org.diylc.core.measures.Size;
import org.diylc.core.measures.SizeUnit;
import org.diylc.utils.Constants;
-public abstract class AbstractCurvedComponent extends AbstractTransparentComponent {
+public abstract class AbstractCurvedComponent extends AbstractTransparentComponent implements
+ IHaveLength {
private static final long serialVersionUID = 1L;
@@ -337,8 +335,8 @@ public abstract class AbstractCurvedComponent extends AbstractTransparentComp
// }
}
- @EditableProperty
- public Size getLength() {
+ @EditableProperty(name = "Length")
+ public Size calculateLength() {
double d = 0;
try {
Point2D[] p = getControlPoints();
diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/IHaveLength.java b/diylc/diylc-core/src/main/java/org/diylc/core/IHaveLength.java
new file mode 100644
index 000000000..07372b766
--- /dev/null
+++ b/diylc/diylc-core/src/main/java/org/diylc/core/IHaveLength.java
@@ -0,0 +1,29 @@
+/*
+
+ DIY Layout Creator (DIYLC).
+ Copyright (c) 2009-2025 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.core;
+
+import org.diylc.core.measures.Size;
+
+public interface IHaveLength {
+
+ Size calculateLength();
+}
diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/measures/ResizeDimensions.java b/diylc/diylc-core/src/main/java/org/diylc/core/measures/ResizeDimensions.java
new file mode 100644
index 000000000..8ca5e488c
--- /dev/null
+++ b/diylc/diylc-core/src/main/java/org/diylc/core/measures/ResizeDimensions.java
@@ -0,0 +1,33 @@
+/*
+ *
+ * DIY Layout Creator (DIYLC).
+ * Copyright (c) 2009-2025 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.core.measures;
+
+/**
+ * Record containing resize dimensions for a component being resized.
+ *
+ * @param width the width of the component (in mm or in, depending on configuration)
+ * @param height the height of the component (in mm or in, depending on configuration)
+ * @param length the length of the component (if it implements IHaveLength), null otherwise
+ */
+public record ResizeDimensions(Size width, Size height, Size length) {
+}
+
diff --git a/diylc/diylc-core/src/main/java/org/diylc/presenter/Presenter.java b/diylc/diylc-core/src/main/java/org/diylc/presenter/Presenter.java
index 7a16225f6..6da611fda 100644
--- a/diylc/diylc-core/src/main/java/org/diylc/presenter/Presenter.java
+++ b/diylc/diylc-core/src/main/java/org/diylc/presenter/Presenter.java
@@ -59,16 +59,10 @@ import com.thoughtworks.xstream.security.AnyTypePermission;
import org.diylc.clipboard.ComponentTransferable;
import org.diylc.common.*;
-import org.diylc.core.ExpansionMode;
-import org.diylc.core.IDIYComponent;
-import org.diylc.core.IDatasheetSupport;
-import org.diylc.core.IView;
-import org.diylc.core.Project;
-import org.diylc.core.Template;
-import org.diylc.core.Theme;
-import org.diylc.core.VisibilityPolicy;
+import org.diylc.core.*;
import org.diylc.core.annotations.IAutoCreator;
import org.diylc.core.gerber.GerberExporter;
+import org.diylc.core.measures.ResizeDimensions;
import org.diylc.core.measures.Size;
import org.diylc.core.measures.SizeUnit;
import org.diylc.filter.IComponentFilter;
@@ -928,29 +922,36 @@ public class Presenter implements IPlugInPort {
messageDispatcher.dispatchMessage(EventType.REPAINT);
}
- @Override
public Rectangle2D getSelectionBounds(boolean applyZoom) {
- if (selectedComponents == null || selectedComponents.isEmpty())
+ return getComponentBounds(selectedComponents, applyZoom);
+ }
+
+ private Rectangle2D getComponentBounds(Collection> components, boolean applyZoom) {
+ if (components == null || components.isEmpty())
return null;
int minX = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE;
int minY = Integer.MAX_VALUE;
int maxY = Integer.MIN_VALUE;
- for (IDIYComponent> c : selectedComponents) {
+
+ for (IDIYComponent> c : components) {
+
ComponentArea compArea = drawingManager.getComponentArea(c);
- if (compArea != null && compArea.getOutlineArea() != null) {
- Rectangle rect = compArea.getOutlineArea().getBounds();
- if (rect.x < minX)
- minX = rect.x;
- if (rect.x + rect.width > maxX)
- maxX = rect.x + rect.width;
- if (rect.y < minY)
- minY = rect.y;
- if (rect.y + rect.height > maxY)
- maxY = rect.y + rect.height;
- } else if (currentProject.getComponents().contains(c))
+ if (compArea == null || compArea.getOutlineArea() == null) {
LOG.debug("Area is null for " + c.getName() + " of type " + c.getClass().getName());
+ return null;
+ }
+
+ Rectangle rect = compArea.getOutlineArea().getBounds();
+ if (rect.x < minX)
+ minX = rect.x;
+ if (rect.x + rect.width > maxX)
+ maxX = rect.x + rect.width;
+ if (rect.y < minY)
+ minY = rect.y;
+ if (rect.y + rect.height > maxY)
+ maxY = rect.y + rect.height;
}
if (configManager.readBoolean(EXTRA_SPACE_KEY, true)) {
@@ -1019,8 +1020,9 @@ public class Presenter implements IPlugInPort {
}
updateSelection(matching);
messageDispatcher.dispatchMessage(EventType.REPAINT, true);
- if (matching.size() > 0)
+ if (!matching.isEmpty()) {
messageDispatcher.dispatchMessage(EventType.SCROLL_TO, getSelectionBounds(true));
+ }
}
@Override
@@ -1237,11 +1239,14 @@ public class Presenter implements IPlugInPort {
Map params = new HashMap();
params.put("point", point);
test.addStep(DIYTest.DRAG_OVER, params);
- }
-
+ }
+
if (point == null || operationMode == OperationMode.HIGHLIGHT_CONNECTED_AREAS) {
return false;
}
+
+ boolean resizeInProgress = false;
+
Point2D scaledPoint = scalePoint(point);
if (controlPointMap != null && !controlPointMap.isEmpty()) {
// We're dragging control point(s).
@@ -1252,6 +1257,13 @@ public class Presenter implements IPlugInPort {
if (actualD == null)
return true;
+ // if there's at least one component with only one control point being dragged it means that we are resizing
+ if (controlPointMap.size() == 1 && controlPointMap.entrySet().stream()
+ .anyMatch(entry -> entry.getValue().size() == 1
+ && entry.getKey().getControlPointCount() > 1)) {
+ resizeInProgress = true;
+ }
+
previousDragPoint.setLocation(previousDragPoint.getX() + actualD.getX(), previousDragPoint.getY() + actualD.getY());
} else if (selectedComponents.isEmpty() && instantiationManager.getComponentTypeSlot() == null
&& previousDragPoint != null) {
@@ -1269,10 +1281,50 @@ public class Presenter implements IPlugInPort {
this.previousScaledPoint = scalePoint(point);
instantiationManager.updateSingleClick(previousScaledPoint, isSnapToGrid(), currentProject.getGridSpacing());
}
- messageDispatcher.dispatchMessage(EventType.REPAINT);
+
+ messageDispatcher.dispatchMessage(EventType.REPAINT, false, resizeInProgress);
return true;
}
+ public ResizeDimensions getResizeDimensions() {
+
+ if (controlPointMap == null || controlPointMap.isEmpty()) {
+ return null;
+ }
+
+ // Find the component being resized (one with a single control point being dragged)
+ IDIYComponent> resizingComponent = null;
+ for (Map.Entry, Set> entry : controlPointMap.entrySet()) {
+ if (entry.getValue().size() == 1 && entry.getKey().getControlPointCount() > 1) {
+ resizingComponent = entry.getKey();
+ break;
+ }
+ }
+
+ if (resizingComponent == null) {
+ return null;
+ }
+
+ Rectangle2D componentBounds = getComponentBounds(controlPointMap.keySet(), false);
+ if (componentBounds == null) {
+ return null;
+ }
+
+ // Convert pixel dimensions to Size (mm or in based on configuration)
+ boolean metric = configManager.readBoolean(METRIC_KEY, true);
+ SizeUnit unit = metric ? SizeUnit.mm : SizeUnit.in;
+ Size width = Size.fromPixels(componentBounds.getWidth(), unit);
+ Size height = Size.fromPixels(componentBounds.getHeight(), unit);
+
+ // Get length if component implements IHaveLength
+ Size length = null;
+ if (resizingComponent instanceof IHaveLength iHaveLength) {
+ length = iHaveLength.calculateLength();
+ }
+
+ return new ResizeDimensions(width, height, length);
+ }
+
private Point2D moveComponents(Map, Set> controlPointMap, int dx, int dy, boolean snapToGrid, boolean snapToObjects) {
// After we make the transfer and snap to grid, calculate actual dx
// and dy. We'll use them to translate the previous drag point.
@@ -2171,6 +2223,10 @@ public class Presenter implements IPlugInPort {
}
Rectangle2D rect = getSelectionBounds(false);
+ if (rect == null) {
+ return null;
+ }
+
double width = rect.getWidth();
double height = rect.getHeight();
diff --git a/diylc/diylc-core/src/main/resources/update.xml b/diylc/diylc-core/src/main/resources/update.xml
index 5fedfa418..6aab193ed 100644
--- a/diylc/diylc-core/src/main/resources/update.xml
+++ b/diylc/diylc-core/src/main/resources/update.xml
@@ -484,5 +484,21 @@
https://github.com/bancika/diy-layout-creator/releases
+
+
+ 5
+ 9
+ 0
+
+ 2025-12-30 00:00:00.000 CET
+
+
+
+ NEW_FEATURE
+ Added balloon tooltip next to cursor showing component width, height and length (where available) while resizing; can be disabled from Config menu
+
+
+ https://github.com/bancika/diy-layout-creator/releases
+
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/AbstractLeadedComponent.java b/diylc/diylc-library/src/main/java/org/diylc/components/AbstractLeadedComponent.java
index 7d23c65c1..f91468ae9 100644
--- a/diylc/diylc-library/src/main/java/org/diylc/components/AbstractLeadedComponent.java
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/AbstractLeadedComponent.java
@@ -43,11 +43,7 @@ import org.diylc.common.IPlugInPort;
import org.diylc.common.LineStyle;
import org.diylc.common.ObjectCache;
import org.diylc.common.VerticalAlignment;
-import org.diylc.core.ComponentState;
-import org.diylc.core.IDrawingObserver;
-import org.diylc.core.Project;
-import org.diylc.core.Theme;
-import org.diylc.core.VisibilityPolicy;
+import org.diylc.core.*;
import org.diylc.core.annotations.EditableProperty;
import org.diylc.core.annotations.PositiveNonZeroMeasureValidator;
import org.diylc.core.measures.Size;
@@ -62,7 +58,8 @@ import org.diylc.utils.Constants;
*
* @author Branislav Stojkovic
*/
-public abstract class AbstractLeadedComponent extends AbstractLabeledComponent {
+public abstract class AbstractLeadedComponent extends AbstractLabeledComponent implements
+ IHaveLength {
private static final long serialVersionUID = 1L;
@@ -640,6 +637,32 @@ public abstract class AbstractLeadedComponent extends AbstractLabeledComponen
this.length = length;
}
+ @Override
+ public Size calculateLength() {
+
+ try {
+ Point2D[] points = getPoints();
+ if (points.length < 2) {
+ return null;
+ }
+
+ // Calculate distance between ending points
+ double distanceInPixels = points[0].distance(points[1]);
+
+ // Convert to Size with appropriate unit (mm or in based on configuration)
+ SizeUnit unit;
+ if (ConfigurationManager.getInstance().readBoolean(IPlugInPort.METRIC_KEY, true)) {
+ unit = SizeUnit.mm;
+ } else {
+ unit = SizeUnit.in;
+ }
+
+ return Size.fromPixels(distanceInPixels, unit);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
@EditableProperty(name = "Width", defaultable = true, validatorClass = PositiveNonZeroMeasureValidator.class, sortOrder = 102)
public Size getWidth() {
return width;
diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/boards/BlankBoard.java b/diylc/diylc-library/src/main/java/org/diylc/components/boards/BlankBoard.java
index 89ceddbac..7ee0a538c 100644
--- a/diylc/diylc-library/src/main/java/org/diylc/components/boards/BlankBoard.java
+++ b/diylc/diylc-library/src/main/java/org/diylc/components/boards/BlankBoard.java
@@ -69,18 +69,18 @@ public class BlankBoard extends AbstractBoard {
}
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1));
- if (componentState != ComponentState.DRAGGING) {
- Composite oldComposite = g2d.getComposite();
- if (alpha < MAX_ALPHA) {
- g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f * alpha / MAX_ALPHA));
- }
- g2d.setColor(boardColor);
- if (getType() == Type.SQUARE)
- g2d.fillRect((int)firstPoint.getX(), (int)firstPoint.getY(), (int)(finalSecondPoint.getX() - firstPoint.getX()), (int)(finalSecondPoint.getY() - firstPoint.getY()));
- else
- g2d.fillOval((int)firstPoint.getX(), (int)firstPoint.getY(), (int)(finalSecondPoint.getX() - firstPoint.getX()), (int)(finalSecondPoint.getY() - firstPoint.getY()));
- g2d.setComposite(oldComposite);
+// if (componentState != ComponentState.DRAGGING) {
+ Composite oldComposite = g2d.getComposite();
+ if (alpha < MAX_ALPHA) {
+ g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f * alpha / MAX_ALPHA));
}
+ g2d.setColor(boardColor);
+ if (getType() == Type.SQUARE)
+ g2d.fillRect((int)firstPoint.getX(), (int)firstPoint.getY(), (int)(finalSecondPoint.getX() - firstPoint.getX()), (int)(finalSecondPoint.getY() - firstPoint.getY()));
+ else
+ g2d.fillOval((int)firstPoint.getX(), (int)firstPoint.getY(), (int)(finalSecondPoint.getX() - firstPoint.getX()), (int)(finalSecondPoint.getY() - firstPoint.getY()));
+ g2d.setComposite(oldComposite);
+// }
// Do not track any changes that follow because the whole board has been
// tracked so far.
drawingObserver.stopTracking();
diff --git a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/canvas/CanvasPlugin.java b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/canvas/CanvasPlugin.java
index cf6a32c9c..947d861b5 100644
--- a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/canvas/CanvasPlugin.java
+++ b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/canvas/CanvasPlugin.java
@@ -41,6 +41,7 @@ import org.apache.log4j.Logger;
import org.diylc.appframework.miscutils.IConfigListener;
import org.diylc.appframework.miscutils.IConfigurationManager;
import org.diylc.appframework.miscutils.Utils;
+import org.diylc.core.measures.ResizeDimensions;
import org.diylc.swingframework.ruler.IRulerListener;
import org.diylc.swingframework.ruler.Ruler.InchSubdivision;
import org.diylc.swingframework.ruler.RulerScrollPane;
@@ -53,7 +54,6 @@ import org.diylc.common.BadPositionException;
import org.diylc.common.EventType;
import org.diylc.common.IPlugIn;
import org.diylc.common.IPlugInPort;
-import org.diylc.core.IDIYComponent;
import org.diylc.core.measures.Size;
import org.diylc.core.measures.SizeUnit;
import org.diylc.presenter.Presenter;
@@ -68,6 +68,8 @@ public class CanvasPlugin implements IPlugIn{
private RulerScrollPane scrollPane;
private CanvasPanel canvasPanel;
private ComponentPopupMenu popupMenu;
+ private ResizeInfoTooltip resizeInfoTooltip;
+ private boolean resizingInProgress = false;
private IPlugInPort plugInPort;
private ISwingUI swingUI;
@@ -193,6 +195,7 @@ public class CanvasPlugin implements IPlugIn{
public CanvasPanel getCanvasPanel() {
if (canvasPanel == null) {
canvasPanel = new CanvasPanel(plugInPort, configManager);
+ resizeInfoTooltip = new ResizeInfoTooltip(canvasPanel, configManager);
canvasPanel.addMouseListener(new MouseAdapter() {
private MouseEvent pressedEvent;
@@ -277,6 +280,15 @@ public class CanvasPlugin implements IPlugIn{
e.isShiftDown(), e.isAltDown()));
plugInPort.mouseMoved(e.getPoint(), Utils.isMac() ? e.isMetaDown() : e.isControlDown(),
e.isShiftDown(), e.isAltDown());
+
+ // Update tooltip position if resizing is in progress and tooltip is enabled, otherwise hide it
+ if (resizeInfoTooltip != null) {
+ if (resizingInProgress && configManager.readBoolean(IPlugInPort.SHOW_RESIZE_DIMENSIONS_TOOLTIP_KEY, true)) {
+ resizeInfoTooltip.updatePosition(e.getPoint());
+ } else {
+ resizeInfoTooltip.hideTooltip();
+ }
+ }
}
});
@@ -505,11 +517,13 @@ public class CanvasPlugin implements IPlugIn{
canvasPanel.repaint();
// Refresh selection bounds after we're done with painting to ensure we have traced the
// component areas
+ boolean panToSelection = params.length > 0 && Boolean.TRUE.equals(params[0]);
+ this.resizingInProgress = params.length > 1 && Boolean.TRUE.equals(params[1]);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
- if (params.length > 0 && Boolean.TRUE.equals(params[0])) {
+ if (panToSelection) {
Rectangle2D selectionBounds = plugInPort.getSelectionBounds(true);
if (selectionBounds != null) {
Rectangle visibleRect = scrollPane.getVisibleRect();
@@ -519,8 +533,28 @@ public class CanvasPlugin implements IPlugIn{
);
}
}
- if (configManager.readBoolean(IPlugInPort.SHOW_RULERS_KEY, true))
+ if (configManager.readBoolean(IPlugInPort.SHOW_RULERS_KEY, true)) {
scrollPane.setSelectionRectangle(plugInPort.getSelectionBounds(true));
+ }
+ if (CanvasPlugin.this.resizingInProgress && getCanvasPanel().getMousePosition() != null) {
+ // Only show tooltip if enabled in configuration
+ if (configManager.readBoolean(IPlugInPort.SHOW_RESIZE_DIMENSIONS_TOOLTIP_KEY, true)) {
+ ResizeDimensions resizeDimensions = plugInPort.getResizeDimensions();
+ if (resizeInfoTooltip != null) {
+ resizeInfoTooltip.update(resizeDimensions, getCanvasPanel().getMousePosition());
+ }
+ } else {
+ // Hide tooltip if disabled
+ if (resizeInfoTooltip != null) {
+ resizeInfoTooltip.hideTooltip();
+ }
+ }
+ } else {
+ // Hide tooltip when not resizing
+ if (resizeInfoTooltip != null) {
+ resizeInfoTooltip.hideTooltip();
+ }
+ }
}
});
break;
@@ -563,25 +597,4 @@ public class CanvasPlugin implements IPlugIn{
double extraSpace = CanvasPlugin.this.plugInPort.getExtraSpace();
getScrollPane().setZeroLocation(new Point2D.Double(extraSpace, extraSpace));
}
-
- class LockAction extends AbstractAction {
-
- private static final long serialVersionUID = 1L;
-
- private IDIYComponent> component;
- private boolean locked;
-
- public LockAction(IDIYComponent> component, boolean locked) {
- super();
- this.locked = locked;
- this.component = component;
- putValue(AbstractAction.NAME, component.getName());
- }
-
- @Override
- public void actionPerformed(ActionEvent e) {
- plugInPort.lockComponent(component, locked);
- }
-
- }
}
diff --git a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/canvas/ResizeInfoTooltip.java b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/canvas/ResizeInfoTooltip.java
new file mode 100644
index 000000000..6d55b224f
--- /dev/null
+++ b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/canvas/ResizeInfoTooltip.java
@@ -0,0 +1,225 @@
+/*
+ *
+ * DIY Layout Creator (DIYLC). Copyright (c) 2009-2025 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.swing.plugins.canvas;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.RenderingHints;
+import java.awt.Window;
+import java.text.DecimalFormat;
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JWindow;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import org.diylc.appframework.miscutils.IConfigurationManager;
+import org.diylc.core.measures.ResizeDimensions;
+import org.diylc.core.measures.Size;
+
+/**
+ * A lightweight tooltip window that displays resize dimensions and tracks the mouse cursor.
+ */
+public class ResizeInfoTooltip {
+
+ private static final int OFFSET_X = 16;
+ private static final int OFFSET_Y = 16;
+ private static final DecimalFormat SIZE_FORMAT = new DecimalFormat("0.00");
+
+ private JWindow tooltipWindow;
+ private JLabel tooltipLabel;
+ private Component parentComponent;
+ private ResizeDimensions currentDimensions;
+
+ public ResizeInfoTooltip(Component parentComponent, IConfigurationManager> configManager) {
+ this.parentComponent = parentComponent;
+ initializeTooltip();
+ }
+
+ private void initializeTooltip() {
+ Window parentWindow = SwingUtilities.getWindowAncestor(parentComponent);
+ if (parentWindow == null) {
+ return;
+ }
+
+ tooltipWindow = new JWindow(parentWindow);
+ tooltipLabel = new JLabel() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ super.paintComponent(g2d);
+ g2d.dispose();
+ }
+ };
+
+ // Use system tooltip font and colors
+ Font tooltipFont = UIManager.getFont("ToolTip.font");
+ if (tooltipFont != null) {
+ tooltipLabel.setFont(tooltipFont);
+ }
+ Color tooltipBackground = UIManager.getColor("ToolTip.background");
+ Color tooltipForeground = UIManager.getColor("ToolTip.foreground");
+ Color tooltipBorderColor = UIManager.getColor("ToolTip.border");
+ if (tooltipBorderColor == null) {
+ tooltipBorderColor = Color.GRAY;
+ }
+
+ tooltipLabel.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createLineBorder(tooltipBorderColor, 1),
+ BorderFactory.createEmptyBorder(4, 6, 4, 6)));
+ tooltipLabel.setOpaque(true);
+ tooltipLabel.setBackground(tooltipBackground != null ? tooltipBackground : new Color(255, 255, 220));
+ tooltipLabel.setForeground(tooltipForeground != null ? tooltipForeground : Color.BLACK);
+
+ tooltipWindow.add(tooltipLabel);
+ tooltipWindow.setFocusableWindowState(false);
+ tooltipWindow.setAlwaysOnTop(true);
+ }
+
+ /**
+ * Updates the tooltip with new dimensions and mouse position.
+ */
+ public void update(ResizeDimensions dimensions, Point mousePosition) {
+ this.currentDimensions = dimensions;
+
+ if (dimensions == null || mousePosition == null) {
+ hideTooltip();
+ return;
+ }
+
+ if (tooltipWindow == null) {
+ initializeTooltip();
+ if (tooltipWindow == null) {
+ return;
+ }
+ }
+
+ // Format the dimensions
+ String text = formatDimensions(dimensions);
+ tooltipLabel.setText(text);
+ tooltipLabel.setPreferredSize(null); // Reset to calculate preferred size
+ tooltipWindow.pack();
+
+ // Position the tooltip near the mouse cursor
+ updateTooltipPosition(mousePosition);
+ tooltipWindow.setVisible(true);
+ }
+
+ /**
+ * Updates the tooltip position based on the current mouse position.
+ */
+ public void updatePosition(Point mousePosition) {
+ if (currentDimensions != null && mousePosition != null && tooltipWindow != null && tooltipWindow.isVisible()) {
+ updateTooltipPosition(mousePosition);
+ }
+ }
+
+ private void updateTooltipPosition(Point mousePosition) {
+ if (tooltipWindow == null || parentComponent == null) {
+ return;
+ }
+
+ // Convert mouse position to screen coordinates
+ Point screenPoint = new Point(mousePosition);
+ SwingUtilities.convertPointToScreen(screenPoint, parentComponent);
+
+ // Position tooltip 16 pixels down and right from cursor
+ int x = screenPoint.x + OFFSET_X;
+ int y = screenPoint.y + OFFSET_Y;
+
+ // Ensure tooltip stays on screen
+ Dimension tooltipSize = tooltipWindow.getSize();
+ if (tooltipSize == null) {
+ tooltipWindow.pack();
+ tooltipSize = tooltipWindow.getSize();
+ }
+
+ if (tooltipSize != null) {
+ java.awt.Toolkit toolkit = java.awt.Toolkit.getDefaultToolkit();
+ Dimension screenSize = toolkit.getScreenSize();
+
+ // Adjust if tooltip would go off screen
+ if (x + tooltipSize.width > screenSize.width) {
+ x = screenPoint.x - tooltipSize.width - OFFSET_X;
+ }
+ if (y + tooltipSize.height > screenSize.height) {
+ y = screenPoint.y - tooltipSize.height - OFFSET_Y;
+ }
+ if (x < 0) {
+ x = 0;
+ }
+ if (y < 0) {
+ y = 0;
+ }
+ }
+
+ tooltipWindow.setLocation(x, y);
+ }
+
+ private String formatDimensions(ResizeDimensions dimensions) {
+ StringBuilder sb = new StringBuilder("");
+
+ // Format width
+ Size width = dimensions.width();
+ String widthUnit = width.getUnit().toString();
+ sb.append(String.format("W: %s %s", SIZE_FORMAT.format(width.getValue()), widthUnit));
+
+ // Format height
+ Size height = dimensions.height();
+ String heightUnit = height.getUnit().toString();
+ sb.append(String.format("
H: %s %s", SIZE_FORMAT.format(height.getValue()), heightUnit));
+
+ // Format length if available
+ Size length = dimensions.length();
+ if (length != null) {
+ String lengthUnit = length.getUnit().toString();
+ sb.append(String.format("
L: %s %s", SIZE_FORMAT.format(length.getValue()), lengthUnit));
+ }
+
+ sb.append("");
+ return sb.toString();
+ }
+
+ /**
+ * Hides the tooltip.
+ */
+ public void hideTooltip() {
+ if (tooltipWindow != null) {
+ tooltipWindow.setVisible(false);
+ }
+ currentDimensions = null;
+ }
+
+ /**
+ * Disposes the tooltip window.
+ */
+ public void dispose() {
+ if (tooltipWindow != null) {
+ tooltipWindow.dispose();
+ tooltipWindow = null;
+ }
+ }
+}
+
diff --git a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/config/ConfigPlugin.java b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/config/ConfigPlugin.java
index 114cd1b4c..9bd3222cf 100644
--- a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/config/ConfigPlugin.java
+++ b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/config/ConfigPlugin.java
@@ -158,6 +158,10 @@ public class ConfigPlugin implements IPlugIn {
ActionFactory.getInstance().createConfigAction(plugInPort, "Show Rulers", IPlugInPort.SHOW_RULERS_KEY, true),
CONFIG_MENU);
+ swingUI.injectMenuAction(
+ ActionFactory.getInstance().createConfigAction(plugInPort, "Show Resize Dimensions",
+ IPlugInPort.SHOW_RESIZE_DIMENSIONS_TOOLTIP_KEY, true), CONFIG_MENU);
+
swingUI.injectSubmenu(SNAP_MENU, IconLoader.GraphEdgeDirected.getIcon(), CONFIG_MENU);
swingUI.injectMenuAction(
ActionFactory.getInstance().createToggleAction(IPlugInPort.SNAP_TO_NONE, IPlugInPort.SNAP_TO_KEY, SNAP_MENU, IPlugInPort.SNAP_TO_DEFAULT),
diff --git a/diylc/diylc-swing/src/main/resources/lang/Deutsch.txt b/diylc/diylc-swing/src/main/resources/lang/Deutsch.txt
index 78c188a4b..adc6cde0b 100644
--- a/diylc/diylc-swing/src/main/resources/lang/Deutsch.txt
+++ b/diylc/diylc-swing/src/main/resources/lang/Deutsch.txt
@@ -363,6 +363,7 @@ Shaft Up|Achse nach oben
Shape|Form
Shapes|Formen
Show Grid|Raster anzeigen
+Show Resize Dimensions|Größenänderungsdimensionen anzeigen
Show Rulers|Lineale anzeigen
Shroud|Wanne
Shrouded|Abgeschirmt
diff --git a/diylc/diylc-swing/src/main/resources/lang/English.txt b/diylc/diylc-swing/src/main/resources/lang/English.txt
index 271f5b339..c14a7b124 100644
--- a/diylc/diylc-swing/src/main/resources/lang/English.txt
+++ b/diylc/diylc-swing/src/main/resources/lang/English.txt
@@ -352,6 +352,7 @@ Shape
Shapes
Show Bracket
Show Grid
+Show Resize Dimensions
Show Rulers
Shroud
Shrouded
diff --git a/diylc/diylc-swing/src/main/resources/lang/Español.txt b/diylc/diylc-swing/src/main/resources/lang/Español.txt
index 45ff7d88b..dcda6accf 100644
--- a/diylc/diylc-swing/src/main/resources/lang/Español.txt
+++ b/diylc/diylc-swing/src/main/resources/lang/Español.txt
@@ -351,6 +351,7 @@ Shaft Up|Eje Arriba
Shape|Forma
Shapes|Formas
Show Grid|Mostrar Cuadrícula
+Show Resize Dimensions|Mostrar Dimensiones de Redimensionamiento
Show Rulers|Mostrar Reglas
Shroud|Envoltura
Shrouded|Con Envoltura
diff --git a/diylc/diylc-swing/src/main/resources/lang/Français.txt b/diylc/diylc-swing/src/main/resources/lang/Français.txt
index 3c25e3d00..47e68b6d8 100644
--- a/diylc/diylc-swing/src/main/resources/lang/Français.txt
+++ b/diylc/diylc-swing/src/main/resources/lang/Français.txt
@@ -348,6 +348,7 @@ Shaft Up|Tige vers le haut
Shape|Forme
Shapes|Formes
Show Grid|Afficher la grille
+Show Resize Dimensions|Afficher les dimensions de redimensionnement
Show Rulers|Afficher les règles
Shroud|Enveloppe
Shrouded|Enveloppé
diff --git a/diylc/diylc-swing/src/main/resources/lang/Italiano.txt b/diylc/diylc-swing/src/main/resources/lang/Italiano.txt
index b108068c6..7134450e9 100644
--- a/diylc/diylc-swing/src/main/resources/lang/Italiano.txt
+++ b/diylc/diylc-swing/src/main/resources/lang/Italiano.txt
@@ -363,6 +363,7 @@ Shaft Up|Shaft Up
Shape|forma
Shapes|forme
Show Grid|Mostra griglia
+Show Resize Dimensions|Mostra dimensioni ridimensionamento
Show Rulers|Mostra righelli
Shroud|Copri
Shrouded|Coperto
diff --git a/diylc/diylc-swing/src/main/resources/lang/Nederlands.txt b/diylc/diylc-swing/src/main/resources/lang/Nederlands.txt
index 5c0425f66..35bd6fb6a 100644
--- a/diylc/diylc-swing/src/main/resources/lang/Nederlands.txt
+++ b/diylc/diylc-swing/src/main/resources/lang/Nederlands.txt
@@ -363,6 +363,7 @@ Shaft Up|As omhoog
Shape|Vorm
Shapes|Vormen
Show Grid|Raster weergeven
+Show Resize Dimensions|Grootte-aanpassingsafmetingen weergeven
Show Rulers|Linialen weergeven
Shroud|Beschermrand
Shrouded|Afgeschermd
diff --git a/diylc/diylc-swing/src/main/resources/lang/Português.txt b/diylc/diylc-swing/src/main/resources/lang/Português.txt
index 76ae999d5..fc4140afe 100644
--- a/diylc/diylc-swing/src/main/resources/lang/Português.txt
+++ b/diylc/diylc-swing/src/main/resources/lang/Português.txt
@@ -351,6 +351,7 @@ Shaft Up|Jogar para cima
Shape|Forma
Shapes|Formas
Show Grid|Mostrar Grid
+Show Resize Dimensions|Mostrar Dimensões de Redimensionamento
Show Rulers|Mostrar Réguas
Shroud|Envoltura
Shrouded|Envolto
diff --git a/diylc/diylc-swing/src/main/resources/lang/Srpski.txt b/diylc/diylc-swing/src/main/resources/lang/Srpski.txt
index 37e095056..7cb6b5456 100644
--- a/diylc/diylc-swing/src/main/resources/lang/Srpski.txt
+++ b/diylc/diylc-swing/src/main/resources/lang/Srpski.txt
@@ -344,6 +344,7 @@ Shaft Up|Osovina Gore
Shape|Oblik
Shapes|Oblici
Show Grid|Prikaz Rešetke
+Show Resize Dimensions|Prikaz Dimenzija Promene Veličine
Show Rulers|Prikaz Lenjira
Shroud|Kućište
Shrouded|Sa Kućištem
diff --git a/diylc/diylc-swing/src/main/resources/lang/हिंदी.txt b/diylc/diylc-swing/src/main/resources/lang/हिंदी.txt
index 8d2b0bc0a..29fdc2bbe 100644
--- a/diylc/diylc-swing/src/main/resources/lang/हिंदी.txt
+++ b/diylc/diylc-swing/src/main/resources/lang/हिंदी.txt
@@ -352,6 +352,7 @@ Shape|आकार
Shapes|आकार
Show Bracket|ब्रैकेट दिखाएं
Show Grid|ग्रिड दिखाएं
+Show Resize Dimensions|आकार बदलने के आयाम दिखाएं
Show Rulers|रूलर दिखाएं
Shroud|श्राउड
Shrouded|श्राउडेड
diff --git a/diylc/diylc-swing/src/main/resources/lang/简体中文.txt b/diylc/diylc-swing/src/main/resources/lang/简体中文.txt
index afd6ad673..228e616d5 100644
--- a/diylc/diylc-swing/src/main/resources/lang/简体中文.txt
+++ b/diylc/diylc-swing/src/main/resources/lang/简体中文.txt
@@ -352,6 +352,7 @@ Shape|形状
Shapes|形状
Show Bracket|显示支架
Show Grid|显示网格
+Show Resize Dimensions|显示调整大小尺寸
Show Rulers|显示标尺
Shroud|护罩
Shrouded|带护罩