Resize tooltip (#986)

* resize tooltip

* Fix blank board area rendering

* Fixes

* translations

* Add length

* Update update.xml
This commit is contained in:
Bane Stojkovic
2025-12-31 15:07:22 +01:00
committed by GitHub
parent 61b3c63bbf
commit 2de3def32e
22 changed files with 521 additions and 104 deletions
@@ -21,7 +21,6 @@
*/
package org.diylc.common;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import org.diylc.core.Project;
@@ -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 <code>useZoom</code> 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<String, List<ComponentType>> 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 <code>zoomLevel = 1.0d</code> means 100%.
*
*
* @return current zoom level
*/
double getZoomLevel();
/**
* Changes current zoom level where <code>zoomLevel = 1.0d</code> 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<Version> 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<PropertyWrapper> 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();
/**
@@ -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<T> extends AbstractTransparentComponent<T> {
public abstract class AbstractCurvedComponent<T> extends AbstractTransparentComponent<T> implements
IHaveLength {
private static final long serialVersionUID = 1L;
@@ -337,8 +335,8 @@ public abstract class AbstractCurvedComponent<T> extends AbstractTransparentComp
// }
}
@EditableProperty
public Size getLength() {
@EditableProperty(name = "Length")
public Size calculateLength() {
double d = 0;
try {
Point2D[] p = getControlPoints();
@@ -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 <http://www.gnu.org/licenses/>.
*/
package org.diylc.core;
import org.diylc.core.measures.Size;
public interface IHaveLength {
Size calculateLength();
}
@@ -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 <http://www.gnu.org/licenses/>.
*
*/
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) {
}
@@ -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<IDIYComponent<?>> 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<String, Object> params = new HashMap<String, Object>();
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<IDIYComponent<?>, Set<Integer>> 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<IDIYComponent<?>, Set<Integer>> 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();
@@ -484,5 +484,21 @@
</changes>
<url>https://github.com/bancika/diy-layout-creator/releases</url>
</org.diylc.appframework.update.Version>
<org.diylc.appframework.update.Version>
<versionNumber>
<major>5</major>
<minor>9</minor>
<build>0</build>
</versionNumber>
<releaseDate>2025-12-30 00:00:00.000 CET</releaseDate>
<name></name>
<changes>
<org.diylc.appframework.update.Change>
<changeType>NEW_FEATURE</changeType>
<description>Added balloon tooltip next to cursor showing component width, height and length (where available) while resizing; can be disabled from Config menu</description>
</org.diylc.appframework.update.Change>
</changes>
<url>https://github.com/bancika/diy-layout-creator/releases</url>
</org.diylc.appframework.update.Version>
</a>
</java.util.Arrays_-ArrayList>