mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-08 12:54:55 +10:00
Compare commits
71 Commits
dark-theme
...
v5.11.0
| Author | SHA1 | Date | |
|---|---|---|---|
| f0c482d2d5 | |||
| f5fedc1a24 | |||
| b3a1187c1c | |||
| 7e79143079 | |||
| d976959e95 | |||
| 8b69903893 | |||
| 391d161c59 | |||
| d970fa10a7 | |||
| b6816e6a62 | |||
| 3272efc8bf | |||
| 0aed29f072 | |||
| 7e2319746e | |||
| 1cf776ace5 | |||
| b9da7510a8 | |||
| 6fde016ed4 | |||
| a32ad6b733 | |||
| be16910767 | |||
| 457f47782f | |||
| b69729c14f | |||
| a88d8f5e03 | |||
| f4223f5773 | |||
| 513cc67362 | |||
| d8f2a3b6e0 | |||
| 7900128d7c | |||
| 1cef0c0977 | |||
| 2383f9ae77 | |||
| 14ec18dfef | |||
| 2f492c8fa8 | |||
| 9db43cd2f4 | |||
| 344c238f73 | |||
| 3376aa995a | |||
| ec75f1b6eb | |||
| 5f2b20ea41 | |||
| 9d130c0ff3 | |||
| 119c41dc86 | |||
| c5613a6fea | |||
| 479bd50540 | |||
| 502a4b8d39 | |||
| 1f85c07453 | |||
| 53ec1e780e | |||
| 2de3def32e | |||
| 61b3c63bbf | |||
| 348eb3ac00 | |||
| 9972e0ee46 | |||
| de971b707b | |||
| 0dcc06f712 | |||
| 109b0788a8 | |||
| d190ebe208 | |||
| fa93af2707 | |||
| 6b59966816 | |||
| b065c9d6fb | |||
| 49d805a60c | |||
| a9a98bf3a4 | |||
| 840d416282 | |||
| dc2cc4c7c8 | |||
| 3d51d2a38d | |||
| 4dfb1ca630 | |||
| e9c8686960 | |||
| 9be97f5213 | |||
| 721d582dcd | |||
| 443f76f3f9 | |||
| 3ad9c49f53 | |||
| 61077ed5a4 | |||
| 1fe3706d0f | |||
| 16df8fd173 | |||
| f7806d4d3c | |||
| 04e44e174e | |||
| f74fe19b77 | |||
| e7487069a5 | |||
| 9c48b80515 | |||
| 814f4296a9 |
@@ -147,3 +147,10 @@ buildNumber.properties
|
||||
|
||||
# Avoid ignoring Maven wrapper jar file (.jar files are usually ignored)
|
||||
!/.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
######################
|
||||
###### Custom ######
|
||||
######################
|
||||
|
||||
# Deployment folder
|
||||
deploy/
|
||||
|
||||
Executable
+123
@@ -0,0 +1,123 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to build all Maven profiles and copy zip files to deployment folder
|
||||
# Usage: ./build-all-profiles.sh [to-deploy-folder]
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Parse arguments
|
||||
DEPLOY_DIR="${1:-}"
|
||||
|
||||
# Configuration
|
||||
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SWING_MODULE_DIR="${PROJECT_DIR}/diylc-swing"
|
||||
DEPLOY_DIR="${DEPLOY_DIR:-${PROJECT_DIR}/deploy}"
|
||||
|
||||
# List of profiles to build
|
||||
PROFILES=(
|
||||
"universal-zip"
|
||||
"linux-appimage"
|
||||
"win64-zip"
|
||||
"win64-installer"
|
||||
"mac-x86"
|
||||
"mac-arm"
|
||||
)
|
||||
|
||||
echo "=========================================="
|
||||
echo "Building all Maven profiles"
|
||||
echo "=========================================="
|
||||
echo "Project directory: ${PROJECT_DIR}"
|
||||
echo "Swing module directory: ${SWING_MODULE_DIR}"
|
||||
echo "Deployment directory: ${DEPLOY_DIR}"
|
||||
echo "Tests: Skipped"
|
||||
echo ""
|
||||
|
||||
# Create deployment directory if it doesn't exist
|
||||
mkdir -p "${DEPLOY_DIR}"
|
||||
|
||||
# Change to project root directory
|
||||
cd "${PROJECT_DIR}"
|
||||
|
||||
# Build each profile (clean before each one)
|
||||
TARGET_DIR="${SWING_MODULE_DIR}/target"
|
||||
TOTAL_ZIP_COUNT=0
|
||||
|
||||
for profile in "${PROFILES[@]}"; do
|
||||
echo "----------------------------------------"
|
||||
echo "Building profile: ${profile}"
|
||||
echo "----------------------------------------"
|
||||
|
||||
# Clean and build from root, specifying the swing module and profile
|
||||
# This ensures dependencies (diylc-core, diylc-library) are built first
|
||||
mvn clean package -pl diylc-swing -am -P"${profile}" -DskipTests || {
|
||||
echo "ERROR: Failed to build profile ${profile}"
|
||||
echo "Continuing with next profile..."
|
||||
continue
|
||||
}
|
||||
|
||||
echo "✓ Profile ${profile} built successfully"
|
||||
|
||||
# Copy zip and exe files immediately after each build (before next clean)
|
||||
if [ -d "${TARGET_DIR}" ]; then
|
||||
echo "Looking for artifacts in ${TARGET_DIR}..."
|
||||
PROFILE_FILE_COUNT=0
|
||||
|
||||
# Find all zip files in target directory (excluding DIYLC-notarize.zip)
|
||||
while IFS= read -r -d '' zipfile; do
|
||||
filename=$(basename "${zipfile}")
|
||||
# Skip DIYLC-notarize.zip files
|
||||
if [[ "${filename}" != "DIYLC-notarize.zip" ]] && [ -f "${zipfile}" ]; then
|
||||
dest="${DEPLOY_DIR}/${filename}"
|
||||
|
||||
echo " → Copying: ${filename}"
|
||||
cp "${zipfile}" "${dest}"
|
||||
TOTAL_ZIP_COUNT=$((TOTAL_ZIP_COUNT + 1))
|
||||
PROFILE_FILE_COUNT=$((PROFILE_FILE_COUNT + 1))
|
||||
fi
|
||||
done < <(find "${TARGET_DIR}" -maxdepth 1 -type f -name "*.zip" -print0 2>/dev/null)
|
||||
|
||||
# Find all exe files in target directory
|
||||
while IFS= read -r -d '' exefile; do
|
||||
if [ -f "${exefile}" ]; then
|
||||
filename=$(basename "${exefile}")
|
||||
dest="${DEPLOY_DIR}/${filename}"
|
||||
|
||||
echo " → Copying: ${filename}"
|
||||
cp "${exefile}" "${dest}"
|
||||
TOTAL_ZIP_COUNT=$((TOTAL_ZIP_COUNT + 1))
|
||||
PROFILE_FILE_COUNT=$((PROFILE_FILE_COUNT + 1))
|
||||
fi
|
||||
done < <(find "${TARGET_DIR}" -maxdepth 1 -type f -name "*.exe" -print0 2>/dev/null)
|
||||
|
||||
if [ ${PROFILE_FILE_COUNT} -eq 0 ]; then
|
||||
echo " ⚠ Warning: No artifacts found for profile ${profile}"
|
||||
# List what's actually in the target directory for debugging
|
||||
echo " Debug: Files in target directory:"
|
||||
ls -la "${TARGET_DIR}"/*.{zip,exe} 2>/dev/null | head -5 || echo " (no zip/exe files found)"
|
||||
else
|
||||
echo " ✓ Copied ${PROFILE_FILE_COUNT} file(s) from ${profile}"
|
||||
fi
|
||||
else
|
||||
echo " ⚠ Warning: Target directory ${TARGET_DIR} does not exist"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "=========================================="
|
||||
echo "Copying summary"
|
||||
echo "=========================================="
|
||||
if [ ${TOTAL_ZIP_COUNT} -eq 0 ]; then
|
||||
echo "No artifacts were copied"
|
||||
else
|
||||
echo "✓ Copied ${TOTAL_ZIP_COUNT} file(s) total to ${DEPLOY_DIR}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Build and deployment complete!"
|
||||
echo "=========================================="
|
||||
echo "Deployment folder: ${DEPLOY_DIR}"
|
||||
echo "Files:"
|
||||
ls -lh "${DEPLOY_DIR}"/*.{zip,exe} 2>/dev/null || echo " (no zip/exe files found)"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.diylc</groupId>
|
||||
<artifactId>diylc-parent</artifactId>
|
||||
<version>5.7.0</version>
|
||||
<version>5.10.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>diylc-core</artifactId>
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.diylc.core.ComponentGroup;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
|
||||
/**
|
||||
@@ -44,19 +45,21 @@ public class ComponentTransferable implements Transferable, Serializable {
|
||||
public static final DataFlavor listFlavor = new DataFlavor(ComponentTransferable.class, "application/diylc");
|
||||
|
||||
private List<IDIYComponent<?>> components;
|
||||
private Set<Set<IDIYComponent<?>>> groups;
|
||||
private Set<ComponentGroup> groups;
|
||||
private String name;
|
||||
|
||||
public ComponentTransferable() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ComponentTransferable(List<IDIYComponent<?>> components) {
|
||||
public ComponentTransferable(List<IDIYComponent<?>> components, String name) {
|
||||
super();
|
||||
this.components = components;
|
||||
this.name = name;
|
||||
this.groups = null;
|
||||
}
|
||||
|
||||
public ComponentTransferable(List<IDIYComponent<?>> components, Set<Set<IDIYComponent<?>>> groups) {
|
||||
public ComponentTransferable(List<IDIYComponent<?>> components, Set<ComponentGroup> groups) {
|
||||
super();
|
||||
this.components = components;
|
||||
this.groups = groups;
|
||||
@@ -84,7 +87,11 @@ public class ComponentTransferable implements Transferable, Serializable {
|
||||
return components;
|
||||
}
|
||||
|
||||
public Set<Set<IDIYComponent<?>>> getGroups() {
|
||||
public Set<ComponentGroup> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
+23
-25
@@ -17,15 +17,11 @@
|
||||
*/
|
||||
package org.diylc.clipboard;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import org.diylc.core.ComponentGroup;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
|
||||
public class ComponentTransferableFactory {
|
||||
@@ -40,13 +36,19 @@ public class ComponentTransferableFactory {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public ComponentTransferable build(Collection<IDIYComponent<?>> selectedComponents, Set<Set<IDIYComponent<?>>> groups) {
|
||||
Set<IDIYComponent<?>> originalComponentSet = new HashSet<IDIYComponent<?>>(selectedComponents);
|
||||
public ComponentTransferable build(Collection<IDIYComponent<?>> selectedComponents, Set<ComponentGroup> groups) {
|
||||
|
||||
List<IDIYComponent<?>> originalComponents = new ArrayList<IDIYComponent<?>>(selectedComponents);
|
||||
|
||||
Map<UUID, UUID> idMap = new HashMap<>();
|
||||
|
||||
List<IDIYComponent<?>> clonedComponents = originalComponents.stream(
|
||||
).map(x -> {
|
||||
try {
|
||||
return x.clone();
|
||||
IDIYComponent<?> clone = x.clone();
|
||||
clone.setId(UUID.randomUUID());
|
||||
idMap.put(x.getId(), clone.getId());
|
||||
return clone;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
LOG.error(e);
|
||||
return null;
|
||||
@@ -54,22 +56,18 @@ public class ComponentTransferableFactory {
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
Set<Set<IDIYComponent<?>>> clonedGroups = new HashSet<Set<IDIYComponent<?>>>();
|
||||
|
||||
for (Set<IDIYComponent<?>> group : groups) {
|
||||
if (group.isEmpty())
|
||||
continue;
|
||||
|
||||
if (originalComponentSet.contains(group.iterator().next())) {
|
||||
Set<IDIYComponent<?>> clonedGroup = new HashSet<IDIYComponent<?>>();
|
||||
clonedGroups.add(clonedGroup);
|
||||
for (int i = 0; i < originalComponents.size(); i++) {
|
||||
if (group.contains(originalComponents.get(i))) {
|
||||
clonedGroup.add(clonedComponents.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Set<ComponentGroup> clonedGroups = groups.stream()
|
||||
.map(group -> {
|
||||
Set<UUID> newIds = group.getComponentIds().stream()
|
||||
.map(idMap::get)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
// Only create group if it has at least one valid ID
|
||||
return newIds.isEmpty() ? null : new ComponentGroup(newIds, group.getName());
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
return new ComponentTransferable(clonedComponents, clonedGroups);
|
||||
}
|
||||
|
||||
@@ -68,5 +68,9 @@ public enum DrawOption {
|
||||
/**
|
||||
* Whether locked components should be rendered as transparent or not.
|
||||
*/
|
||||
LOCKED_ALPHA;
|
||||
LOCKED_ALPHA,
|
||||
/**
|
||||
* When applied, all colors will be converted to blackest black or whitest white
|
||||
*/
|
||||
MONOCHROMATIC;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
*/
|
||||
package org.diylc.common;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
import org.diylc.core.Project;
|
||||
@@ -55,12 +54,7 @@ public enum EventType {
|
||||
*/
|
||||
FILE_STATUS_CHANGED,
|
||||
/**
|
||||
* Called when selection rectangle is changed. Object of type {@link Rectangle} is the only
|
||||
* parameter and it contains the new selection rectangle.
|
||||
*/
|
||||
SELECTION_RECT_CHANGED,
|
||||
/**
|
||||
* Called when component selection is changed. New {@link ComponentSelection} is attached as a
|
||||
* Called when component selection is changed. New list of components and control point map is attached as a
|
||||
* parameter.
|
||||
*/
|
||||
SELECTION_CHANGED,
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
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.common;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.diylc.core.ComponentGroup;
|
||||
|
||||
/**
|
||||
* Interface for component grouping operations.
|
||||
*
|
||||
* @author Branislav Stojkovic
|
||||
*/
|
||||
public interface IGroupProcessor {
|
||||
|
||||
/**
|
||||
* Returns the set of component groups in the current project. Each group is a set of components
|
||||
* that are grouped together.
|
||||
*
|
||||
* @return set of component groups
|
||||
*/
|
||||
Set<ComponentGroup> getComponentGroups();
|
||||
|
||||
/**
|
||||
* Groups all selected components.
|
||||
*/
|
||||
void groupSelectedComponents();
|
||||
|
||||
/**
|
||||
* Ungroups all selected components.
|
||||
*/
|
||||
void ungroupSelectedComponents();
|
||||
}
|
||||
|
||||
@@ -41,16 +41,18 @@ 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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -84,18 +86,20 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP
|
||||
public static final String RENUMBER_ON_PASTE_KEY = "renumberOnPaste";
|
||||
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";
|
||||
|
||||
@@ -108,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
|
||||
@@ -137,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
|
||||
@@ -158,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
|
||||
*/
|
||||
@@ -191,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();
|
||||
@@ -200,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();
|
||||
@@ -208,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
|
||||
@@ -222,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
|
||||
@@ -269,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
|
||||
*/
|
||||
@@ -277,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
|
||||
@@ -291,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();
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,7 +31,7 @@ import org.diylc.core.ExpansionMode;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.measures.Size;
|
||||
|
||||
public interface ISelectionProcessor {
|
||||
public interface ISelectionProcessor extends IGroupProcessor {
|
||||
|
||||
/**
|
||||
* Returns the current {@link ComponentTransferable}.
|
||||
@@ -89,16 +89,6 @@ public interface ISelectionProcessor {
|
||||
*/
|
||||
void rotateSelection(int direction);
|
||||
|
||||
/**
|
||||
* Groups all selected components.
|
||||
*/
|
||||
void groupSelectedComponents();
|
||||
|
||||
/**
|
||||
* Ungroups all selected components.
|
||||
*/
|
||||
void ungroupSelectedComponents();
|
||||
|
||||
/**
|
||||
* Mirrors selected components in the given axis.
|
||||
*
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.diylc.core.IDIYComponent;
|
||||
@@ -52,6 +53,7 @@ public abstract class AbstractComponent<T> implements IDIYComponent<T> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected UUID id = UUID.randomUUID();
|
||||
protected String name = "";
|
||||
|
||||
public static Color SELECTION_COLOR = Color.red;
|
||||
@@ -62,6 +64,7 @@ public abstract class AbstractComponent<T> implements IDIYComponent<T> {
|
||||
public static Color LIGHT_METAL_COLOR = Color.decode("#EEEEEE");
|
||||
public static Color COPPER_COLOR = Color.decode("#DA8A67");
|
||||
public static Color GOLD_COLOR = Color.decode("#F4B301");
|
||||
public static Color BRASS_COLOR = Color.decode("#B8860B");
|
||||
public static Color FR4_COLOR = Color.decode("#6B9B6B");
|
||||
public static Color FR4_LIGHT_COLOR = Color.decode("#BABE71");
|
||||
public static Color PHENOLIC_COLOR = Color.decode("#F8EBB3");
|
||||
@@ -78,6 +81,19 @@ public abstract class AbstractComponent<T> implements IDIYComponent<T> {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getId() {
|
||||
if (id == null) {
|
||||
id = UUID.randomUUID();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(UUID id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canControlPointOverlap(int index) {
|
||||
return false;
|
||||
@@ -186,6 +202,10 @@ public abstract class AbstractComponent<T> implements IDIYComponent<T> {
|
||||
field.setAccessible(true);
|
||||
Object value = field.get(this);
|
||||
|
||||
if (field.getName().equalsIgnoreCase("id")) {
|
||||
value = getId();
|
||||
}
|
||||
|
||||
// Deep copy point arrays.
|
||||
// TODO: something nicer
|
||||
if (value != null && value.getClass().isArray()
|
||||
|
||||
@@ -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,12 @@ public abstract class AbstractCurvedComponent<T> extends AbstractTransparentComp
|
||||
// }
|
||||
}
|
||||
|
||||
@EditableProperty
|
||||
@EditableProperty(name = "Length")
|
||||
public Size getLength() {
|
||||
return calculateLength();
|
||||
}
|
||||
|
||||
public Size calculateLength() {
|
||||
double d = 0;
|
||||
try {
|
||||
Point2D[] p = getControlPoints();
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.diylc.core;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ComponentGroup implements Serializable {
|
||||
|
||||
private Set<UUID> componentIds;
|
||||
private String name;
|
||||
|
||||
public ComponentGroup(Set<UUID> componentIds) {
|
||||
this.componentIds = componentIds;
|
||||
}
|
||||
|
||||
public ComponentGroup(Set<UUID> componentIds, String name) {
|
||||
this.componentIds = componentIds;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public static ComponentGroup from(Collection<IDIYComponent<?>> components, String name) {
|
||||
Set<UUID> componentIds = components.stream()
|
||||
.map(IDIYComponent::getId)
|
||||
.filter(id -> id != null)
|
||||
.collect(Collectors.toSet());
|
||||
return new ComponentGroup(componentIds, name);
|
||||
}
|
||||
|
||||
public Set<UUID> getComponentIds() {
|
||||
return componentIds;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import java.awt.Graphics2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.diylc.core.annotations.ComponentDescriptor;
|
||||
import org.diylc.core.annotations.EditableProperty;
|
||||
@@ -55,6 +56,10 @@ public interface IDIYComponent<T> extends Serializable, Cloneable {
|
||||
public static final int WIRING = 5;
|
||||
public static final int TEXT = 6;
|
||||
|
||||
UUID getId();
|
||||
|
||||
void setId(UUID id);
|
||||
|
||||
/**
|
||||
* @return component instance name.
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -23,13 +23,9 @@ package org.diylc.core;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.diylc.appframework.update.VersionNumber;
|
||||
|
||||
import org.diylc.core.annotations.EditableProperty;
|
||||
@@ -67,6 +63,7 @@ public class Project implements Serializable, Cloneable {
|
||||
private Integer dotSpacing;
|
||||
private List<IDIYComponent<?>> components;
|
||||
private Set<Set<IDIYComponent<?>>> groups;
|
||||
private Set<ComponentGroup> groupsEx;
|
||||
private Set<Integer> lockedLayers;
|
||||
private Set<IDIYComponent<?>> lockedComponents;
|
||||
private Set<Integer> hiddenLayers;
|
||||
@@ -75,6 +72,7 @@ public class Project implements Serializable, Cloneable {
|
||||
public Project() {
|
||||
components = new ArrayList<IDIYComponent<?>>();
|
||||
groups = new HashSet<Set<IDIYComponent<?>>>();
|
||||
groupsEx = new HashSet<>();
|
||||
lockedLayers = new HashSet<Integer>();
|
||||
hiddenLayers = new HashSet<Integer>();
|
||||
lockedComponents = new HashSet<IDIYComponent<?>>();
|
||||
@@ -153,22 +151,35 @@ public class Project implements Serializable, Cloneable {
|
||||
|
||||
/**
|
||||
* List of components sorted by z-order ascending.
|
||||
*
|
||||
* s
|
||||
* @return
|
||||
*/
|
||||
public List<IDIYComponent<?>> getComponents() {
|
||||
return components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set of grouped components.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Set<Set<IDIYComponent<?>>> getGroups() {
|
||||
return groups;
|
||||
public Set<ComponentGroup> getGroupsEx() {
|
||||
if (groupsEx == null) {
|
||||
groupsEx = new HashSet<>();
|
||||
|
||||
if (groups != null && !groups.isEmpty()) {
|
||||
for (Set<IDIYComponent<?>> group : groups) {
|
||||
Set<IDIYComponent<?>> nonNullIds =
|
||||
group.stream().filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
if (!nonNullIds.isEmpty()) {
|
||||
ComponentGroup newGroup = ComponentGroup.from(group, null);
|
||||
groupsEx.add(newGroup);
|
||||
}
|
||||
}
|
||||
groups.clear();
|
||||
}
|
||||
}
|
||||
groupsEx.removeAll(groupsEx.stream().
|
||||
filter(x -> x.getComponentIds() == null ||
|
||||
x.getComponentIds().stream().noneMatch(Objects::nonNull)).collect(Collectors.toSet()));
|
||||
return groupsEx;
|
||||
}
|
||||
|
||||
|
||||
public Set<IDIYComponent<?>> getLockedComponents() {
|
||||
if (lockedComponents == null)
|
||||
lockedComponents = new HashSet<IDIYComponent<?>>();
|
||||
@@ -292,6 +303,11 @@ public class Project implements Serializable, Cloneable {
|
||||
return false;
|
||||
} else if (!groups.equals(other.groups))
|
||||
return false;
|
||||
if (groupsEx == null) {
|
||||
if (other.groupsEx != null)
|
||||
return false;
|
||||
} else if (!groupsEx.equals(other.groupsEx))
|
||||
return false;
|
||||
if (lockedLayers == null) {
|
||||
if (other.lockedLayers != null)
|
||||
return false;
|
||||
@@ -356,12 +372,15 @@ public class Project implements Serializable, Cloneable {
|
||||
}
|
||||
}
|
||||
|
||||
for (Set<IDIYComponent<?>> group : this.groups) {
|
||||
Set<IDIYComponent<?>> cloneGroup = new HashSet<IDIYComponent<?>>();
|
||||
for (IDIYComponent<?> component : group) {
|
||||
cloneGroup.add(cloneMap.get(component));
|
||||
for (ComponentGroup group : this.getGroupsEx()) {
|
||||
// Filter out null IDs when cloning groups
|
||||
Set<UUID> validIds = group.getComponentIds().stream()
|
||||
.filter(id -> id != null)
|
||||
.collect(Collectors.toSet());
|
||||
// Only add group if it has valid component IDs
|
||||
if (!validIds.isEmpty()) {
|
||||
project.getGroupsEx().add(new ComponentGroup(validIds, group.getName()));
|
||||
}
|
||||
project.groups.add(cloneGroup);
|
||||
}
|
||||
|
||||
for (IDIYComponent<?> component : this.getLockedComponents()) {
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
package org.diylc.common;
|
||||
package org.diylc.filter;
|
||||
|
||||
import org.diylc.core.IDIYComponent;
|
||||
|
||||
+1
-2
@@ -19,11 +19,10 @@
|
||||
along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
package org.diylc.presenter;
|
||||
package org.diylc.filter;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.diylc.common.IComponentFilter;
|
||||
import org.diylc.common.PCBLayer;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.diylc.filter;
|
||||
|
||||
import org.diylc.core.IBoard;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.gerber.GerberRenderMode;
|
||||
import org.diylc.core.gerber.IGerberComponent;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class SilkScreenFilter implements IComponentFilter {
|
||||
|
||||
@Override
|
||||
public boolean testComponent(IDIYComponent<?> component) {
|
||||
if (component instanceof IBoard board) {
|
||||
return board.shouldExportToGerber();
|
||||
}
|
||||
if (!(component instanceof IGerberComponent gerberComponent)) {
|
||||
return false;
|
||||
}
|
||||
Set<GerberRenderMode> gerberRenderModes = gerberComponent.getGerberRenderModes();
|
||||
return gerberRenderModes.contains(GerberRenderMode.Outline);
|
||||
}
|
||||
}
|
||||
@@ -18,13 +18,8 @@
|
||||
package org.diylc.presenter;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.diylc.appframework.miscutils.IConfigurationManager;
|
||||
|
||||
@@ -145,6 +140,7 @@ public class BuildingBlockManager {
|
||||
clone.setName(instantiationManager.createUniqueName(ComponentProcessor.getInstance()
|
||||
.extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) clone.getClass()),
|
||||
testComponents));
|
||||
clone.setId(UUID.randomUUID());
|
||||
testComponents.add(clone);
|
||||
clones.add(clone);
|
||||
} catch (CloneNotSupportedException e) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.diylc.presenter;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.List;
|
||||
|
||||
@@ -71,4 +72,54 @@ public class CalcUtils {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the relative luminance (perceived brightness) of a color.
|
||||
* Uses the standard formula: L = 0.299*R + 0.587*G + 0.114*B
|
||||
*
|
||||
* @param color the color to calculate luminance for
|
||||
* @return the luminance value between 0 and 255
|
||||
*/
|
||||
public static double calculateLuminance(Color color) {
|
||||
if (color == null) {
|
||||
return 0.0;
|
||||
}
|
||||
return 0.299 * color.getRed() + 0.587 * color.getGreen() + 0.114 * color.getBlue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a color to monochrome (black or white) based on its luminance.
|
||||
* Colors with luminance below the threshold (128) are converted to black,
|
||||
* otherwise to white. The alpha channel is preserved if present.
|
||||
*
|
||||
* @param color the color to convert
|
||||
* @param threshold the luminance threshold (default 128 for middle gray)
|
||||
* @return black or white color, preserving alpha channel
|
||||
*/
|
||||
public static Color convertToMonochrome(Color color, double threshold) {
|
||||
if (color == null) {
|
||||
return null;
|
||||
}
|
||||
double luminance = calculateLuminance(color);
|
||||
// Use <= to handle edge case where luminance equals threshold (should be white)
|
||||
// Add small epsilon to account for floating point precision issues
|
||||
Color monochromeColor = (luminance < threshold - 1e-10) ? Color.BLACK : Color.WHITE;
|
||||
// Preserve alpha channel if present
|
||||
if (color.getAlpha() < 255) {
|
||||
monochromeColor = new Color(monochromeColor.getRed(), monochromeColor.getGreen(),
|
||||
monochromeColor.getBlue(), color.getAlpha());
|
||||
}
|
||||
return monochromeColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a color to monochrome (black or white) based on its luminance.
|
||||
* Uses the default threshold of 128 (middle gray).
|
||||
*
|
||||
* @param color the color to convert
|
||||
* @return black or white color, preserving alpha channel
|
||||
*/
|
||||
public static Color convertToMonochrome(Color color) {
|
||||
return convertToMonochrome(color, 128.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@ import java.awt.image.BufferedImage;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import org.apache.log4j.Logger;
|
||||
@@ -63,6 +67,8 @@ public class ComponentProcessor {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(ComponentProcessor.class);
|
||||
|
||||
private static final boolean LOG_COMPONENT_TYPE_DETAILS = false;
|
||||
|
||||
private static ComponentProcessor instance;
|
||||
|
||||
private Map<String, List<PropertyWrapper>> propertyCache;
|
||||
@@ -219,6 +225,18 @@ public class ComponentProcessor {
|
||||
return cloneProperties(properties);
|
||||
}
|
||||
|
||||
private static String formatPropertyType(Class<?> type) {
|
||||
return type == null ? null : type.getSimpleName();
|
||||
}
|
||||
|
||||
private static List<String> getPossibleValues(Class<?> type) {
|
||||
if (type == null || !type.isEnum())
|
||||
return null;
|
||||
return Arrays.stream(type.getEnumConstants())
|
||||
.map(Object::toString)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<PropertyWrapper> cloneProperties(List<PropertyWrapper> properties) {
|
||||
List<PropertyWrapper> result = new ArrayList<PropertyWrapper>(properties.size());
|
||||
for (PropertyWrapper propertyWrapper : properties) {
|
||||
@@ -243,6 +261,7 @@ public class ComponentProcessor {
|
||||
Set<Class<?>> componentTypeClasses = null;
|
||||
try {
|
||||
componentTypeClasses = reflections.getTypesAnnotatedWith(ComponentDescriptor.class, false);
|
||||
List<Map<String, Object>> componentSnapshots = LOG_COMPONENT_TYPE_DETAILS ? new ArrayList<>() : null;
|
||||
|
||||
for (Class<?> clazz : componentTypeClasses) {
|
||||
if (!Modifier.isAbstract(clazz.getModifiers()) && IDIYComponent.class.isAssignableFrom(clazz)) {
|
||||
@@ -262,6 +281,42 @@ public class ComponentProcessor {
|
||||
componentTypes.put(componentType.getCategory(), nestedList);
|
||||
}
|
||||
nestedList.add(componentType);
|
||||
|
||||
if (LOG_COMPONENT_TYPE_DETAILS) {
|
||||
try {
|
||||
Map<String, Object> snapshot = new LinkedHashMap<>();
|
||||
snapshot.put("name", componentType.getName());
|
||||
snapshot.put("description", componentType.getDescription());
|
||||
snapshot.put("creationMethod", componentType.getCreationMethod());
|
||||
snapshot.put("category", componentType.getCategory());
|
||||
List<PropertyWrapper> props = propertyCache.get(clazz.getName());
|
||||
snapshot.put("properties", props != null ? props.stream()
|
||||
.map(p -> {
|
||||
Map<String, Object> prop = new LinkedHashMap<>();
|
||||
prop.put("name", p.getName());
|
||||
prop.put("type", formatPropertyType(p.getType()));
|
||||
List<String> possibleValues = getPossibleValues(p.getType());
|
||||
if (possibleValues != null) {
|
||||
prop.put("possibleValues", possibleValues);
|
||||
}
|
||||
return prop;
|
||||
})
|
||||
.collect(Collectors.toList()) : Collections.emptyList());
|
||||
componentSnapshots.add(snapshot);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Failed to log component type details for " + clazz.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (LOG_COMPONENT_TYPE_DETAILS && componentSnapshots != null && !componentSnapshots.isEmpty()) {
|
||||
try {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||
LOG.info("Component types: " + mapper.writeValueAsString(componentSnapshots));
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Failed to log component types JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,10 +26,12 @@ import java.lang.ref.SoftReference;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import org.diylc.common.ComponentType;
|
||||
import org.diylc.common.DrawOption;
|
||||
import org.diylc.core.ComponentForRender;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
@@ -67,14 +69,14 @@ public class DrawingCache {
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void bulkPrepare(Collection<ComponentForRender> components, G2DWrapper g2d, boolean outlineMode, Project project,
|
||||
double zoom, double scaleFactor) {
|
||||
double zoom, double scaleFactor, Set<DrawOption> drawOptions) {
|
||||
// run all renders in parallel to utilize all CPU cores
|
||||
components.stream().parallel().forEach(x -> {
|
||||
ComponentType type = ComponentProcessor.getInstance()
|
||||
.extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) x.getComponent().getClass());
|
||||
|
||||
if (type != null && type.getEnableCache()) {
|
||||
renderAndCache(x.getComponent(), g2d, x.getState(), outlineMode, project, zoom, scaleFactor, x.getZOrder());
|
||||
renderAndCache(x.getComponent(), g2d, x.getState(), outlineMode, project, zoom, scaleFactor, x.getZOrder(), drawOptions);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -92,7 +94,7 @@ public class DrawingCache {
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void draw(IDIYComponent<?> component, G2DWrapper g2d, ComponentState componentState,
|
||||
boolean outlineMode, Project project, double zoom, double scaleFactor, int zOrder, Rectangle2D visibleRect) {
|
||||
boolean outlineMode, Project project, double zoom, double scaleFactor, int zOrder, Rectangle2D visibleRect, Set<DrawOption> drawOptions) {
|
||||
ComponentType type = ComponentProcessor.getInstance()
|
||||
.extractComponentTypeFrom((Class<? extends IDIYComponent<?>>) component.getClass());
|
||||
|
||||
@@ -100,7 +102,7 @@ public class DrawingCache {
|
||||
// if we need to apply caching
|
||||
Point2D firstPoint = component.getControlPoint(0);
|
||||
|
||||
CacheValue value = renderAndCache(component, g2d, componentState, outlineMode, project, zoom, scaleFactor, zOrder);
|
||||
CacheValue value = renderAndCache(component, g2d, componentState, outlineMode, project, zoom, scaleFactor, zOrder, drawOptions);
|
||||
|
||||
if (value == null) {
|
||||
return;
|
||||
@@ -180,7 +182,7 @@ public class DrawingCache {
|
||||
}
|
||||
|
||||
private CacheValue renderAndCache(IDIYComponent<?> component, G2DWrapper g2d, ComponentState componentState,
|
||||
boolean outlineMode, Project project, double zoom, double scaleFactor, int zOrder) {
|
||||
boolean outlineMode, Project project, double zoom, double scaleFactor, int zOrder, Set<DrawOption> drawOptions) {
|
||||
|
||||
if (!g2d.getClipBounds().intersects(component.getCachingBounds())) {
|
||||
return null;
|
||||
@@ -199,9 +201,11 @@ public class DrawingCache {
|
||||
}
|
||||
|
||||
// only honor the cache if the component hasn't changed in the meantime
|
||||
// Also check if drawOptions changed (e.g., MONOCHROMATIC affects rendering)
|
||||
boolean drawOptionsChanged = value == null || !value.getDrawOptions().equals(drawOptions);
|
||||
if (value == null || !value.getComponent().equalsTo(component)
|
||||
|| value.getState() != componentState || value.getZoom() != zoom || value.getOutlineMode() != outlineMode
|
||||
|| value.getScaleFactor() != scaleFactor) {
|
||||
|| value.getScaleFactor() != scaleFactor || drawOptionsChanged) {
|
||||
// figure out size and placement of buffer image
|
||||
Rectangle2D rect = component.getCachingBounds();
|
||||
int width = (int)Math.round(rect.getWidth() * zoom);
|
||||
@@ -233,7 +237,7 @@ public class DrawingCache {
|
||||
cg2d.translate(-firstPoint.getX() + dx, -firstPoint.getY() + dy);
|
||||
|
||||
// wrap the graphics so we can track
|
||||
G2DWrapper wrapper = new G2DWrapper(cg2d, zoom);
|
||||
G2DWrapper wrapper = new G2DWrapper(cg2d, zoom, drawOptions);
|
||||
|
||||
// initialize wrapper
|
||||
wrapper.startedDrawingComponent(zOrder);
|
||||
@@ -275,7 +279,7 @@ public class DrawingCache {
|
||||
|
||||
// add to the cache
|
||||
value = new CacheValue(component, image, wrapper.getCurrentArea(), wrapper.getContinuityPositiveAreas(),
|
||||
wrapper.getContinuityNegativeAreas(), componentState, outlineMode, zoom, scaleFactor, dx, dy, rect);
|
||||
wrapper.getContinuityNegativeAreas(), componentState, outlineMode, zoom, scaleFactor, dx, dy, rect, drawOptions);
|
||||
imageCache.put(component, new SoftReference<DrawingCache.CacheValue>(value));
|
||||
}
|
||||
|
||||
@@ -295,11 +299,12 @@ public class DrawingCache {
|
||||
int dx;
|
||||
int dy;
|
||||
Rectangle2D cacheBounds;
|
||||
Set<DrawOption> drawOptions;
|
||||
|
||||
public CacheValue(IDIYComponent<?> component, BufferedImage image, Area currentArea,
|
||||
Map<String, Area> continuityPositiveAreas, Map<String, Area> continuityNegativeAreas,
|
||||
ComponentState state, boolean outlineMode, double zoom, double scaleFactor, int dx, int dy,
|
||||
Rectangle2D cacheBounds) {
|
||||
Rectangle2D cacheBounds, Set<DrawOption> drawOptions) {
|
||||
super();
|
||||
this.cacheBounds = cacheBounds;
|
||||
// take a copy of the component, so we can check if it changed in the meantime
|
||||
@@ -317,6 +322,7 @@ public class DrawingCache {
|
||||
this.dx = dx;
|
||||
this.dy = dy;
|
||||
this.scaleFactor = scaleFactor;
|
||||
this.drawOptions = drawOptions;
|
||||
}
|
||||
|
||||
public IDIYComponent<?> getComponent() {
|
||||
@@ -366,6 +372,10 @@ public class DrawingCache {
|
||||
public Rectangle2D getCacheBounds() {
|
||||
return cacheBounds;
|
||||
}
|
||||
|
||||
public Set<DrawOption> getDrawOptions() {
|
||||
return drawOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
@@ -54,6 +54,7 @@ import org.diylc.components.AbstractTransparentComponent;
|
||||
import org.diylc.core.*;
|
||||
import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
import org.diylc.filter.IComponentFilter;
|
||||
import org.diylc.netlist.ContinuityGraph;
|
||||
import org.diylc.netlist.NetlistBuilder;
|
||||
import org.diylc.utils.Constants;
|
||||
@@ -183,7 +184,7 @@ public class DrawingManager {
|
||||
if (externalZoom != null)
|
||||
zoom *= externalZoom;
|
||||
|
||||
G2DWrapper g2dWrapper = new G2DWrapper(g2d, zoom);
|
||||
G2DWrapper g2dWrapper = new G2DWrapper(g2d, zoom, drawOptions);
|
||||
|
||||
configureRenderingHints(g2d, drawOptions);
|
||||
|
||||
@@ -267,7 +268,7 @@ public class DrawingManager {
|
||||
|
||||
// give the cache a chance to prepare all the components in multiple threads
|
||||
if (drawOptions.contains(DrawOption.ENABLE_CACHING)) {
|
||||
DrawingCache.Instance.bulkPrepare(componentForRenderList, g2dWrapper, outlineMode, project, zoom, scaleFactor);
|
||||
DrawingCache.Instance.bulkPrepare(componentForRenderList, g2dWrapper, outlineMode, project, zoom, scaleFactor, drawOptions);
|
||||
}
|
||||
|
||||
// componentAreaMap.clear();
|
||||
@@ -561,10 +562,12 @@ public class DrawingManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static void drawComponent(Project project, Set<DrawOption> drawOptions, Double scaleFactor, Rectangle2D visibleRect, IDIYComponent<?> component, G2DWrapper g2dWrapper, ComponentState state, double zoom, int i, boolean outlineMode) {
|
||||
private static void drawComponent(Project project, Set<DrawOption> drawOptions, Double scaleFactor,
|
||||
Rectangle2D visibleRect, IDIYComponent<?> component, G2DWrapper g2dWrapper, ComponentState state,
|
||||
double zoom, int i, boolean outlineMode) {
|
||||
if (drawOptions.contains(DrawOption.ENABLE_CACHING)) // go through the DrawingCache
|
||||
DrawingCache.Instance.draw(component, g2dWrapper, state,
|
||||
drawOptions.contains(DrawOption.OUTLINE_MODE), project, zoom, scaleFactor, i, visibleRect);
|
||||
drawOptions.contains(DrawOption.OUTLINE_MODE), project, zoom, scaleFactor, i, visibleRect, drawOptions);
|
||||
else // go straight to the wrapper
|
||||
component.draw(g2dWrapper, state, outlineMode, project, g2dWrapper);
|
||||
}
|
||||
|
||||
@@ -53,17 +53,11 @@ import java.awt.image.renderable.RenderableImage;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.text.AttributedCharacterIterator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.diylc.common.DrawOption;
|
||||
import org.diylc.common.ObjectCache;
|
||||
import org.diylc.common.ZoomableStroke;
|
||||
import org.diylc.core.IDrawingObserver;
|
||||
@@ -71,7 +65,7 @@ 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
|
||||
* {@link #startedDrawingComponent(int)} 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.
|
||||
*
|
||||
@@ -96,7 +90,9 @@ class G2DWrapper extends Graphics2D implements IDrawingObserver {
|
||||
|
||||
private int uniqueCounter = 0;
|
||||
|
||||
private Graphics2D canvasGraphics;
|
||||
private final Graphics2D canvasGraphics;
|
||||
private final Set<DrawOption> drawOptions;
|
||||
|
||||
private Stroke originalStroke;
|
||||
private Color originalColor;
|
||||
private Composite originalComposite;
|
||||
@@ -115,12 +111,14 @@ class G2DWrapper extends Graphics2D implements IDrawingObserver {
|
||||
|
||||
/**
|
||||
* Creates a wrapper around specified {@link Graphics2D} object.
|
||||
*
|
||||
*
|
||||
* @param canvasGraphics
|
||||
* @param drawOptions
|
||||
*/
|
||||
public G2DWrapper(Graphics2D canvasGraphics, double zoom) {
|
||||
public G2DWrapper(Graphics2D canvasGraphics, double zoom, Set<DrawOption> drawOptions) {
|
||||
super();
|
||||
this.canvasGraphics = canvasGraphics;
|
||||
this.drawOptions = drawOptions;
|
||||
String debugAreaPerformanceStr = System.getProperty(DEBUG_AREA_PERFORMANCE);
|
||||
debugAreaPerformance = "true".equalsIgnoreCase(debugAreaPerformanceStr);
|
||||
this.zoom = zoom;
|
||||
@@ -482,7 +480,9 @@ class G2DWrapper extends Graphics2D implements IDrawingObserver {
|
||||
|
||||
@Override
|
||||
public void setComposite(Composite comp) {
|
||||
canvasGraphics.setComposite(comp);
|
||||
if (!drawOptions.contains(DrawOption.OUTLINE_MODE)) {
|
||||
canvasGraphics.setComposite(comp);
|
||||
}
|
||||
// FIXME: check this.
|
||||
}
|
||||
|
||||
@@ -758,7 +758,11 @@ class G2DWrapper extends Graphics2D implements IDrawingObserver {
|
||||
|
||||
@Override
|
||||
public void setColor(Color c) {
|
||||
canvasGraphics.setColor(c);
|
||||
if (drawOptions.contains(DrawOption.MONOCHROMATIC) && c != null) {
|
||||
canvasGraphics.setColor(CalcUtils.convertToMonochrome(c));
|
||||
} else {
|
||||
canvasGraphics.setColor(c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,13 +24,7 @@ package org.diylc.presenter;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
@@ -40,6 +34,7 @@ import org.diylc.clipboard.ComponentTransferable;
|
||||
import org.diylc.common.*;
|
||||
|
||||
import org.diylc.core.Angle;
|
||||
import org.diylc.core.ComponentGroup;
|
||||
import org.diylc.core.CreationMethod;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.Project;
|
||||
@@ -68,9 +63,9 @@ public class InstantiationManager {
|
||||
public static final ComponentType clipboardType = new ComponentType("Clipboard contents",
|
||||
"Components from the clipboard", CreationMethod.SINGLE_CLICK, "Multi", "", "", null, null, 0, false, null,
|
||||
false, null, KeywordPolicy.NEVER_SHOW, null, false, null, 0);
|
||||
public static final ComponentType blockType = new ComponentType("Building block",
|
||||
"Components from the building block", CreationMethod.SINGLE_CLICK, "Multi", "", "", null, null, 0, false, null,
|
||||
false, null, KeywordPolicy.NEVER_SHOW, null, false, null, 0);
|
||||
// public static final ComponentType blockType = new ComponentType("Building block",
|
||||
// "Components from the building block", CreationMethod.SINGLE_CLICK, "Multi", "", "", null, null, 0, false, null,
|
||||
// false, null, KeywordPolicy.NEVER_SHOW, null, false, null, 0);
|
||||
|
||||
public InstantiationManager() {}
|
||||
|
||||
@@ -213,7 +208,10 @@ public class InstantiationManager {
|
||||
|
||||
// Update the component type slot so the app knows that something's
|
||||
// being instantiated.
|
||||
this.componentTypeSlot = autoGroup ? blockType : clipboardType;
|
||||
String slotName = Optional.ofNullable(componentTransferable.getName()).orElse("Building Block");
|
||||
this.componentTypeSlot = autoGroup ? new ComponentType(slotName,
|
||||
"Components from the building block", CreationMethod.SINGLE_CLICK, "Multi", "", "", null, null, 0, false, null,
|
||||
false, null, KeywordPolicy.NEVER_SHOW, null, false, null, 0) : clipboardType;
|
||||
|
||||
if (snapToGrid) {
|
||||
scaledPoint = new Point2D.Double(scaledPoint.getX(), scaledPoint.getY());
|
||||
@@ -222,9 +220,14 @@ public class InstantiationManager {
|
||||
// Update the location according to mouse location
|
||||
updateSingleClick(scaledPoint, snapToGrid, gridSpacing);
|
||||
|
||||
// copy group information if available
|
||||
// copy group information if available, but filter out invalid groups
|
||||
if (componentTransferable.getGroups() != null) {
|
||||
currentProject.getGroups().addAll(componentTransferable.getGroups());
|
||||
for (ComponentGroup group : componentTransferable.getGroups()) {
|
||||
if (group.getComponentIds() != null && !group.getComponentIds().isEmpty() &&
|
||||
group.getComponentIds().stream().anyMatch(id -> id != null)) {
|
||||
currentProject.getGroupsEx().add(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,7 +276,7 @@ public class InstantiationManager {
|
||||
private List<IDIYComponent<?>> instantiateComponent(ComponentType componentType, Point2D point,
|
||||
Project currentProject) throws InstantiationException, IllegalAccessException, NoSuchMethodException,
|
||||
SecurityException, IllegalArgumentException, InvocationTargetException {
|
||||
LOG.info("Instatiating component of type: " + componentType.getInstanceClass().getName());
|
||||
LOG.info("Instantiating component of type: " + componentType.getInstanceClass().getName());
|
||||
|
||||
// Instantiate the component.
|
||||
IDIYComponent<?> component;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,131 @@
|
||||
You are helping me generate Appendix A ? Component Model Reference for an ebook about DIY Layout Creator (DIYLC).
|
||||
|
||||
I will provide you with a JSON file that defines all component types used by DIYLC.
|
||||
|
||||
Your task
|
||||
|
||||
Convert the JSON into a single DOCX appendix that documents all components defined in the JSON, with no omissions and no inferred or invented information.
|
||||
|
||||
Structure rules (must follow exactly)
|
||||
1. Document structure
|
||||
|
||||
Output format: DOCX
|
||||
|
||||
Document title (H1):
|
||||
Appendix A ? Component Model Reference
|
||||
|
||||
Group components by category
|
||||
|
||||
Each category is an H2 heading
|
||||
|
||||
Category order: alphabetical
|
||||
|
||||
Component order within category: alphabetical
|
||||
|
||||
2. Component entry format
|
||||
|
||||
Do NOT use H3 or any heading for component names.
|
||||
|
||||
For each component, render the following:
|
||||
|
||||
Component Name (bold, normal paragraph)
|
||||
Description: <exact description text from JSON>
|
||||
Creation method: <human-friendly label>
|
||||
|
||||
|
||||
Creation method mapping:
|
||||
|
||||
POINT_BY_POINT ? Point-by-point
|
||||
|
||||
SINGLE_CLICK ? Single-click
|
||||
|
||||
Missing value ? (not specified)
|
||||
|
||||
3. Properties table
|
||||
|
||||
If the component defines properties, render a table immediately after the metadata:
|
||||
|
||||
| Property | Type | Possible values |
|
||||
|
||||
Table formatting requirements:
|
||||
|
||||
No visible borders (markdown-style look)
|
||||
|
||||
Column widths:
|
||||
|
||||
Property: 30%
|
||||
|
||||
Type: 30%
|
||||
|
||||
Possible values: 40%
|
||||
|
||||
Header row:
|
||||
|
||||
Bold text
|
||||
|
||||
Very light gray background (#EEEEEE)
|
||||
|
||||
Data rows:
|
||||
|
||||
Alternating row background
|
||||
|
||||
Very very light gray (#F7F7F7) on every second row
|
||||
|
||||
Row height:
|
||||
|
||||
As compact as possible
|
||||
|
||||
No extra vertical padding
|
||||
|
||||
Content rules:
|
||||
|
||||
Property name ? JSON name
|
||||
|
||||
Type ? JSON type
|
||||
|
||||
Possible values:
|
||||
|
||||
Populate only if the JSON defines an enum/list
|
||||
|
||||
Leave empty otherwise
|
||||
|
||||
If a component has no properties, write:
|
||||
|
||||
This component has no configurable properties.
|
||||
|
||||
4. Styling consistency
|
||||
|
||||
Font: Calibri
|
||||
|
||||
Base text size: 11 pt
|
||||
|
||||
Match the visual style of previously generated ebook chapters
|
||||
|
||||
No decorative elements
|
||||
|
||||
Clean, technical, reference-style layout
|
||||
|
||||
Strict constraints
|
||||
|
||||
? Do NOT summarize components
|
||||
|
||||
? Do NOT invent descriptions, properties, or values
|
||||
|
||||
? Do NOT rename properties or types
|
||||
|
||||
? Do NOT add commentary or interpretation
|
||||
|
||||
? Do NOT introduce additional columns
|
||||
|
||||
Everything must come directly from the JSON.
|
||||
|
||||
Final output
|
||||
|
||||
Produce one DOCX file
|
||||
|
||||
File name:
|
||||
Appendix A ? Component Model Reference.docx
|
||||
|
||||
When finished, provide a download link to the DOCX file.
|
||||
|
||||
Acknowledge when you are ready to receive the JSON file.
|
||||
@@ -36,6 +36,7 @@ import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
@@ -54,6 +55,7 @@ import com.thoughtworks.xstream.mapper.MapperWrapper;
|
||||
import com.thoughtworks.xstream.security.AnyTypePermission;
|
||||
|
||||
import org.diylc.common.EventType;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.Project;
|
||||
import org.diylc.presenter.Presenter;
|
||||
import org.diylc.test.DIYTest;
|
||||
@@ -112,6 +114,7 @@ public class ProjectFileManager {
|
||||
xStream.autodetectAnnotations(true);
|
||||
xStream.alias("point", java.awt.geom.Point2D.class);
|
||||
xStream.alias("point", java.awt.geom.Point2D.Double.class);
|
||||
xStream.alias("group", org.diylc.core.ComponentGroup.class);
|
||||
xStream.alias("font", java.awt.Font.class);
|
||||
xStream.alias("project", Project.class);
|
||||
xStream.aliasPackage("diylc", "org.diylc.components");
|
||||
@@ -278,9 +281,29 @@ public class ProjectFileManager {
|
||||
LOG.warn("The opened file is missing the following properties: " + String.join(", ", missingFields));
|
||||
warnings.add("The project references unknown component properties, most likely because it was created with a newer version of DIYLC.");
|
||||
}
|
||||
ensureUniqueComponentIds(project);
|
||||
fis.close();
|
||||
return project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures no two components share the same identifier. Assigns a new UUID to any
|
||||
* component that has a null id or an id already seen (duplicate).
|
||||
*/
|
||||
private void ensureUniqueComponentIds(Project project) {
|
||||
if (project == null || project.getComponents() == null) {
|
||||
return;
|
||||
}
|
||||
Set<UUID> seenIds = new HashSet<UUID>();
|
||||
for (IDIYComponent<?> component : project.getComponents()) {
|
||||
UUID id = component.getId();
|
||||
if (id == null || seenIds.contains(id)) {
|
||||
component.setId(UUID.randomUUID());
|
||||
LOG.warn("Detected duplicated componentId: " + id);
|
||||
}
|
||||
seenIds.add(component.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private VersionNumber readV3Version(String fileName) throws Exception {
|
||||
File fXmlFile = new File(fileName);
|
||||
|
||||
@@ -4128,4 +4128,204 @@
|
||||
</org.diylc.core.Template>
|
||||
</list>
|
||||
</entry>
|
||||
</map>
|
||||
<entry>
|
||||
<string>org.diylc.components.electromechanical.TactileMicroSwitch</string>
|
||||
<list>
|
||||
<org.diylc.core.Template>
|
||||
<name>6x6 (e.g. Omron B3F-1000)</name>
|
||||
<values>
|
||||
<entry>
|
||||
<string>Spacing X</string>
|
||||
<org.diylc.core.measures.Size value="6.5" unit="mm"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Spacing Y</string>
|
||||
<org.diylc.core.measures.Size value="4.5" unit="mm"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Label</string>
|
||||
<awt-color hex="ffffff"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Orientation</string>
|
||||
<org.diylc.common.Orientation>DEFAULT</org.diylc.common.Orientation>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Case</string>
|
||||
<org.diylc.core.measures.Size value="6.0" unit="mm"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Name</string>
|
||||
<string>SW2</string>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Font Size Override</string>
|
||||
<null/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Value</string>
|
||||
<string></string>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Alpha</string>
|
||||
<byte>127</byte>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Body</string>
|
||||
<awt-color hex="c0c0c0"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Border</string>
|
||||
<awt-color hex="595959"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Display</string>
|
||||
<org.diylc.common.Display>BOTH</org.diylc.common.Display>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Pin Pairing Mode</string>
|
||||
<diylc.electromechanical.TactileMicroSwitch_-PinPairingMode>Opposite</diylc.electromechanical.TactileMicroSwitch_-PinPairingMode>
|
||||
</entry>
|
||||
</values>
|
||||
<points>
|
||||
<point x="0.0" y="0.0"/>
|
||||
<point x="0.0" y="0.175"/>
|
||||
<point x="0.255" y="0.0"/>
|
||||
<point x="0.255" y="0.175"/>
|
||||
</points>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-01-02T10:01:17.805688</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>12x12 (e.g. Omron B3F-4000)</name>
|
||||
<values>
|
||||
<entry>
|
||||
<string>Spacing X</string>
|
||||
<org.diylc.core.measures.Size value="12.6" unit="mm"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Spacing Y</string>
|
||||
<org.diylc.core.measures.Size value="5.0" unit="mm"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Label</string>
|
||||
<awt-color hex="ffffff"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Orientation</string>
|
||||
<org.diylc.common.Orientation>DEFAULT</org.diylc.common.Orientation>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Case</string>
|
||||
<org.diylc.core.measures.Size value="12.0" unit="mm"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Name</string>
|
||||
<string>SW1</string>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Font Size Override</string>
|
||||
<null/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Value</string>
|
||||
<string></string>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Alpha</string>
|
||||
<byte>127</byte>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Body</string>
|
||||
<awt-color hex="c0c0c0"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Border</string>
|
||||
<awt-color hex="595959"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Display</string>
|
||||
<org.diylc.common.Display>BOTH</org.diylc.common.Display>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Pin Pairing Mode</string>
|
||||
<diylc.electromechanical.TactileMicroSwitch_-PinPairingMode>Opposite</diylc.electromechanical.TactileMicroSwitch_-PinPairingMode>
|
||||
</entry>
|
||||
</values>
|
||||
<points>
|
||||
<point x="0.0" y="0.0"/>
|
||||
<point x="0.0" y="0.195"/>
|
||||
<point x="0.495" y="0.0"/>
|
||||
<point x="0.495" y="0.195"/>
|
||||
</points>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-01-02T10:00:31.950676</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
<org.diylc.core.Template>
|
||||
<name>5x5 Low Profile SMD (e.g. Alps SKQG)</name>
|
||||
<values>
|
||||
<entry>
|
||||
<string>Spacing X</string>
|
||||
<org.diylc.core.measures.Size value="5.2" unit="mm"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Spacing Y</string>
|
||||
<org.diylc.core.measures.Size value="3.7" unit="mm"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Label</string>
|
||||
<awt-color hex="ffffff"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Orientation</string>
|
||||
<org.diylc.common.Orientation>DEFAULT</org.diylc.common.Orientation>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Case</string>
|
||||
<org.diylc.core.measures.Size value="5.0" unit="mm"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Name</string>
|
||||
<string>SW2</string>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Font Size Override</string>
|
||||
<null/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Value</string>
|
||||
<string></string>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Alpha</string>
|
||||
<byte>127</byte>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Border</string>
|
||||
<awt-color hex="595959"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Body</string>
|
||||
<awt-color hex="c0c0c0"/>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Display</string>
|
||||
<org.diylc.common.Display>BOTH</org.diylc.common.Display>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Pin Pairing Mode</string>
|
||||
<diylc.electromechanical.TactileMicroSwitch_-PinPairingMode>Diagonal</diylc.electromechanical.TactileMicroSwitch_-PinPairingMode>
|
||||
</entry>
|
||||
</values>
|
||||
<points>
|
||||
<point x="0.0" y="0.0"/>
|
||||
<point x="0.0" y="0.145"/>
|
||||
<point x="0.2" y="0.0"/>
|
||||
<point x="0.2" y="0.145"/>
|
||||
</points>
|
||||
<defaultFlag>false</defaultFlag>
|
||||
<createdOn>2026-01-02T10:04:33.795741</createdOn>
|
||||
</org.diylc.core.Template>
|
||||
</list>
|
||||
</entry>
|
||||
</map>
|
||||
|
||||
@@ -1,249 +1,5 @@
|
||||
<java.util.Arrays_-ArrayList>
|
||||
<a class="org.diylc.appframework.update.Version-array">
|
||||
<org.diylc.appframework.update.Version>
|
||||
<versionNumber>
|
||||
<major>4</major>
|
||||
<minor>41</minor>
|
||||
<build>1</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2025-03-08 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Do not mirror bottom layers for Gerber export as fabrication service providers expect a top view for everything</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Selecting components by drawing a rectangle around them works glitchy</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
</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>0</minor>
|
||||
<build>0</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2025-03-09 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Switched to AppImage distribution for Linux system, abandoning Flatpak as a means of distribution. DIYLC AppImage will be available alongside with other executables on the official DIYLC GitHub page.</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Upgraded Windows installer version and Windows EXE launcher version</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Fixed alignment issues of round elements (e.g. solder pads and holes) in exported Gerber files</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Fixed the issue with opening DIY files on Mac OS</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
</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>1</minor>
|
||||
<build>0</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2025-03-24 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added ability to render the underside of boards along with the top side for easier validation, building and debugging</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Configurable 'High DPI Rendering' feature which makes the diagram smoother on high-resolution displays but can affect performance</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Significantly improved rendering performance</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Increased initial memory allocation to 1GB for smoother performance</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Upgraded source code structure to modern standards and introduced Maven</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Improved robustness of series/parallel graph construction code used in circuit analysis</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Omit boards, wires, traces, terminals, etc from Spice netlist output</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Updated the windows installer (hopefully) to avoid false positive anti-virus detection</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Moving selected components with a mouse doesn't register file modification action and doesn't enable Undo action</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Rotating selected components sometimes wacks the alignment off the grid</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Ctrl+X (Command+X) shortcut for Cut action doesn't work because 'Project Explorer' steals the focus on X key press</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
</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>1</minor>
|
||||
<build>1</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2025-03-27 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Include windows ZIP package</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Windows version doesn't start in some cases</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
</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>2</minor>
|
||||
<build>0</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2025-04-12 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added 'AI Assistant' that can help with general electronics questions, questions about DIYLC usage and circuit-related questions</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>'Virtual Multimeter' from 'Analyze' menu traces the circuit finding all possible paths of electricity between two points designated by the probe symbol</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added ability to display switch lug markings in order to illustrate how it works internally</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added a 'Save' button to all analysis screens which saves results of a circuit analysis to an HTML file</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added ability control if the board underside display should render as transparent or not</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added DIY Cloud password reset page</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Moved 'Analyze' functions from 'File' menu to their own menu for faster access</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Moved 'Highlight Connected Areas' feature from 'Config' menu to 'Analyze' menu</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Fully configurable colors for 'Mini Toggle Switch' components</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Removed 'DP3T' options from 'Mini Toggle Switch' because that is covered by 'Slide Switch' component and does not belong here</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Include components in the underside display that have at least one control point laying in the board, as opposed to all</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Applying a variant to a 'Hookup Wire' or 'Curved Trace' makes it lose its shape</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Copy operation doesn't work when certain component types are selected</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Board underside display does not work if there's a Label or PCB Text component on the board</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Using board underside display sometimes makes other components semi-transparent</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Fixed Windows ZIP package issue with the bundled JRE</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
</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>3</minor>
|
||||
<build>0</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2025-04-21 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Improved AI Assistant quality of responses, especially when analyzing a circuit</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>When saving a project to the new file, retain all the AI Assistant chat history for the new project file</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Added ability to be logged into the same DIYLC Cloud account from several machines at the same time</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Confirmation dialog before deleting AI Assistant chat history</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Output current subscription statistics at the end of the chat for usage transparency</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Improved styling and usability of chatbot tool</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Language selection doesn't work on some OSs</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>'Connect to Patreon' option doesn't work if the user name contains a space or another non-alphanumerical character</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
</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>
|
||||
@@ -422,17 +178,209 @@
|
||||
<minor>8</minor>
|
||||
<build>0</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2025-12-21 00:00:00.000 CET</releaseDate>
|
||||
<releaseDate>2025-12-30 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added Silkscreen export to PDF/PNG and print capabilities</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added 'Multiwatt IC' package with 11 and 15 pin varieties</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added new property in 'LP Switch' that allows having the middle terminals connected (as they were) or disconnected to allow more flexibility</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added 'Ending Style' property to 'Copper Trace' and 'Curved Trace' to allow choosing round or flat style</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added Chinese simplified (thank you chenjingyuanku) and Hindi UI translation and updated all other languages with new translations</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added configuration for text scale factor in rare cases when system scale factor is not applied correctly; requires adding a configuration property to config.xml with key 'fontScaleFactor' and a desired float value</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Improved behavior of mouse cursor during drag operations; this should also fix behavior on some linux machines that show a "forbidden" cursor while dragging</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Improved sensitivity of 'Multimeter Probe' that required pixel-level precision to detect connectivity; improved rendering of the probe in outline mode</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Improve contrast of tooltips on some Linux machines that show white text against yellow balloon</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Honor label color settings for 'Tape Measure' component</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Reorganized export and print related items in the File menu to reduce clutter</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Fixed underside rendering and mirroring position issues that affected only SPDT 'Mini Toggle Switch' types</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Application freezes when using underside rendering or mirroring on 'Pin Header' when sized at 1x1</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>'Rotary Switch (Open)' cannot be selected by clicking on the wafer for 4-pole variations</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
</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>2026-01-10 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added ability to name groups of components</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Show groups of components as single items in the Project Explorer list</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<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>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added 'Freeway 3x3-05 (Ultra) Switch' (thank you mm-rnd)</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Configurable 'Tactile Micro Switch' dimensions and added a few most commonly used variants</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Exposed the most common pin pairing modes and added switching logic for 'Tactile Micro Switch' to allow using it for analysis</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>'Potentiometer Panel' can now render numerical lug markers for easier identification</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>'Auto-Wrap Label' retains line breaks from the input text</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>'Cliff 1/8" Jack', 'Open 1/8" Jack' and 'Open 1/4" Jack' can be rotated</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Fixed the offset of terminals on a DP4T Tele type of 'Lever Switch' to be aligned with the physical switch (attention - this will break existing diagrams and will require adjusting leads to match the new terminal positions)</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Make the label of schematic symbols (e.g. 'Resistor Symbol') clickable as well</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Ability to resize 'Transformer Core' schematic symbol after it's already placed</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Clicking on 'Tactile Micro Switch' body doesn't register</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>New lines at the end of the 'Label' text get erroneously rendered as br HTML tags</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
</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>10</minor>
|
||||
<build>0</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2026-01-20 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added 'M' and 'N' potentiometer taper options (thank you mm-rnd)</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added E-C-B BJT pinout to TO-92 transistor package</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Fixed the performance issue that happens when groups of components are copy-pasted and the project size grows uncontrollably</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>All building blocks of the same type get added to the same group</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>'Tactile Micro Switch' editor doesn't show up on double click</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
</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>11</minor>
|
||||
<build>0</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2026-01-20 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added ability to export chat history from 'AI Assistant' to a HTML file</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Upgraded AI models for AI-powered tools and improved overall quality of the responses</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Shape components shouldn't appear in the BOM</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Improved rendering of 'Switch (Latching)' symbol to clearly make visual contact between the terminals</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Consolidate a few keyboard shortcuts for consistency</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Using "Duplicate Selection" results in duplicates of component ids, breaking Project Explorer panel and causing other issues downstream</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Calculated length disappeared from Hookup Wire and similar curved components</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Resize dimensions do not show for 'Chassis Panel' component when dragging a corner point</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
</changes>
|
||||
<url>https://github.com/bancika/diy-layout-creator/releases</url>
|
||||
</org.diylc.appframework.update.Version>
|
||||
|
||||
@@ -4773,5 +4773,249 @@
|
||||
</changes>
|
||||
<url>https://github.com/bancika/diy-layout-creator/releases</url>
|
||||
</org.diylc.appframework.update.Version>
|
||||
<org.diylc.appframework.update.Version>
|
||||
<versionNumber>
|
||||
<major>4</major>
|
||||
<minor>41</minor>
|
||||
<build>1</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2025-03-08 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Do not mirror bottom layers for Gerber export as fabrication service providers expect a top view for everything</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Selecting components by drawing a rectangle around them works glitchy</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
</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>0</minor>
|
||||
<build>0</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2025-03-09 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Switched to AppImage distribution for Linux system, abandoning Flatpak as a means of distribution. DIYLC AppImage will be available alongside with other executables on the official DIYLC GitHub page.</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Upgraded Windows installer version and Windows EXE launcher version</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Fixed alignment issues of round elements (e.g. solder pads and holes) in exported Gerber files</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Fixed the issue with opening DIY files on Mac OS</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
</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>1</minor>
|
||||
<build>0</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2025-03-24 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added ability to render the underside of boards along with the top side for easier validation, building and debugging</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Configurable 'High DPI Rendering' feature which makes the diagram smoother on high-resolution displays but can affect performance</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Significantly improved rendering performance</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Increased initial memory allocation to 1GB for smoother performance</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Upgraded source code structure to modern standards and introduced Maven</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Improved robustness of series/parallel graph construction code used in circuit analysis</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Omit boards, wires, traces, terminals, etc from Spice netlist output</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Updated the windows installer (hopefully) to avoid false positive anti-virus detection</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Moving selected components with a mouse doesn't register file modification action and doesn't enable Undo action</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Rotating selected components sometimes wacks the alignment off the grid</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Ctrl+X (Command+X) shortcut for Cut action doesn't work because 'Project Explorer' steals the focus on X key press</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
</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>1</minor>
|
||||
<build>1</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2025-03-27 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Include windows ZIP package</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Windows version doesn't start in some cases</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
</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>2</minor>
|
||||
<build>0</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2025-04-12 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added 'AI Assistant' that can help with general electronics questions, questions about DIYLC usage and circuit-related questions</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>'Virtual Multimeter' from 'Analyze' menu traces the circuit finding all possible paths of electricity between two points designated by the probe symbol</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added ability to display switch lug markings in order to illustrate how it works internally</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added a 'Save' button to all analysis screens which saves results of a circuit analysis to an HTML file</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added ability control if the board underside display should render as transparent or not</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added DIY Cloud password reset page</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Moved 'Analyze' functions from 'File' menu to their own menu for faster access</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Moved 'Highlight Connected Areas' feature from 'Config' menu to 'Analyze' menu</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Fully configurable colors for 'Mini Toggle Switch' components</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Removed 'DP3T' options from 'Mini Toggle Switch' because that is covered by 'Slide Switch' component and does not belong here</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Include components in the underside display that have at least one control point laying in the board, as opposed to all</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Applying a variant to a 'Hookup Wire' or 'Curved Trace' makes it lose its shape</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Copy operation doesn't work when certain component types are selected</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Board underside display does not work if there's a Label or PCB Text component on the board</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Using board underside display sometimes makes other components semi-transparent</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Fixed Windows ZIP package issue with the bundled JRE</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
</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>3</minor>
|
||||
<build>0</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2025-04-21 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Improved AI Assistant quality of responses, especially when analyzing a circuit</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>When saving a project to the new file, retain all the AI Assistant chat history for the new project file</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Added ability to be logged into the same DIYLC Cloud account from several machines at the same time</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Confirmation dialog before deleting AI Assistant chat history</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Output current subscription statistics at the end of the chat for usage transparency</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Improved styling and usability of chatbot tool</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Language selection doesn't work on some OSs</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>'Connect to Patreon' option doesn't work if the user name contains a space or another non-alphanumerical character</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>
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.diylc.presenter;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -142,4 +143,116 @@ public class CalcUtilsTest {
|
||||
point2.setLocation(10.0, 21.0);
|
||||
assertFalse(CalcUtils.pointsMatch(point1, point2, 0.5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCalculateLuminance() {
|
||||
// Test pure black
|
||||
assertEquals(0.0, CalcUtils.calculateLuminance(Color.BLACK), 0.001);
|
||||
|
||||
// Test pure white
|
||||
assertEquals(255.0, CalcUtils.calculateLuminance(Color.WHITE), 0.001);
|
||||
|
||||
// Test pure red
|
||||
double redLuminance = CalcUtils.calculateLuminance(Color.RED);
|
||||
assertEquals(76.245, redLuminance, 0.001); // 0.299 * 255
|
||||
|
||||
// Test pure green
|
||||
double greenLuminance = CalcUtils.calculateLuminance(Color.GREEN);
|
||||
assertEquals(149.685, greenLuminance, 0.001); // 0.587 * 255
|
||||
|
||||
// Test pure blue
|
||||
double blueLuminance = CalcUtils.calculateLuminance(Color.BLUE);
|
||||
assertEquals(29.07, blueLuminance, 0.001); // 0.114 * 255
|
||||
|
||||
// Test middle gray (128, 128, 128)
|
||||
Color gray = new Color(128, 128, 128);
|
||||
double grayLuminance = CalcUtils.calculateLuminance(gray);
|
||||
assertEquals(128.0, grayLuminance, 0.001);
|
||||
|
||||
// Test null color
|
||||
assertEquals(0.0, CalcUtils.calculateLuminance(null), 0.001);
|
||||
|
||||
// Test custom color
|
||||
Color custom = new Color(100, 150, 200);
|
||||
double customLuminance = CalcUtils.calculateLuminance(custom);
|
||||
double expected = 0.299 * 100 + 0.587 * 150 + 0.114 * 200;
|
||||
assertEquals(expected, customLuminance, 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertToMonochrome() {
|
||||
// Test pure black -> should stay black
|
||||
Color result = CalcUtils.convertToMonochrome(Color.BLACK);
|
||||
assertEquals(Color.BLACK, result);
|
||||
|
||||
// Test pure white -> should stay white
|
||||
result = CalcUtils.convertToMonochrome(Color.WHITE);
|
||||
assertEquals(Color.WHITE, result);
|
||||
|
||||
// Test dark color -> should become black
|
||||
Color darkBlue = new Color(0, 0, 50);
|
||||
result = CalcUtils.convertToMonochrome(darkBlue);
|
||||
assertEquals(Color.BLACK, result);
|
||||
|
||||
// Test light color -> should become white
|
||||
Color lightYellow = new Color(255, 255, 200);
|
||||
result = CalcUtils.convertToMonochrome(lightYellow);
|
||||
assertEquals(Color.WHITE, result);
|
||||
|
||||
// Test middle gray -> should become white (threshold is 128, gray is exactly 128)
|
||||
Color gray = new Color(128, 128, 128);
|
||||
result = CalcUtils.convertToMonochrome(gray);
|
||||
assertEquals(Color.WHITE, result);
|
||||
|
||||
// Test color just below threshold -> should become black
|
||||
Color darkGray = new Color(127, 127, 127);
|
||||
result = CalcUtils.convertToMonochrome(darkGray);
|
||||
assertEquals(Color.BLACK, result);
|
||||
|
||||
// Test color just above threshold -> should become white
|
||||
Color lightGray = new Color(129, 129, 129);
|
||||
result = CalcUtils.convertToMonochrome(lightGray);
|
||||
assertEquals(Color.WHITE, result);
|
||||
|
||||
// Test with custom threshold
|
||||
Color mediumColor = new Color(100, 100, 100);
|
||||
result = CalcUtils.convertToMonochrome(mediumColor, 50.0);
|
||||
assertEquals(Color.WHITE, result); // Luminance ~100, threshold 50, so white
|
||||
|
||||
result = CalcUtils.convertToMonochrome(mediumColor, 150.0);
|
||||
assertEquals(Color.BLACK, result); // Luminance ~100, threshold 150, so black
|
||||
|
||||
// Test null color
|
||||
assertNull(CalcUtils.convertToMonochrome(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertToMonochromeWithAlpha() {
|
||||
// Test color with alpha channel - alpha should be preserved
|
||||
// Red (255, 0, 0) has luminance ~76, which is < 128, so it becomes BLACK
|
||||
Color transparentRed = new Color(255, 0, 0, 128);
|
||||
Color result = CalcUtils.convertToMonochrome(transparentRed);
|
||||
assertEquals(Color.BLACK.getRed(), result.getRed());
|
||||
assertEquals(Color.BLACK.getGreen(), result.getGreen());
|
||||
assertEquals(Color.BLACK.getBlue(), result.getBlue());
|
||||
assertEquals(128, result.getAlpha());
|
||||
|
||||
// Test light color with alpha - should become WHITE
|
||||
Color transparentLight = new Color(255, 255, 200, 128);
|
||||
result = CalcUtils.convertToMonochrome(transparentLight);
|
||||
assertEquals(Color.WHITE.getRed(), result.getRed());
|
||||
assertEquals(Color.WHITE.getGreen(), result.getGreen());
|
||||
assertEquals(Color.WHITE.getBlue(), result.getBlue());
|
||||
assertEquals(128, result.getAlpha());
|
||||
|
||||
// Test fully transparent color
|
||||
Color transparent = new Color(100, 150, 200, 0);
|
||||
result = CalcUtils.convertToMonochrome(transparent);
|
||||
assertEquals(0, result.getAlpha());
|
||||
|
||||
// Test opaque color - alpha should remain 255
|
||||
Color opaque = new Color(50, 50, 50);
|
||||
result = CalcUtils.convertToMonochrome(opaque);
|
||||
assertEquals(255, result.getAlpha());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.diylc</groupId>
|
||||
<artifactId>diylc-parent</artifactId>
|
||||
<version>5.7.0</version>
|
||||
<version>5.10.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>diylc-library</artifactId>
|
||||
|
||||
@@ -31,7 +31,6 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.diylc.common.HorizontalAlignment;
|
||||
import org.diylc.common.VerticalAlignment;
|
||||
@@ -72,13 +71,18 @@ public class StringUtils {
|
||||
* @return a non-empty list of strings
|
||||
*/
|
||||
public static List<String> wrap(String str, FontMetrics fm, int maxWidth) {
|
||||
String[] split = str.split("\\<br\\>");
|
||||
List<String> lines = Arrays.stream(split).flatMap(s -> splitIntoLines(s).stream()).collect(Collectors.toList());
|
||||
if (lines.size() == 0)
|
||||
return lines;
|
||||
// First split by actual newlines to honor them, then split by <br> tags
|
||||
List<String> lines = splitIntoLines(str);
|
||||
List<String> allLines = new ArrayList<String>();
|
||||
for (String line : lines) {
|
||||
String[] split = line.split("\\<br\\>");
|
||||
allLines.addAll(Arrays.asList(split));
|
||||
}
|
||||
if (allLines.size() == 0)
|
||||
return allLines;
|
||||
|
||||
ArrayList<String> strings = new ArrayList<String>();
|
||||
for (Iterator<String> iter = lines.iterator(); iter.hasNext();)
|
||||
for (Iterator<String> iter = allLines.iterator(); iter.hasNext();)
|
||||
wrapLineInto((String) iter.next(), strings, fm, maxWidth);
|
||||
return strings;
|
||||
}
|
||||
@@ -126,8 +130,8 @@ public class StringUtils {
|
||||
line = line.substring(pos).trim();
|
||||
len = line.length();
|
||||
}
|
||||
if (len > 0)
|
||||
list.add(line);
|
||||
// Always add the line, even if empty, to preserve empty lines from newlines
|
||||
list.add(line);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,12 +213,24 @@ public class StringUtils {
|
||||
if (text == null)
|
||||
return;
|
||||
|
||||
String[] parts = text.split("\\<br\\>");
|
||||
// Use -1 limit to preserve trailing empty strings so we can filter them out
|
||||
String[] parts = text.split("\\<br\\>", -1);
|
||||
if (parts.length > 1) {
|
||||
// Remove trailing empty strings to avoid rendering extra blank lines
|
||||
int lastNonEmpty = parts.length - 1;
|
||||
while (lastNonEmpty >= 0 && parts[lastNonEmpty].trim().isEmpty()) {
|
||||
lastNonEmpty--;
|
||||
}
|
||||
// If all parts are empty, don't render anything
|
||||
if (lastNonEmpty < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
FontMetrics fontMetrics = g2d.getFontMetrics();
|
||||
Rectangle stringBounds = fontMetrics.getStringBounds(parts[0], g2d).getBounds();
|
||||
for (int i = 0; i < parts.length; i++)
|
||||
drawCenteredText(g2d, parts[i], x, (int)(y - stringBounds.height * (parts.length - 1) / 2d + i * stringBounds.height), horizontalAlignment, verticalAlignment);
|
||||
int actualLength = lastNonEmpty + 1;
|
||||
for (int i = 0; i < actualLength; i++)
|
||||
drawCenteredText(g2d, parts[i], x, (int)(y - stringBounds.height * (actualLength - 1) / 2d + i * stringBounds.height), horizontalAlignment, verticalAlignment);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+36
-11
@@ -43,12 +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.components.AbstractLabeledComponent;
|
||||
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;
|
||||
@@ -63,7 +58,8 @@ import org.diylc.utils.Constants;
|
||||
*
|
||||
* @author Branislav Stojkovic
|
||||
*/
|
||||
public abstract class AbstractLeadedComponent<T> extends AbstractLabeledComponent<T> {
|
||||
public abstract class AbstractLeadedComponent<T> extends AbstractLabeledComponent<T> implements
|
||||
IHaveLength {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -367,8 +363,8 @@ public abstract class AbstractLeadedComponent<T> extends AbstractLabeledComponen
|
||||
offset = -offset;
|
||||
}
|
||||
|
||||
drawingObserver.startTracking();
|
||||
if (getMoveLabel()) {
|
||||
drawingObserver.startTracking();
|
||||
g2d.setTransform(oldTransform);
|
||||
g2d.translate(getPoints()[2].getX(), getPoints()[2].getY());
|
||||
if (getLabelOriantation() != LabelOriantation.Horizontal) {
|
||||
@@ -378,19 +374,18 @@ public abstract class AbstractLeadedComponent<T> extends AbstractLabeledComponen
|
||||
g2d.setTransform(oldTransform);
|
||||
} else {
|
||||
if (isStanding() || getLabelOriantation() == LabelOriantation.Horizontal) {
|
||||
drawingObserver.startTracking();
|
||||
g2d.setTransform(oldTransform);
|
||||
double x = (getPoints()[0].getX() + getPoints()[1].getX() - length) / 2.0;
|
||||
double y = (getPoints()[0].getY() + getPoints()[1].getY() - width) / 2.0;
|
||||
g2d.drawString(label, (int) (x + (length - textRect.getWidth()) / 2 + offset),
|
||||
(int) (y + calculateLabelYOffset(shapeRect, textRect, fontMetrics)));
|
||||
} else {
|
||||
drawingObserver.stopTracking();
|
||||
g2d.drawString(label, (int) (length - textRect.getWidth()) / 2 + offset,
|
||||
calculateLabelYOffset(shapeRect, textRect, fontMetrics));
|
||||
g2d.setTransform(oldTransform);
|
||||
}
|
||||
}
|
||||
drawingObserver.stopTracking();
|
||||
}
|
||||
// if (getLabelOriantation() == LabelOriantation.Horizontal) {
|
||||
//
|
||||
@@ -434,7 +429,7 @@ public abstract class AbstractLeadedComponent<T> extends AbstractLabeledComponen
|
||||
Stroke stroke = null;
|
||||
switch (getStyle()) {
|
||||
case SOLID:
|
||||
stroke = ObjectCache.getInstance().fetchBasicStroke(thickness);
|
||||
stroke = ObjectCache.getInstance().fetchStroke(thickness, getEndingStyle() == LineEndingStyle.Round ? BasicStroke.CAP_ROUND : BasicStroke.CAP_BUTT);
|
||||
break;
|
||||
case DASHED:
|
||||
stroke =
|
||||
@@ -641,6 +636,32 @@ public abstract class AbstractLeadedComponent<T> 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;
|
||||
@@ -747,6 +768,10 @@ public abstract class AbstractLeadedComponent<T> extends AbstractLabeledComponen
|
||||
hideShortLeads = false;
|
||||
return hideShortLeads;
|
||||
}
|
||||
|
||||
protected LineEndingStyle getEndingStyle() {
|
||||
return LineEndingStyle.Round;
|
||||
}
|
||||
|
||||
public void setHideShortLeads(Boolean hideShortLeads) {
|
||||
this.hideShortLeads = hideShortLeads;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.diylc.components;
|
||||
|
||||
public enum LineEndingStyle {
|
||||
Round, Flat
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -64,17 +64,31 @@ public class ChassisPanel extends AbstractShape {
|
||||
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f * alpha / MAX_ALPHA));
|
||||
}
|
||||
g2d.setColor(color);
|
||||
g2d.fillRoundRect((int)firstPoint.getX(), (int)firstPoint.getY(), (int)(secondPoint.getX() - firstPoint.getX()), (int)(secondPoint.getY() - firstPoint.getY()), radius,
|
||||
radius);
|
||||
if (radius == 0) {
|
||||
g2d.fillRect((int) firstPoint.getX(), (int) firstPoint.getY(),
|
||||
(int) (secondPoint.getX() - firstPoint.getX()),
|
||||
(int) (secondPoint.getY() - firstPoint.getY()));
|
||||
} else {
|
||||
g2d.fillRoundRect((int) firstPoint.getX(), (int) firstPoint.getY(),
|
||||
(int) (secondPoint.getX() - firstPoint.getX()),
|
||||
(int) (secondPoint.getY() - firstPoint.getY()), radius, radius);
|
||||
}
|
||||
g2d.setComposite(oldComposite);
|
||||
drawingObserver.stopTracking();
|
||||
}
|
||||
// Do not track any changes that follow because the whole rect has been
|
||||
// tracked so far.
|
||||
drawingObserver.stopTracking();
|
||||
g2d.setColor(componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING ? SELECTION_COLOR
|
||||
: borderColor);
|
||||
g2d.drawRoundRect((int)(firstPoint.getX()), (int)(firstPoint.getY()), (int)(secondPoint.getX() - firstPoint.getX()), (int)(secondPoint.getY() - firstPoint.getY()), radius,
|
||||
radius);
|
||||
if (radius == 0) {
|
||||
g2d.drawRect((int) (firstPoint.getX()), (int) (firstPoint.getY()),
|
||||
(int) (secondPoint.getX() - firstPoint.getX()),
|
||||
(int) (secondPoint.getY() - firstPoint.getY()));
|
||||
} else {
|
||||
g2d.drawRoundRect((int) (firstPoint.getX()), (int) (firstPoint.getY()),
|
||||
(int) (secondPoint.getX() - firstPoint.getX()),
|
||||
(int) (secondPoint.getY() - firstPoint.getY()), radius, radius);
|
||||
}
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Radius")
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.bancika.gerberwriter.GerberFunctions;
|
||||
import org.diylc.common.ObjectCache;
|
||||
import org.diylc.common.PCBLayer;
|
||||
import org.diylc.components.AbstractLeadedComponent;
|
||||
import org.diylc.components.LineEndingStyle;
|
||||
import org.diylc.components.transform.SimpleComponentTransformer;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.CreationMethod;
|
||||
@@ -58,6 +59,7 @@ public class CopperTrace extends AbstractLeadedComponent<Void> implements ILayer
|
||||
|
||||
private Size thickness = THICKNESS;
|
||||
private PCBLayer layer = PCBLayer._1;
|
||||
private LineEndingStyle endingStyle = LineEndingStyle.Round;
|
||||
|
||||
public CopperTrace() {
|
||||
super();
|
||||
@@ -202,6 +204,19 @@ public class CopperTrace extends AbstractLeadedComponent<Void> implements ILayer
|
||||
return false;
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Ending Style")
|
||||
@Override
|
||||
public LineEndingStyle getEndingStyle() {
|
||||
if (endingStyle == null) {
|
||||
endingStyle = LineEndingStyle.Round;
|
||||
}
|
||||
return endingStyle;
|
||||
}
|
||||
|
||||
public void setEndingStyle(LineEndingStyle endingStyle) {
|
||||
this.endingStyle = endingStyle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<GerberRenderMode> getGerberRenderModes() {
|
||||
return EnumSet.of(GerberRenderMode.Normal);
|
||||
|
||||
+17
-3
@@ -17,8 +17,7 @@
|
||||
*/
|
||||
package org.diylc.components.connectivity;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.*;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
@@ -29,6 +28,8 @@ import org.diylc.common.LineStyle;
|
||||
import org.diylc.common.ObjectCache;
|
||||
import org.diylc.common.PCBLayer;
|
||||
import org.diylc.components.AbstractCurvedComponent;
|
||||
import org.diylc.components.AbstractLeadedComponent;
|
||||
import org.diylc.components.LineEndingStyle;
|
||||
import org.diylc.components.transform.SimpleComponentTransformer;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
@@ -58,6 +59,7 @@ public class CurvedTrace extends AbstractCurvedComponent<Void> implements ILayer
|
||||
|
||||
protected Size size = SIZE;
|
||||
private PCBLayer layer = PCBLayer._1;
|
||||
private LineEndingStyle endingStyle = LineEndingStyle.Round;
|
||||
|
||||
@Override
|
||||
protected Color getDefaultColor() {
|
||||
@@ -67,7 +69,7 @@ public class CurvedTrace extends AbstractCurvedComponent<Void> implements ILayer
|
||||
@Override
|
||||
protected void drawCurve(Path2D curve, Graphics2D g2d, ComponentState componentState, IDrawingObserver drawingObserver) {
|
||||
float thickness = (float) size.convertToPixels();
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(thickness));
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchStroke(thickness, getEndingStyle() == LineEndingStyle.Round ? BasicStroke.CAP_ROUND : BasicStroke.CAP_BUTT));
|
||||
Color curveColor =
|
||||
componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING ? SELECTION_COLOR
|
||||
: color;
|
||||
@@ -97,6 +99,18 @@ public class CurvedTrace extends AbstractCurvedComponent<Void> implements ILayer
|
||||
public void setLayer(PCBLayer layer) {
|
||||
this.layer = layer;
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Ending Style")
|
||||
public LineEndingStyle getEndingStyle() {
|
||||
if (endingStyle == null) {
|
||||
endingStyle = LineEndingStyle.Round;
|
||||
}
|
||||
return endingStyle;
|
||||
}
|
||||
|
||||
public void setEndingStyle(LineEndingStyle endingStyle) {
|
||||
this.endingStyle = endingStyle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLayerId() {
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ import org.diylc.utils.Constants;
|
||||
|
||||
@ComponentDescriptor(name = "Twisted Leads", author = "Branislav Stojkovic",
|
||||
category = "Connectivity", instanceNamePrefix = "W",
|
||||
description = "A pair of flexible leads twisted tighly together", zOrder = IDIYComponent.WIRING,
|
||||
description = "A pair of flexible leads twisted tightly together", zOrder = IDIYComponent.WIRING,
|
||||
flexibleZOrder = true, bomPolicy = BomPolicy.NEVER_SHOW,
|
||||
transformer = SimpleComponentTransformer.class, enableCache = true)
|
||||
public class TwistedWire extends AbstractCurvedComponent<Void> implements IContinuity {
|
||||
|
||||
+2
-1
@@ -42,6 +42,7 @@ import org.diylc.common.Orientation;
|
||||
import org.diylc.common.VerticalAlignment;
|
||||
import org.diylc.components.AbstractMultiPartComponent;
|
||||
import org.diylc.components.electromechanical.OpenJack1_4.OpenJackType;
|
||||
import org.diylc.components.transform.OpenJackTransformer;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.IDrawingObserver;
|
||||
@@ -56,7 +57,7 @@ import org.diylc.utils.Constants;
|
||||
|
||||
@ComponentDescriptor(name = "Open 1/4\" Jack", category = "Electro-Mechanical", author = "Branislav Stojkovic",
|
||||
description = "Switchcraft-style open panel mount 1/4\" phono jack, stereo and mono",
|
||||
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "J", enableCache = true)
|
||||
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "J", transformer = OpenJackTransformer.class, enableCache = true)
|
||||
public class OpenJack1_4 extends AbstractMultiPartComponent<OpenJackType> {
|
||||
|
||||
private static final float FONT_SCALE = 0.8f;
|
||||
|
||||
+2
-1
@@ -42,6 +42,7 @@ import org.diylc.common.Orientation;
|
||||
import org.diylc.common.VerticalAlignment;
|
||||
import org.diylc.components.AbstractMultiPartComponent;
|
||||
import org.diylc.components.electromechanical.OpenJack1_8.OpenJackType;
|
||||
import org.diylc.components.transform.OpenJackTransformer;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.IDrawingObserver;
|
||||
@@ -56,7 +57,7 @@ import org.diylc.utils.Constants;
|
||||
|
||||
@ComponentDescriptor(name = "Open 1/8\" Jack", category = "Electro-Mechanical", author = "Branislav Stojkovic",
|
||||
description = "Switchcraft-style open panel mount 1/8\" phono jack, stereo and mono",
|
||||
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "J", enableCache = true)
|
||||
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "J", transformer = OpenJackTransformer.class, enableCache = true)
|
||||
public class OpenJack1_8 extends AbstractMultiPartComponent<OpenJackType> {
|
||||
|
||||
private static final float FONT_SCALE = 0.6f;
|
||||
|
||||
+2
-2
@@ -374,13 +374,13 @@ public class RotarySwitchOpen extends AbstractAngledComponent<RotarySwitchOpenTy
|
||||
// Draw body
|
||||
if (componentState != ComponentState.DRAGGING) {
|
||||
g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : getColor());
|
||||
drawingObserver.startTracking();
|
||||
g2d.fill(body[0]);
|
||||
drawingObserver.stopTracking();
|
||||
}
|
||||
|
||||
g2d.setColor(finalBorderColor);
|
||||
drawingObserver.startTracking();
|
||||
g2d.draw(body[0]);
|
||||
drawingObserver.stopTracking();
|
||||
|
||||
// Draw pins
|
||||
for (int i = 1; i <= secondLevelStart; i++) {
|
||||
|
||||
+2
-2
@@ -110,8 +110,8 @@ public class SwitchLatchingSymbol
|
||||
offsetX = terminalSize;
|
||||
offsetY = 0;
|
||||
} else {
|
||||
offsetX = terminalSize * 2 / 3;
|
||||
offsetY = terminalSize / 2;
|
||||
offsetX = terminalSize / 3;
|
||||
offsetY = terminalSize / 3;
|
||||
}
|
||||
|
||||
Point2D endPoint = new Point2D.Double(
|
||||
|
||||
+133
-25
@@ -35,13 +35,10 @@ import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.common.ObjectCache;
|
||||
import org.diylc.common.Orientation;
|
||||
import org.diylc.components.AbstractLabeledComponent;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.IDrawingObserver;
|
||||
import org.diylc.core.Project;
|
||||
import org.diylc.core.Theme;
|
||||
import org.diylc.core.VisibilityPolicy;
|
||||
import org.diylc.components.guitar.LeverSwitchPositionPropertyValueSource;
|
||||
import org.diylc.core.*;
|
||||
import org.diylc.core.annotations.ComponentDescriptor;
|
||||
import org.diylc.core.annotations.DynamicEditableProperty;
|
||||
import org.diylc.core.annotations.EditableProperty;
|
||||
import org.diylc.core.annotations.KeywordPolicy;
|
||||
import org.diylc.core.gerber.IGerberComponentSimple;
|
||||
@@ -53,22 +50,21 @@ import org.diylc.utils.Constants;
|
||||
category = "Electro-Mechanical", instanceNamePrefix = "SW",
|
||||
description = "4-pin tactile momentary switch", zOrder = IDIYComponent.COMPONENT,
|
||||
keywordPolicy = KeywordPolicy.SHOW_VALUE, enableCache = true)
|
||||
public class TactileMicroSwitch extends AbstractLabeledComponent<String> implements IGerberComponentSimple {
|
||||
public class TactileMicroSwitch extends AbstractLabeledComponent<String> implements IGerberComponentSimple,
|
||||
ISwitch {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static Color BODY_COLOR = Color.lightGray;
|
||||
public static Color BORDER_COLOR = Color.gray.darker();
|
||||
public static Color PIN_COLOR = METAL_COLOR;
|
||||
public static Color PIN_BORDER_COLOR = PIN_COLOR.darker();
|
||||
public static Color INDENT_COLOR = Color.gray.darker();
|
||||
public static Color LABEL_COLOR = Color.white;
|
||||
public static Size PIN_SIZE = new Size(0.04d, SizeUnit.in);
|
||||
public static int PIN_COUNT = 4;
|
||||
public static Size CASE_SIZE = new Size(6d, SizeUnit.mm);
|
||||
public static Size INDENT_SIZE = new Size(3d, SizeUnit.mm);
|
||||
private static Size PIN_SPACING = new Size(4.5d, SizeUnit.mm);
|
||||
private static Size ROW_SPACING = new Size(6.5d, SizeUnit.mm);
|
||||
private static final Color BODY_COLOR = Color.lightGray;
|
||||
private static final Color BORDER_COLOR = Color.gray.darker();
|
||||
private static final Color PIN_COLOR = METAL_COLOR;
|
||||
private static final Color PIN_BORDER_COLOR = PIN_COLOR.darker();
|
||||
private static final Color INDENT_COLOR = Color.gray.darker();
|
||||
private static final Color LABEL_COLOR = Color.white;
|
||||
private static final Size PIN_SIZE = new Size(0.04d, SizeUnit.in);
|
||||
private static final Size CASE_SIZE = new Size(6d, SizeUnit.mm);
|
||||
private static final Size PIN_SPACING = new Size(4.5d, SizeUnit.mm);
|
||||
private static final Size ROW_SPACING = new Size(6.5d, SizeUnit.mm);
|
||||
|
||||
private String value = "";
|
||||
private Orientation orientation = Orientation.DEFAULT;
|
||||
@@ -80,6 +76,12 @@ public class TactileMicroSwitch extends AbstractLabeledComponent<String> impleme
|
||||
private Color labelColor = LABEL_COLOR;
|
||||
transient private Area[] body;
|
||||
|
||||
private Size caseSize;
|
||||
private Size pinSpacing;
|
||||
private Size rowSpacing;
|
||||
private PinPairingMode pinPairingMode;
|
||||
private int selectedPosition;
|
||||
|
||||
public TactileMicroSwitch() {
|
||||
super();
|
||||
updateControlPoints();
|
||||
@@ -146,10 +148,11 @@ public class TactileMicroSwitch extends AbstractLabeledComponent<String> impleme
|
||||
|
||||
private void updateControlPoints() {
|
||||
Point2D firstPoint = controlPoints[0];
|
||||
int PIN_COUNT = 4;
|
||||
controlPoints = new Point2D[PIN_COUNT];
|
||||
controlPoints[0] = firstPoint;
|
||||
double pinSpacing = PIN_SPACING.convertToPixels();
|
||||
double rowSpacing = ROW_SPACING.convertToPixels();
|
||||
double pinSpacing = getPinSpacing().convertToPixels();
|
||||
double rowSpacing = getRowSpacing().convertToPixels();
|
||||
// Update control points.
|
||||
double dx1;
|
||||
double dy1;
|
||||
@@ -198,8 +201,8 @@ public class TactileMicroSwitch extends AbstractLabeledComponent<String> impleme
|
||||
double y = controlPoints[0].getY();
|
||||
double width;
|
||||
double height;
|
||||
double caseSize = CASE_SIZE.convertToPixels();
|
||||
double indentSize = INDENT_SIZE.convertToPixels();
|
||||
double caseSize = getCaseSize().convertToPixels();
|
||||
double actuatorSize = caseSize / 2;
|
||||
|
||||
width = height = caseSize;
|
||||
x = (controlPoints[0].getX() + controlPoints[3].getX() - width) / 2;
|
||||
@@ -207,7 +210,7 @@ public class TactileMicroSwitch extends AbstractLabeledComponent<String> impleme
|
||||
|
||||
body[0] = new Area(new Rectangle2D.Double(x, y, width, height));
|
||||
|
||||
width = height = indentSize;
|
||||
width = height = actuatorSize;
|
||||
x = (controlPoints[0].getX() + controlPoints[3].getX() - width) / 2;
|
||||
y = (controlPoints[0].getY() + controlPoints[3].getY() - height) / 2;
|
||||
|
||||
@@ -244,7 +247,7 @@ public class TactileMicroSwitch extends AbstractLabeledComponent<String> impleme
|
||||
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f * alpha / MAX_ALPHA));
|
||||
}
|
||||
g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : getBodyColor());
|
||||
drawingObserver.stopTracking();
|
||||
drawingObserver.startTracking();
|
||||
g2d.fill(mainArea);
|
||||
drawingObserver.stopTracking();
|
||||
|
||||
@@ -391,6 +394,59 @@ public class TactileMicroSwitch extends AbstractLabeledComponent<String> impleme
|
||||
this.labelColor = labelColor;
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Case Size")
|
||||
public Size getCaseSize() {
|
||||
if (caseSize == null) {
|
||||
caseSize = CASE_SIZE;
|
||||
}
|
||||
return caseSize;
|
||||
}
|
||||
|
||||
public void setCaseSize(Size caseSize) {
|
||||
this.caseSize = caseSize;
|
||||
this.body = null;
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Pin Spacing")
|
||||
public Size getPinSpacing() {
|
||||
if (pinSpacing == null) {
|
||||
pinSpacing = PIN_SPACING;
|
||||
}
|
||||
return pinSpacing;
|
||||
}
|
||||
|
||||
public void setPinSpacing(Size pinSpacing) {
|
||||
this.pinSpacing = pinSpacing;
|
||||
this.body = null;
|
||||
updateControlPoints();
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Row Spacing")
|
||||
public Size getRowSpacing() {
|
||||
if (rowSpacing == null) {
|
||||
rowSpacing = ROW_SPACING;
|
||||
}
|
||||
return rowSpacing;
|
||||
}
|
||||
|
||||
public void setRowSpacing(Size rowSpacing) {
|
||||
this.rowSpacing = rowSpacing;
|
||||
this.body = null;
|
||||
updateControlPoints();
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Pin Pairing Mode")
|
||||
public PinPairingMode getPinPairingMode() {
|
||||
if (pinPairingMode == null) {
|
||||
pinPairingMode = PinPairingMode.Opposite;
|
||||
}
|
||||
return pinPairingMode;
|
||||
}
|
||||
|
||||
public void setPinPairingMode(PinPairingMode pinPairingMode) {
|
||||
this.pinPairingMode = pinPairingMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPointMoveFreely(int pointIndex) {
|
||||
return false;
|
||||
@@ -418,4 +474,56 @@ public class TactileMicroSwitch extends AbstractLabeledComponent<String> impleme
|
||||
return new Rectangle2D.Double(minX - margin, minY - margin, maxX - minX + 2 * margin,
|
||||
maxY - minY + 2 * margin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPositionCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPositionName(int position) {
|
||||
return position == 0 ? "Released" : "Pressed";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean arePointsConnected(int index1, int index2, int position) {
|
||||
// Only connect pins when switch is pressed (position = 1)
|
||||
if (position != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PinPairingMode mode = getPinPairingMode();
|
||||
|
||||
switch (mode) {
|
||||
case Opposite:
|
||||
// Pins on opposite sides (same row): 0-2 and 1-3
|
||||
return (index1 == 0 && index2 == 2) || (index1 == 2 && index2 == 0) ||
|
||||
(index1 == 1 && index2 == 3) || (index1 == 3 && index2 == 1);
|
||||
case Diagonal:
|
||||
// Diagonally opposite pins: 0-3 and 1-2
|
||||
return (index1 == 0 && index2 == 3) || (index1 == 3 && index2 == 0) ||
|
||||
(index1 == 1 && index2 == 2) || (index1 == 2 && index2 == 1);
|
||||
case Adjacent:
|
||||
// Pins next to each other (same column): 0-1 and 2-3
|
||||
return (index1 == 0 && index2 == 1) || (index1 == 1 && index2 == 0) ||
|
||||
(index1 == 2 && index2 == 3) || (index1 == 3 && index2 == 2);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@DynamicEditableProperty(source = TactileMicroSwitchPositionPropertyValueSource.class)
|
||||
@EditableProperty(name = "Selected Position")
|
||||
@Override
|
||||
public Integer getSelectedPosition() {
|
||||
return selectedPosition;
|
||||
}
|
||||
|
||||
public void setSelectedPosition(Integer selectedPosition) {
|
||||
this.selectedPosition = selectedPosition;
|
||||
}
|
||||
|
||||
public enum PinPairingMode {
|
||||
Opposite, Diagonal, Adjacent;
|
||||
}
|
||||
}
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
*
|
||||
* 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.components.electromechanical;
|
||||
|
||||
import org.diylc.core.AbstractDynamicPropertyValueSource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TactileMicroSwitchPositionPropertyValueSource extends AbstractDynamicPropertyValueSource {
|
||||
|
||||
@Override
|
||||
public List<Object> getAvailableValues() {
|
||||
return List.of(0, 1);
|
||||
}
|
||||
}
|
||||
@@ -59,8 +59,8 @@ import org.diylc.core.measures.SizeUnit;
|
||||
import org.diylc.utils.Constants;
|
||||
import org.diylc.utils.SwitchUtils;
|
||||
|
||||
@ComponentDescriptor(name = "Freeway 3X3-03 Toggle", category = "Guitar",
|
||||
author = "Branislav Stojkovic", description = "Freeway 3X3-03 Toggle Switch",
|
||||
@ComponentDescriptor(name = "Freeway 3X3-03 Switch", category = "Guitar",
|
||||
author = "Branislav Stojkovic", description = "Freeway 3X3-03 (Standard) Toggle Switch",
|
||||
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "SW",
|
||||
keywordPolicy = KeywordPolicy.SHOW_TAG, keywordTag = "Guitar Wiring Diagram",
|
||||
transformer = Freeway3x3_03Transformer.class, enableCache = true)
|
||||
|
||||
@@ -0,0 +1,493 @@
|
||||
/*
|
||||
*
|
||||
* 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.components.guitar;
|
||||
|
||||
import com.kitfox.svg.SVGDiagram;
|
||||
import com.kitfox.svg.SVGElement;
|
||||
import com.kitfox.svg.SVGUniverse;
|
||||
import com.kitfox.svg.ShapeElement;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.awt.StringUtils;
|
||||
import org.diylc.common.*;
|
||||
import org.diylc.components.AbstractTransparentComponent;
|
||||
import org.diylc.components.transform.Freeway3x3_03Transformer;
|
||||
import org.diylc.core.*;
|
||||
import org.diylc.core.annotations.ComponentDescriptor;
|
||||
import org.diylc.core.annotations.DynamicEditableProperty;
|
||||
import org.diylc.core.annotations.EditableProperty;
|
||||
import org.diylc.core.annotations.KeywordPolicy;
|
||||
import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
import org.diylc.utils.Constants;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.diylc.core.measures.SizeUnit.px;
|
||||
|
||||
@ComponentDescriptor(name = "Freeway 3X3-05 (Ultra) Switch", category = "Guitar",
|
||||
author = "Martin Morrell", description = "Freeway 3X3-05 (Ultra) Toggle Switch",
|
||||
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "SW",
|
||||
keywordPolicy = KeywordPolicy.SHOW_TAG, keywordTag = "Guitar Wiring Diagram",
|
||||
transformer = Freeway3x3_03Transformer.class, enableCache = false)
|
||||
public class Freeway3x3_05 extends AbstractTransparentComponent<Void> implements ISwitch {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(Freeway3x3_05.class);
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static Color BASE_COLOR = FR4_COLOR;// Color.DARK_GRAY;
|
||||
private static Color PAD_COLOR = COPPER_COLOR;
|
||||
private static Color LABEL_COLOR = Color.WHITE;
|
||||
private static Color CASE_COLOR = METAL_COLOR;
|
||||
private static Color MARKER_COLOR = Color.lightGray;
|
||||
|
||||
private static final double DIAMETER_SWITCH = 34.0;
|
||||
private static final double PAD_RADIUS = 15.3;
|
||||
private static final double PAD_SIZE = 1.5;
|
||||
private static final int NUM_CONTACTS = 28;
|
||||
private static final double CONTACT_SPACING_RAD = Math.PI * (360.0 / NUM_CONTACTS) / 180.0;
|
||||
private static final double CONTACT_SPACING_INITIAL = (CONTACT_SPACING_RAD * 14) + CONTACT_SPACING_RAD / 2;
|
||||
private static final double GROUND_DIAMETER = 20;
|
||||
private static final double CENTRE_LEN = 17;
|
||||
private static final double CENTRE_WID = 10;
|
||||
private static final double MARKER_OFFSET = 3.1;
|
||||
|
||||
private static final String[] PAD_NAMES = new String[] {
|
||||
"1B", "2B", "3B", "6B", "5B", "4B", "CB", "TB", "6B", "5B", "4B", "1B", "2B", "3B",
|
||||
"6A", "5A", "4A", "1A", "2A", "3A", "TA", "CA", "1A", "2A", "3A", "6A", "5A", "4A"
|
||||
};
|
||||
|
||||
private static final AffineTransform SCALE_TX = AffineTransform.getScaleInstance(0.615d, 0.615d);
|
||||
|
||||
private Point2D[] controlPoints = new Point2D[] {new Point2D.Double(0, 0)};
|
||||
|
||||
private Orientation orientation = Orientation.DEFAULT;
|
||||
|
||||
private Integer selectedPosition;
|
||||
private Boolean showMarkers;
|
||||
|
||||
private transient SVGDiagram svgDiagram;
|
||||
private transient List<Shape> pads;
|
||||
private transient List<Shape> labels;
|
||||
private transient Shape base;
|
||||
private transient Shape caseShape;
|
||||
private transient double[] xOffsetsPx;
|
||||
private transient double[] yOffsetsPx;
|
||||
|
||||
public Freeway3x3_05() {
|
||||
super();
|
||||
updateControlPoints();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getControlPointNodeName(int index) {
|
||||
return PAD_NAMES[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode,
|
||||
Project project, IDrawingObserver drawingObserver) {
|
||||
|
||||
double x = controlPoints[0].getX();
|
||||
double y = controlPoints[0].getY();
|
||||
|
||||
if (getOrientation() != Orientation.DEFAULT) {
|
||||
double theta = orientation.toRadians();
|
||||
g2d.rotate(theta, x, y);
|
||||
}
|
||||
|
||||
Composite oldComposite = g2d.getComposite();
|
||||
if (alpha < MAX_ALPHA) {
|
||||
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f * alpha / MAX_ALPHA));
|
||||
}
|
||||
|
||||
double offsetX = getXOffsetsPx()[0];
|
||||
double offsetY = getYOffsetsPx()[0];
|
||||
//double origin = DIAMETER_SWITCH / 2;
|
||||
|
||||
int diameter = (int)(new Size(DIAMETER_SWITCH, SizeUnit.mm)).convertToPixels();
|
||||
|
||||
double originX = x - offsetX + diameter / 2.0;
|
||||
double originY = y - offsetY + diameter / 2.0;
|
||||
|
||||
//Main circular shape
|
||||
Color finalBorderColor;
|
||||
if (outlineMode) {
|
||||
Theme theme = (Theme) ConfigurationManager.getInstance().readObject(IPlugInPort.THEME_KEY,
|
||||
Constants.DEFAULT_THEME);
|
||||
finalBorderColor =
|
||||
componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING
|
||||
? SELECTION_COLOR
|
||||
: theme.getOutlineColor();
|
||||
} else {
|
||||
finalBorderColor =
|
||||
componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING
|
||||
? SELECTION_COLOR
|
||||
: BASE_COLOR.darker();
|
||||
}
|
||||
Shape base = new Ellipse2D.Double( originX, originY, diameter, diameter);
|
||||
drawingObserver.startTracking();
|
||||
g2d.setColor(BASE_COLOR);
|
||||
g2d.fill(base);
|
||||
drawingObserver.stopTracking();
|
||||
g2d.setStroke(new BasicStroke(1f));
|
||||
g2d.setColor(finalBorderColor);
|
||||
g2d.draw(base);
|
||||
|
||||
//Ground circular shape
|
||||
int gndDiameter = (int)(new Size(GROUND_DIAMETER, SizeUnit.mm)).convertToPixels();
|
||||
int gndShift = (int)(new Size( (DIAMETER_SWITCH - GROUND_DIAMETER) / 2, SizeUnit.mm).convertToPixels());
|
||||
Shape gnd = new Ellipse2D.Double( originX + gndShift, originY + gndShift, gndDiameter, gndDiameter);
|
||||
g2d.setPaint(PAD_COLOR);
|
||||
g2d.fill(gnd);
|
||||
g2d.setColor(PAD_COLOR.darker());
|
||||
g2d.draw(gnd);
|
||||
|
||||
//Centre rectangle casing block
|
||||
int rectLen = (int)(new Size(CENTRE_LEN, SizeUnit.mm)).convertToPixels();
|
||||
int rectWid = (int)(new Size(CENTRE_WID, SizeUnit.mm)).convertToPixels();
|
||||
|
||||
int rectShiftX = (int)(new Size( (DIAMETER_SWITCH - CENTRE_WID) / 2, SizeUnit.mm).convertToPixels());
|
||||
int rectShiftY = (int)(new Size( (DIAMETER_SWITCH - CENTRE_LEN) / 2, SizeUnit.mm).convertToPixels());
|
||||
|
||||
int rectangleArc = (int)(new Size(1.0, SizeUnit.mm)).convertToPixels();
|
||||
|
||||
Shape casing = new RoundRectangle2D.Double(originX + rectShiftX, originY + rectShiftY, rectWid, rectLen, rectangleArc, rectangleArc);
|
||||
|
||||
g2d.setPaint(CASE_COLOR);
|
||||
g2d.fill(casing);
|
||||
g2d.setColor(CASE_COLOR.darker());
|
||||
g2d.draw(casing);
|
||||
|
||||
//Pads
|
||||
double circleShift = (int)(new Size( DIAMETER_SWITCH / 2, SizeUnit.mm).convertToPixels());
|
||||
double circleCentreX = originX + circleShift;
|
||||
double circleCentreY = originY + circleShift;
|
||||
double radChange = CONTACT_SPACING_RAD * 0.45;
|
||||
double extendedRadius = (int)new Size(PAD_RADIUS + PAD_SIZE, SizeUnit.mm).convertToPixels();
|
||||
double reducedRadius = (int)new Size(PAD_RADIUS - PAD_SIZE, SizeUnit.mm).convertToPixels();
|
||||
for (int i = 0; i < NUM_CONTACTS; i++) {
|
||||
double rad = ((i + 0.5) * CONTACT_SPACING_RAD);
|
||||
|
||||
double rotExX = Math.cos(rad) * (extendedRadius * 1.01) + circleCentreX;
|
||||
double rotExY = Math.sin(rad) * (extendedRadius * 1.01) + circleCentreY;
|
||||
double rotExPosX = Math.cos(rad + radChange) * extendedRadius + circleCentreX;
|
||||
double rotExPosY = Math.sin(rad + radChange) * extendedRadius + circleCentreY;
|
||||
double rotExNegX = Math.cos(rad - radChange) * extendedRadius + circleCentreX;
|
||||
double rotExNegY = Math.sin(rad - radChange) * extendedRadius + circleCentreY;
|
||||
double rotReX = Math.cos(rad) * (reducedRadius * 1.01) + circleCentreX;
|
||||
double rotReY = Math.sin(rad) * (reducedRadius * 1.01) + circleCentreY;
|
||||
double rotRePosX = Math.cos(rad + radChange) * reducedRadius + circleCentreX;
|
||||
double rotRePosY = Math.sin(rad + radChange) * reducedRadius + circleCentreY;
|
||||
double rotReNegX = Math.cos(rad - radChange) * reducedRadius + circleCentreX;
|
||||
double rotReNegY = Math.sin(rad - radChange) * reducedRadius + circleCentreY;
|
||||
|
||||
Path2D pad = new Path2D.Double();
|
||||
pad.moveTo(rotExNegX, rotExNegY);
|
||||
pad.quadTo(rotExX, rotExY, rotExPosX, rotExPosY);
|
||||
pad.lineTo(rotRePosX, rotRePosY);
|
||||
pad.quadTo(rotReX, rotReY,rotReNegX, rotReNegY);
|
||||
pad.lineTo(rotExNegX, rotExNegY);
|
||||
pad.closePath();
|
||||
|
||||
g2d.setPaint(PAD_COLOR);
|
||||
g2d.fill(pad);
|
||||
g2d.setColor(PAD_COLOR.darker());
|
||||
g2d.draw(pad);
|
||||
}
|
||||
|
||||
//Labels
|
||||
double offset = (int) new Size(MARKER_OFFSET, SizeUnit.mm).convertToPixels();
|
||||
for (int i = 0; i < NUM_CONTACTS; i++) {
|
||||
Point2D p = getControlPoint(i);
|
||||
|
||||
double px = Math.sin(CONTACT_SPACING_INITIAL + i * CONTACT_SPACING_RAD) * (PAD_RADIUS - MARKER_OFFSET);
|
||||
double py = Math.cos(CONTACT_SPACING_INITIAL + i * CONTACT_SPACING_RAD) * (PAD_RADIUS - MARKER_OFFSET);
|
||||
|
||||
g2d.setPaint(LABEL_COLOR);
|
||||
StringUtils.drawCenteredText(g2d, PAD_NAMES[i],
|
||||
(new Size(px + DIAMETER_SWITCH / 2.0, SizeUnit.mm).convertToPixels()) + originX,
|
||||
(new Size(py + DIAMETER_SWITCH / 2.0, SizeUnit.mm).convertToPixels()) + originY,
|
||||
HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
|
||||
}
|
||||
|
||||
//Add other text
|
||||
int makeLen = (int)(new Size(12.0, SizeUnit.mm)).convertToPixels();
|
||||
int modelLen = (int)(new Size(8.0, SizeUnit.mm)).convertToPixels();
|
||||
double mWidth = 1.8;
|
||||
int modelWid = (int)(new Size(mWidth, SizeUnit.mm)).convertToPixels();
|
||||
|
||||
int makeShiftX = (int)(new Size( (DIAMETER_SWITCH / 2) - 7.25, SizeUnit.mm).convertToPixels());
|
||||
int makeShiftY = (int)(new Size( (DIAMETER_SWITCH / 2) - 6.0, SizeUnit.mm).convertToPixels());
|
||||
int modelShiftX = (int)(new Size( (DIAMETER_SWITCH / 2) + 5.25, SizeUnit.mm).convertToPixels());
|
||||
int modelShiftY = (int)(new Size( (DIAMETER_SWITCH / 2) - 4.0, SizeUnit.mm).convertToPixels());
|
||||
|
||||
int modelArc = (int)(new Size(mWidth, SizeUnit.mm)).convertToPixels();
|
||||
|
||||
Shape make = new RoundRectangle2D.Double(originX + makeShiftX, originY + makeShiftY, modelWid, makeLen, modelArc, modelArc);
|
||||
Shape model = new RoundRectangle2D.Double(originX + modelShiftX, originY + modelShiftY, modelWid, modelLen, modelArc, modelArc);
|
||||
|
||||
g2d.setPaint(BASE_COLOR);
|
||||
g2d.fill(make);
|
||||
g2d.fill(model);
|
||||
g2d.setColor(PAD_COLOR.darker());
|
||||
g2d.draw(make);
|
||||
g2d.draw(model);
|
||||
|
||||
/*String makeTxt = "FREE-WAY·ULTRA";
|
||||
String modelTxt = "3X3-05";
|
||||
g2d.setPaint(LABEL_COLOR);
|
||||
StringUtils.drawCenteredText(g2d, makeTxt,
|
||||
(new Size(0.0, SizeUnit.mm).convertToPixels()) + originX + makeShiftX,
|
||||
(new Size(0.0, SizeUnit.mm).convertToPixels()) + originY + makeShiftY,
|
||||
HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
|
||||
StringUtils.drawCenteredText(g2d, modelTxt,
|
||||
(new Size(0.0, SizeUnit.mm).convertToPixels()) + originX + modelShiftX,
|
||||
(new Size(0.0, SizeUnit.mm).convertToPixels()) + originY + modelShiftY,
|
||||
HorizontalAlignment.CENTER, VerticalAlignment.CENTER);*/
|
||||
|
||||
if (getShowMarkers()) {
|
||||
g2d.setColor(MARKER_COLOR);
|
||||
|
||||
for (int i = 0; i < getControlPointCount(); i++) {
|
||||
Point2D p = getControlPoint(i);
|
||||
Point2D labelPoint = new Point2D.Double(offset, 0);
|
||||
|
||||
double px = Math.sin(CONTACT_SPACING_INITIAL + i * CONTACT_SPACING_RAD) * (PAD_RADIUS + MARKER_OFFSET);
|
||||
double py = Math.cos(CONTACT_SPACING_INITIAL + i * CONTACT_SPACING_RAD) * (PAD_RADIUS + MARKER_OFFSET);
|
||||
|
||||
String marker = PAD_NAMES[i];
|
||||
if(marker.equals("CA")) {
|
||||
marker = "A";
|
||||
} else if (marker.equals("CB")) {
|
||||
marker = "B";
|
||||
} else if (marker.startsWith("T")) {
|
||||
continue;
|
||||
} else {
|
||||
marker = marker.substring(0, 1);
|
||||
}
|
||||
|
||||
StringUtils.drawCenteredText(g2d, marker,
|
||||
(new Size(px + DIAMETER_SWITCH / 2.0, SizeUnit.mm).convertToPixels()) + originX,
|
||||
(new Size(py + DIAMETER_SWITCH / 2.0, SizeUnit.mm).convertToPixels()) + originY,
|
||||
HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
|
||||
}
|
||||
}
|
||||
|
||||
g2d.setComposite(oldComposite);
|
||||
}
|
||||
|
||||
public double[] getXOffsetsPx() {
|
||||
if (xOffsetsPx == null) {
|
||||
xOffsetsPx = new double[NUM_CONTACTS];
|
||||
for (int i = 0; i < NUM_CONTACTS; i++) {
|
||||
double x = Math.sin(CONTACT_SPACING_INITIAL + i * CONTACT_SPACING_RAD) * PAD_RADIUS;
|
||||
xOffsetsPx[i] = new Size(x + DIAMETER_SWITCH, SizeUnit.mm).convertToPixels();
|
||||
}
|
||||
}
|
||||
return xOffsetsPx;
|
||||
}
|
||||
|
||||
public double[] getYOffsetsPx() {
|
||||
if (yOffsetsPx == null) {
|
||||
yOffsetsPx = new double[NUM_CONTACTS];
|
||||
for (int i = 0; i < NUM_CONTACTS; i++) {
|
||||
double y = Math.cos(CONTACT_SPACING_INITIAL + i * CONTACT_SPACING_RAD) * PAD_RADIUS;
|
||||
yOffsetsPx[i] = new Size(y + DIAMETER_SWITCH , SizeUnit.mm).convertToPixels();
|
||||
}
|
||||
}
|
||||
return yOffsetsPx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawIcon(Graphics2D g2d, int width, int height) {
|
||||
Shape base = new Ellipse2D.Double( 0, 0, width, height);
|
||||
g2d.setColor(BASE_COLOR);
|
||||
g2d.fill(base);
|
||||
|
||||
double contactOffset = 1;
|
||||
Shape contacts = new Ellipse2D.Double( contactOffset, contactOffset, width - contactOffset * 2, height - contactOffset * 2);
|
||||
g2d.setColor(PAD_COLOR);
|
||||
g2d.fill(contacts);
|
||||
|
||||
double centreOffset = 4;
|
||||
Shape centre = new Ellipse2D.Double( centreOffset, centreOffset, width - centreOffset * 2, height - centreOffset * 2);
|
||||
g2d.setColor(BASE_COLOR);
|
||||
g2d.fill(centre);
|
||||
|
||||
double gndOffset = 9;
|
||||
Shape gnd = new Ellipse2D.Double( gndOffset, gndOffset, width - gndOffset * 2, height - gndOffset * 2);
|
||||
g2d.setColor(PAD_COLOR);
|
||||
g2d.fill(gnd);
|
||||
|
||||
double arc = 2.0;
|
||||
Shape casing = new RoundRectangle2D.Double(12, 10, 8, 12, arc, arc);
|
||||
g2d.setColor(CASE_COLOR);
|
||||
g2d.fill(casing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getControlPointCount() {
|
||||
return NUM_CONTACTS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2D getControlPoint(int index) {
|
||||
return controlPoints[index];
|
||||
}
|
||||
|
||||
private void updateControlPoints() {
|
||||
Point2D firstPoint = controlPoints[0];
|
||||
controlPoints = new Point2D[NUM_CONTACTS];
|
||||
controlPoints[0] = firstPoint;
|
||||
double[] xOffsetsPx = getXOffsetsPx();
|
||||
double[] yOffsetsPx = getYOffsetsPx();
|
||||
double theta = orientation.toRadians();
|
||||
AffineTransform tx =
|
||||
AffineTransform.getRotateInstance(theta, firstPoint.getX(), firstPoint.getY());
|
||||
for (int i = 1; i < controlPoints.length; i++) {
|
||||
controlPoints[i] = new Point2D.Double(firstPoint.getX() + (xOffsetsPx[i] - xOffsetsPx[0]),
|
||||
firstPoint.getY() + (yOffsetsPx[i] - yOffsetsPx[0]));
|
||||
tx.transform(controlPoints[i], controlPoints[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isControlPointSticky(int index) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VisibilityPolicy getControlPointVisibilityPolicy(int index) {
|
||||
return VisibilityPolicy.ALWAYS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setControlPoint(Point2D point, int index) {
|
||||
controlPoints[index].setLocation(point);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void getValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(Void value) {}
|
||||
|
||||
@Override
|
||||
public boolean canPointMoveFreely(int pointIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@EditableProperty
|
||||
public Orientation getOrientation() {
|
||||
if (orientation == null)
|
||||
orientation = Orientation.DEFAULT;
|
||||
return orientation;
|
||||
}
|
||||
|
||||
public void setOrientation(Orientation orientation) {
|
||||
this.orientation = orientation;
|
||||
updateControlPoints();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPositionCount() {
|
||||
return 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPositionName(int position) {
|
||||
return Integer.toString(position + 1);
|
||||
}
|
||||
|
||||
@DynamicEditableProperty(source = Freeway3x4_03SwitchPositionPropertyValueSource.class)
|
||||
@EditableProperty(name = "Selected Position")
|
||||
@Override
|
||||
public Integer getSelectedPosition() {
|
||||
return selectedPosition;
|
||||
}
|
||||
|
||||
public void setSelectedPosition(Integer selectedPosition) {
|
||||
this.selectedPosition = selectedPosition;
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Markers")
|
||||
public Boolean getShowMarkers() {
|
||||
if (showMarkers == null) {
|
||||
showMarkers = false;
|
||||
}
|
||||
return showMarkers;
|
||||
}
|
||||
|
||||
public void setShowMarkers(Boolean showMarkers) {
|
||||
this.showMarkers = showMarkers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean arePointsConnected(int index1, int index2, int position) {
|
||||
if (position == 0 &&
|
||||
((index1 == 0 && index2 == 6) || (index1 == 6 && index2 == 11) || (index1 == 0 && index2 == 11) ||
|
||||
(index1 == 17 && index2 == 21) || (index1 == 21 && index2 == 22) || (index1 == 17 && index2 == 22))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (position == 1 &&
|
||||
((index1 == 1 && index2 == 6) || (index1 == 6 && index2 == 12) || (index1 == 1 && index2 == 12) ||
|
||||
(index1 == 18 && index2 == 21) || (index1 == 21 && index2 == 23) || (index1 == 18 && index2 == 23))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (position == 2 &&
|
||||
((index1 == 2 && index2 == 6) || (index1 == 6 && index2 == 13) || (index1 == 2 && index2 == 13) ||
|
||||
(index1 == 19 && index2 == 21) || (index1 == 21 && index2 == 24) || (index1 == 19 && index2 == 24))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (position == 3 &&
|
||||
((index1 == 5 && index2 == 6) || (index1 == 6 && index2 == 10) || (index1 == 5 && index2 == 10) ||
|
||||
(index1 == 16 && index2 == 21) || (index1 == 21 && index2 == 27) || (index1 == 16 && index2 == 27))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (position == 4 &&
|
||||
((index1 == 4 && index2 == 6) || (index1 == 6 && index2 == 9) || (index1 == 4 && index2 == 9) ||
|
||||
(index1 == 15 && index2 == 21) || (index1 == 21 && index2 == 26) || (index1 == 15 && index2 == 26))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (position == 5 &&
|
||||
((index1 == 3 && index2 == 6) || (index1 == 6 && index2 == 8) || (index1 == 3 && index2 == 8) ||
|
||||
(index1 == 14 && index2 == 21) || (index1 == 21 && index2 == 25) || (index1 == 14 && index2 == 25))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -63,15 +63,18 @@ public class LPSwitch extends AbstractTransparentComponent<String> implements IS
|
||||
private static Size LENGTH = new Size(1.3d, SizeUnit.in);
|
||||
private static Size BASE_LENGTH = new Size(18d, SizeUnit.mm);
|
||||
private static Size WAFER_THICKNESS = new Size(0.05d, SizeUnit.in);
|
||||
private static Size TERMINAL_SPACING = new Size(0.2d, SizeUnit.in);
|
||||
private static Size TERMINAL_SPACING_CONNECTED = new Size(0.2d, SizeUnit.in);
|
||||
private static Size TERMINAL_SPACING_DISCONNECTED = new Size(0.1d, SizeUnit.in);
|
||||
|
||||
private String value = "";
|
||||
private Point2D[] controlPoints = new Point2D[] {new Point2D.Double(0, 0), new Point2D.Double(0, 0), new Point2D.Double(0, 0), new Point2D.Double(0, 0)};
|
||||
transient Shape[] body;
|
||||
private Orientation orientation = Orientation.DEFAULT;
|
||||
private MiddleTerminalConfiguration middleTerminalConfiguration;
|
||||
|
||||
public LPSwitch() {
|
||||
super();
|
||||
middleTerminalConfiguration = MiddleTerminalConfiguration.Connected;
|
||||
updateControlPoints();
|
||||
}
|
||||
|
||||
@@ -127,7 +130,7 @@ public class LPSwitch extends AbstractTransparentComponent<String> implements IS
|
||||
g2d.setColor(finalBorderColor);
|
||||
g2d.draw(body[1]);
|
||||
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(2));
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(3));
|
||||
g2d.setColor(METAL_COLOR);
|
||||
drawingObserver.startTracking();
|
||||
g2d.draw(body[2]);
|
||||
@@ -178,24 +181,44 @@ public class LPSwitch extends AbstractTransparentComponent<String> implements IS
|
||||
}
|
||||
}
|
||||
|
||||
int terminalSpacing = (int) TERMINAL_SPACING.convertToPixels();
|
||||
|
||||
GeneralPath terminalPath = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
|
||||
terminalPath.moveTo(x - waferThickness * 3 / 2, bodyY + 1);
|
||||
terminalPath.lineTo(x - waferThickness * 3 / 2, bodyY + baseLength);
|
||||
terminalPath.lineTo(x, y + length);
|
||||
|
||||
terminalPath.moveTo(x + waferThickness * 3 / 2, bodyY + 1);
|
||||
terminalPath.lineTo(x + waferThickness * 3 / 2, bodyY + baseLength);
|
||||
terminalPath.lineTo(x, y + length);
|
||||
if (getMiddleTerminalConfiguration() == MiddleTerminalConfiguration.Connected) {
|
||||
int terminalSpacing = (int) TERMINAL_SPACING_CONNECTED.convertToPixels();
|
||||
terminalPath.moveTo(x - waferThickness * 3 / 2, bodyY + 1);
|
||||
terminalPath.lineTo(x - waferThickness * 3 / 2, bodyY + baseLength);
|
||||
terminalPath.lineTo(x, y + length);
|
||||
|
||||
terminalPath.moveTo(x - waferThickness * 5 / 2, bodyY + 1);
|
||||
terminalPath.lineTo(x - waferThickness * 5 / 2, bodyY + baseLength);
|
||||
terminalPath.lineTo(x - terminalSpacing, y + length);
|
||||
terminalPath.moveTo(x + waferThickness * 3 / 2, bodyY + 1);
|
||||
terminalPath.lineTo(x + waferThickness * 3 / 2, bodyY + baseLength);
|
||||
terminalPath.lineTo(x, y + length);
|
||||
|
||||
terminalPath.moveTo(x + waferThickness * 5 / 2, bodyY + 1);
|
||||
terminalPath.lineTo(x + waferThickness * 5 / 2, bodyY + baseLength);
|
||||
terminalPath.lineTo(x + terminalSpacing, y + length);
|
||||
terminalPath.moveTo(x - waferThickness * 5 / 2, bodyY + 1);
|
||||
terminalPath.lineTo(x - waferThickness * 5 / 2, bodyY + baseLength);
|
||||
terminalPath.lineTo(x - terminalSpacing, y + length);
|
||||
|
||||
terminalPath.moveTo(x + waferThickness * 5 / 2, bodyY + 1);
|
||||
terminalPath.lineTo(x + waferThickness * 5 / 2, bodyY + baseLength);
|
||||
terminalPath.lineTo(x + terminalSpacing, y + length);
|
||||
} else {
|
||||
int terminalSpacing = (int) TERMINAL_SPACING_DISCONNECTED.convertToPixels();
|
||||
terminalPath.moveTo(x - waferThickness * 3 / 2, bodyY + 1);
|
||||
terminalPath.lineTo(x - waferThickness * 3 / 2, bodyY + baseLength);
|
||||
terminalPath.lineTo(x- terminalSpacing, y + length);
|
||||
|
||||
terminalPath.moveTo(x + waferThickness * 3 / 2, bodyY + 1);
|
||||
terminalPath.lineTo(x + waferThickness * 3 / 2, bodyY + baseLength);
|
||||
terminalPath.lineTo(x + terminalSpacing, y + length);
|
||||
|
||||
terminalPath.moveTo(x - waferThickness * 5 / 2, bodyY + 1);
|
||||
terminalPath.lineTo(x - waferThickness * 5 / 2, bodyY + baseLength);
|
||||
terminalPath.lineTo(x - terminalSpacing * 2, y + length);
|
||||
|
||||
terminalPath.moveTo(x + waferThickness * 5 / 2, bodyY + 1);
|
||||
terminalPath.lineTo(x + waferThickness * 5 / 2, bodyY + baseLength);
|
||||
terminalPath.lineTo(x + terminalSpacing * 2, y + length);
|
||||
}
|
||||
body[2] = terminalPath;
|
||||
|
||||
// Rotate if needed
|
||||
@@ -219,14 +242,37 @@ public class LPSwitch extends AbstractTransparentComponent<String> implements IS
|
||||
|
||||
@SuppressWarnings("incomplete-switch")
|
||||
private void updateControlPoints() {
|
||||
|
||||
Point2D[] newControlPoints = null;
|
||||
if (getMiddleTerminalConfiguration() == MiddleTerminalConfiguration.Connected && controlPoints.length == 5) {
|
||||
newControlPoints = new Point2D[4];
|
||||
} else if (getMiddleTerminalConfiguration() == MiddleTerminalConfiguration.Disconnected && controlPoints.length == 4) {
|
||||
newControlPoints = new Point2D[5];
|
||||
newControlPoints[4] = new Point2D.Double();
|
||||
}
|
||||
if (newControlPoints != null) {
|
||||
for (int i = 0; i < newControlPoints.length && i < controlPoints.length; i++) {
|
||||
newControlPoints[i] = controlPoints[i];
|
||||
}
|
||||
controlPoints = newControlPoints;
|
||||
}
|
||||
|
||||
double x = controlPoints[0].getX();
|
||||
double y = controlPoints[0].getY();
|
||||
int terminalSpacing = (int) TERMINAL_SPACING.convertToPixels();
|
||||
int length = (int) LENGTH.convertToPixels();
|
||||
|
||||
controlPoints[1].setLocation(x - terminalSpacing, y + length);
|
||||
controlPoints[2].setLocation(x, y + length);
|
||||
controlPoints[3].setLocation(x + terminalSpacing, y + length);
|
||||
if (getMiddleTerminalConfiguration() == MiddleTerminalConfiguration.Connected) {
|
||||
int terminalSpacing = (int) TERMINAL_SPACING_CONNECTED.convertToPixels();
|
||||
controlPoints[1].setLocation(x - terminalSpacing, y + length);
|
||||
controlPoints[2].setLocation(x, y + length);
|
||||
controlPoints[3].setLocation(x + terminalSpacing, y + length);
|
||||
} else {
|
||||
int terminalSpacing = (int) TERMINAL_SPACING_DISCONNECTED.convertToPixels();
|
||||
controlPoints[1].setLocation(x - terminalSpacing * 2, y + length);
|
||||
controlPoints[2].setLocation(x - terminalSpacing, y + length);
|
||||
controlPoints[3].setLocation(x + terminalSpacing, y + length);
|
||||
controlPoints[4].setLocation(x + terminalSpacing * 2, y + length);
|
||||
}
|
||||
|
||||
// Rotate if needed
|
||||
if (orientation != Orientation.DEFAULT) {
|
||||
@@ -331,8 +377,24 @@ public class LPSwitch extends AbstractTransparentComponent<String> implements IS
|
||||
// Invalidate the body
|
||||
body = null;
|
||||
}
|
||||
|
||||
// @Override
|
||||
|
||||
@EditableProperty(name = "Middle Terminals")
|
||||
public MiddleTerminalConfiguration getMiddleTerminalConfiguration() {
|
||||
if (middleTerminalConfiguration == null) {
|
||||
middleTerminalConfiguration = MiddleTerminalConfiguration.Connected;
|
||||
}
|
||||
return middleTerminalConfiguration;
|
||||
}
|
||||
|
||||
public void setMiddleTerminalConfiguration(
|
||||
MiddleTerminalConfiguration middleTerminalConfiguration) {
|
||||
this.middleTerminalConfiguration = middleTerminalConfiguration;
|
||||
updateControlPoints();
|
||||
// Invalidate the body
|
||||
body = null;
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public String getControlPointNodeName(int index) {
|
||||
// // we don't want the switch to produce any nodes, it just makes connections
|
||||
// return null;
|
||||
@@ -347,27 +409,34 @@ public class LPSwitch extends AbstractTransparentComponent<String> implements IS
|
||||
|
||||
@Override
|
||||
public String getPositionName(int position) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
return "Treble";
|
||||
case 1:
|
||||
return "Middle";
|
||||
case 2:
|
||||
return "Rhythm";
|
||||
}
|
||||
return null;
|
||||
return switch (position) {
|
||||
case 0 -> "Treble";
|
||||
case 1 -> "Middle";
|
||||
case 2 -> "Rhythm";
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean arePointsConnected(int index1, int index2, int position) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
return index1 == 1 && index2 == 2;
|
||||
case 1:
|
||||
return index1 > 0 && index2 > 0 && index1 < index2;
|
||||
case 2:
|
||||
return index1 == 2 && index2 == 3;
|
||||
if (getMiddleTerminalConfiguration() == MiddleTerminalConfiguration.Connected) {
|
||||
return switch (position) {
|
||||
case 0 -> index1 == 1 && index2 == 2;
|
||||
case 1 -> index1 > 0 && index2 > 0 && index1 < index2;
|
||||
case 2 -> index1 == 2 && index2 == 3;
|
||||
default -> false;
|
||||
};
|
||||
} else {
|
||||
return switch (position) {
|
||||
case 0 -> index1 == 1 && index2 == 2;
|
||||
case 1 -> (index1 == 1 && index2 == 2) || (index1 == 3 && index2 == 4);
|
||||
case 2 -> index1 == 3 && index2 == 4;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public enum MiddleTerminalConfiguration {
|
||||
Connected, Disconnected
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,7 +321,8 @@ public class LeverSwitch extends AbstractAngledComponent<LeverSwitch.LeverSwitch
|
||||
case DP4T:
|
||||
controlPoints = new Point2D[10];
|
||||
for (int i = 0; i < controlPoints.length; i++) {
|
||||
controlPoints[i] = new Point2D.Double(x + (i % 2 == 1 ? terminalLength : 0), y + i * terminalSpacing);
|
||||
double yPos = i % 2 == 1 ? y + (i - 2) * terminalSpacing : y + i * terminalSpacing;
|
||||
controlPoints[i] = new Point2D.Double(x + (i % 2 == 1 ? terminalLength : 0), yPos);
|
||||
}
|
||||
break;
|
||||
case DP5T:
|
||||
|
||||
@@ -45,7 +45,7 @@ import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
|
||||
@ComponentDescriptor(name = "Auto-Wrap Label", author = "Branislav Stojkovic", category = "Misc",
|
||||
description = "User defined label with auto-wrapped text", instanceNamePrefix = "L", zOrder = IDIYComponent.TEXT, flexibleZOrder = true,
|
||||
description = "User defined label with auto-wrapped text", instanceNamePrefix = "LAB", zOrder = IDIYComponent.TEXT, flexibleZOrder = true,
|
||||
bomPolicy = BomPolicy.NEVER_SHOW, transformer = TextTransformer.class)
|
||||
public class WrapLabel extends AbstractComponent<String> {
|
||||
|
||||
|
||||
@@ -0,0 +1,398 @@
|
||||
/*
|
||||
|
||||
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.components.passive;
|
||||
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.common.Display;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.common.ObjectCache;
|
||||
import org.diylc.common.Orientation;
|
||||
import org.diylc.components.AbstractTransparentComponent;
|
||||
import org.diylc.core.*;
|
||||
import org.diylc.core.annotations.ComponentDescriptor;
|
||||
import org.diylc.core.annotations.EditableProperty;
|
||||
import org.diylc.core.measures.Resistance;
|
||||
import org.diylc.core.measures.ResistanceUnit;
|
||||
import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
import org.diylc.utils.Constants;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.io.Serial;
|
||||
|
||||
//@ComponentDescriptor(name = "Box Multi-Turn Pot", author = "Clive", category = "Passive",
|
||||
// instanceNamePrefix = "VR",
|
||||
// description = "Box style multi-turn potentiometer (e.g. Bourns PV36)",
|
||||
// zOrder = IDIYComponent.COMPONENT,
|
||||
// transformer = org.diylc.components.transform.BoxTrimmerTransformer.class)
|
||||
//<org.diylc.appframework.update.Change>
|
||||
// <changeType>NEW_FEATURE</changeType>
|
||||
// <description>Added Box style multi-turn potentiometer component (thank you chayward)</description>
|
||||
// </org.diylc.appframework.update.Change>
|
||||
public class BoxTrimmer extends AbstractTransparentComponent<Resistance> {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static Size PIN_SPACING = new Size(0.1d, SizeUnit.in);
|
||||
public static Size BODY_WIDTH = new Size(0.19d, SizeUnit.in); // 2x 2.41mm = 4.82mm ≈ 0.19"
|
||||
public static Size BODY_LENGTH = new Size(0.375d, SizeUnit.in); // 9.53mm from datasheet
|
||||
public static Color BODY_COLOR = Color.decode("#4477BB");
|
||||
public static Color BORDER_COLOR = BODY_COLOR.darker();
|
||||
public static Color PIN_COLOR = METAL_COLOR;
|
||||
public static Color SCREW_COLOR = BRASS_COLOR;
|
||||
protected String name;
|
||||
private Resistance value = new Resistance(10d, ResistanceUnit.K);
|
||||
private Orientation orientation = Orientation._90; // Default to vertical (90 degrees)
|
||||
private Display display = Display.NAME;
|
||||
private Color bodyColor = BODY_COLOR;
|
||||
private Color borderColor = BORDER_COLOR;
|
||||
private boolean screwFlippedHorizontally = false; // Track if screw flipped left-right
|
||||
private boolean screwFlippedVertically = false; // Track if screw flipped top-bottom
|
||||
|
||||
private Point2D[] controlPoints = new Point2D[3];
|
||||
|
||||
public BoxTrimmer() {
|
||||
super();
|
||||
updateControlPoints();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode,
|
||||
Project project, IDrawingObserver drawingObserver) {
|
||||
|
||||
Theme theme = (Theme) ConfigurationManager.getInstance()
|
||||
.readObject(IPlugInPort.THEME_KEY, Constants.DEFAULT_THEME);
|
||||
|
||||
int pinSpacing = (int) PIN_SPACING.convertToPixels();
|
||||
int bodyWidth = (int) BODY_WIDTH.convertToPixels();
|
||||
int bodyLength = (int) BODY_LENGTH.convertToPixels();
|
||||
|
||||
// Calculate positions - body is centered on the middle pin
|
||||
int centerX = (int) controlPoints[1].getX();
|
||||
int centerY = (int) controlPoints[1].getY();
|
||||
|
||||
int bodyX, bodyY, bodyW, bodyH;
|
||||
|
||||
// Position body based on orientation - fits tightly around pins
|
||||
if (orientation == Orientation.DEFAULT || orientation == Orientation._180) {
|
||||
// Horizontal - body is wide, pins are below/above
|
||||
bodyW = bodyLength;
|
||||
bodyH = bodyWidth;
|
||||
bodyX = centerX - bodyLength / 2;
|
||||
bodyY = centerY - bodyWidth / 2;
|
||||
} else {
|
||||
// Vertical - body is tall, pins are left/right
|
||||
bodyW = bodyWidth;
|
||||
bodyH = bodyLength;
|
||||
bodyX = centerX - bodyWidth / 2;
|
||||
bodyY = centerY - bodyLength / 2;
|
||||
}
|
||||
|
||||
// Draw body
|
||||
Composite oldComposite = g2d.getComposite();
|
||||
if (alpha < MAX_ALPHA) {
|
||||
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f * alpha / MAX_ALPHA));
|
||||
}
|
||||
g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
|
||||
g2d.fillRoundRect(bodyX, bodyY, bodyW, bodyH, bodyW / 10, bodyH / 10);
|
||||
g2d.setComposite(oldComposite);
|
||||
|
||||
// Draw border
|
||||
Color finalBorderColor =
|
||||
componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING ?
|
||||
SELECTION_COLOR :
|
||||
(outlineMode ? theme.getOutlineColor() : borderColor);
|
||||
g2d.setColor(finalBorderColor);
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1));
|
||||
g2d.drawRoundRect(bodyX, bodyY, bodyW, bodyH, bodyW / 10, bodyH / 10);
|
||||
|
||||
// Draw adjustment screw (slotted screw head) - position based on orientation
|
||||
if (!outlineMode) {
|
||||
// Brass/gold color for screw head
|
||||
// Color brassColor = new Color(184, 134, 11); // Dark goldenrod
|
||||
|
||||
int screwDiameter = Math.min(bodyW, bodyH) / 3;
|
||||
int screwX, screwY;
|
||||
|
||||
// Position screw based on orientation and flip state
|
||||
// The screw should be in the corner near pin 0, but can flip horizontally or vertically
|
||||
screwY = switch (orientation) {
|
||||
case DEFAULT -> {
|
||||
screwX = screwFlippedHorizontally ? bodyX + bodyW - screwDiameter : bodyX;
|
||||
yield screwFlippedVertically ? bodyY : bodyY + bodyH - screwDiameter;
|
||||
}
|
||||
case _90 -> {
|
||||
screwX = screwFlippedHorizontally ? bodyX + bodyW - screwDiameter : bodyX;
|
||||
yield screwFlippedVertically ? bodyY : bodyY + bodyH - screwDiameter;
|
||||
}
|
||||
case _180 -> {
|
||||
screwX = screwFlippedHorizontally ? bodyX : bodyX + bodyW - screwDiameter;
|
||||
yield screwFlippedVertically ? bodyY : bodyY + bodyH - screwDiameter;
|
||||
}
|
||||
case _270 -> {
|
||||
screwX = screwFlippedHorizontally ? bodyX + bodyW - screwDiameter : bodyX;
|
||||
yield screwFlippedVertically ? bodyY + bodyH - screwDiameter : bodyY;
|
||||
}
|
||||
default -> {
|
||||
screwX = bodyX;
|
||||
yield bodyY + bodyH - screwDiameter;
|
||||
}
|
||||
};
|
||||
|
||||
// Draw screw head circle
|
||||
g2d.setColor(SCREW_COLOR);
|
||||
g2d.fillOval(screwX, screwY, screwDiameter, screwDiameter);
|
||||
|
||||
// Draw slot in screw - centered horizontally through the circle
|
||||
g2d.setColor(SCREW_COLOR.darker());
|
||||
g2d.drawOval(screwX, screwY, screwDiameter, screwDiameter);
|
||||
|
||||
int slotLength = (int) (screwDiameter * 0.7);
|
||||
int slotThickness = Math.max(1, screwDiameter / 10);
|
||||
int slotCenterX = screwX + screwDiameter / 2;
|
||||
int slotCenterY = screwY + screwDiameter / 2;
|
||||
g2d.fillRect(slotCenterX - slotLength / 2, slotCenterY - slotThickness / 2, slotLength,
|
||||
slotThickness);
|
||||
}
|
||||
|
||||
// Draw pins
|
||||
g2d.setColor(outlineMode ? theme.getOutlineColor() : PIN_COLOR);
|
||||
for (Point2D p : controlPoints) {
|
||||
g2d.fillOval((int) p.getX() - 2, (int) p.getY() - 2, 4, 4);
|
||||
}
|
||||
|
||||
// Draw label - position outside body for readability
|
||||
Color labelColor =
|
||||
componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING ?
|
||||
LABEL_COLOR_SELECTED :
|
||||
LABEL_COLOR;
|
||||
g2d.setColor(labelColor);
|
||||
g2d.setFont(project.getFont());
|
||||
|
||||
String label = display == Display.NAME ?
|
||||
getName() :
|
||||
display == Display.VALUE ?
|
||||
getValue().toString() :
|
||||
display == Display.BOTH ? getName() + " " + getValue() : "";
|
||||
|
||||
if (!label.isEmpty()) {
|
||||
// Position label outside the component body based on orientation
|
||||
int labelX, labelY;
|
||||
java.awt.FontMetrics fm = g2d.getFontMetrics();
|
||||
|
||||
if (orientation == Orientation.DEFAULT || orientation == Orientation._180) {
|
||||
// Horizontal orientation - label below body
|
||||
labelX = bodyX + bodyW / 2;
|
||||
labelY = bodyY + bodyH + fm.getHeight();
|
||||
} else {
|
||||
// Vertical orientation - label to the right of body
|
||||
labelX = bodyX + bodyW + fm.stringWidth(label) / 2 + 5; // 5 pixel gap
|
||||
labelY = bodyY + bodyH / 2;
|
||||
}
|
||||
|
||||
drawCenteredText(g2d, label, labelX, labelY);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawCenteredText(Graphics2D g2d, String text, int x, int y) {
|
||||
java.awt.FontMetrics fm = g2d.getFontMetrics();
|
||||
int w = fm.stringWidth(text);
|
||||
int h = fm.getHeight();
|
||||
g2d.drawString(text, x - w / 2, y + fm.getAscent() - h / 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawIcon(Graphics2D g2d, int width, int height) {
|
||||
// Side profile view of PV36 trimmer
|
||||
int bodyWidth = width - 8;
|
||||
int bodyHeight = height - 10;
|
||||
int bodyX = 4;
|
||||
int bodyY = 2;
|
||||
|
||||
// Draw blue body (side profile - rectangular)
|
||||
g2d.setColor(BODY_COLOR);
|
||||
g2d.fillRect(bodyX, bodyY, bodyWidth, bodyHeight);
|
||||
g2d.setColor(BORDER_COLOR);
|
||||
g2d.drawRect(bodyX, bodyY, bodyWidth, bodyHeight);
|
||||
|
||||
// Draw brass adjustment screw on top left
|
||||
Color brassColor = new Color(184, 134, 11);
|
||||
int screwSize = Math.min(bodyWidth, bodyHeight) / 3;
|
||||
g2d.setColor(brassColor);
|
||||
g2d.fillOval(bodyX + 2, bodyY - screwSize / 2, screwSize, screwSize);
|
||||
|
||||
// Draw slot in screw
|
||||
g2d.setColor(new Color(80, 80, 80));
|
||||
int slotLen = (int) (screwSize * 0.6);
|
||||
g2d.drawLine(bodyX + 2 + screwSize / 2 - slotLen / 2, bodyY,
|
||||
bodyX + 2 + screwSize / 2 + slotLen / 2, bodyY);
|
||||
|
||||
// Draw three pins at bottom (light gray)
|
||||
g2d.setColor(PIN_COLOR);
|
||||
int pinSpacing = bodyWidth / 4;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int pinX = bodyX + pinSpacing * (i + 1) - 1;
|
||||
int pinY = bodyY + bodyHeight;
|
||||
g2d.fillRect(pinX, pinY, 2, 6);
|
||||
}
|
||||
}
|
||||
|
||||
@EditableProperty
|
||||
public Resistance getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(Resistance value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@EditableProperty
|
||||
public Orientation getOrientation() {
|
||||
return orientation;
|
||||
}
|
||||
|
||||
public void setOrientation(Orientation orientation) {
|
||||
this.orientation = orientation;
|
||||
updateControlPoints();
|
||||
}
|
||||
|
||||
public boolean getScrewFlippedHorizontally() {
|
||||
return screwFlippedHorizontally;
|
||||
}
|
||||
|
||||
public void setScrewFlippedHorizontally(boolean flipped) {
|
||||
this.screwFlippedHorizontally = flipped;
|
||||
}
|
||||
|
||||
public boolean getScrewFlippedVertically() {
|
||||
return screwFlippedVertically;
|
||||
}
|
||||
|
||||
public void setScrewFlippedVertically(boolean flipped) {
|
||||
this.screwFlippedVertically = flipped;
|
||||
}
|
||||
|
||||
@EditableProperty
|
||||
public Display getDisplay() {
|
||||
return display;
|
||||
}
|
||||
|
||||
public void setDisplay(Display display) {
|
||||
this.display = display;
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Body")
|
||||
public Color getBodyColor() {
|
||||
return bodyColor;
|
||||
}
|
||||
|
||||
public void setBodyColor(Color color) {
|
||||
this.bodyColor = color;
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Border")
|
||||
public Color getBorderColor() {
|
||||
return borderColor;
|
||||
}
|
||||
|
||||
public void setBorderColor(Color color) {
|
||||
this.borderColor = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getControlPointCount() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2D getControlPoint(int index) {
|
||||
return controlPoints[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public VisibilityPolicy getControlPointVisibilityPolicy(int index) {
|
||||
return VisibilityPolicy.WHEN_SELECTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setControlPoint(Point2D point, int index) {
|
||||
// Only allow moving via the first control point
|
||||
if (index == 0) {
|
||||
controlPoints[0] = new Point2D.Double(point.getX(), point.getY());
|
||||
updateControlPoints();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateControlPoints() {
|
||||
int pinSpacing = (int) PIN_SPACING.convertToPixels();
|
||||
double x = controlPoints[0] != null ? controlPoints[0].getX() : 0;
|
||||
double y = controlPoints[0] != null ? controlPoints[0].getY() : 0;
|
||||
|
||||
controlPoints[0] = new Point2D.Double(x, y);
|
||||
|
||||
// Pin arrangement depends on orientation
|
||||
// Pin 0 is always CCW, Pin 1 is Wiper, Pin 2 is CW
|
||||
switch (orientation) {
|
||||
case DEFAULT: // Horizontal, pins go right from pin 0
|
||||
controlPoints[1] = new Point2D.Double(x + pinSpacing, y);
|
||||
controlPoints[2] = new Point2D.Double(x + 2 * pinSpacing, y);
|
||||
break;
|
||||
case _90: // Vertical, pins go down from pin 0
|
||||
controlPoints[1] = new Point2D.Double(x, y + pinSpacing);
|
||||
controlPoints[2] = new Point2D.Double(x, y + 2 * pinSpacing);
|
||||
break;
|
||||
case _180: // Horizontal mirrored, pins go left from pin 0
|
||||
controlPoints[1] = new Point2D.Double(x - pinSpacing, y);
|
||||
controlPoints[2] = new Point2D.Double(x - 2 * pinSpacing, y);
|
||||
break;
|
||||
case _270: // Vertical mirrored, pins go up from pin 0
|
||||
controlPoints[1] = new Point2D.Double(x, y - pinSpacing);
|
||||
controlPoints[2] = new Point2D.Double(x, y - 2 * pinSpacing);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getControlPointNodeName(int index) {
|
||||
return new String[] {"CCW (1)", "Wiper (2)", "CW (3)"}[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name != null && !name.trim().isEmpty() ? name : super.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@EditableProperty(defaultable = false)
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isControlPointSticky(int index) {
|
||||
// First control point is sticky so component can be placed from side panel
|
||||
// But setControlPoint ensures all pins move together
|
||||
return index == 0;
|
||||
}
|
||||
}
|
||||
+75
@@ -35,9 +35,12 @@ import java.awt.geom.Rectangle2D;
|
||||
import java.awt.geom.RoundRectangle2D;
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
|
||||
import org.diylc.awt.StringUtils;
|
||||
import org.diylc.common.HorizontalAlignment;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.common.ObjectCache;
|
||||
import org.diylc.common.Orientation;
|
||||
import org.diylc.common.VerticalAlignment;
|
||||
import org.diylc.components.transform.PotentiometerTransformer;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.CreationMethod;
|
||||
@@ -66,10 +69,12 @@ public class PotentiometerPanel extends AbstractPotentiometer implements ILayere
|
||||
protected static Size PIN_SIZE = new Size(0.05d, SizeUnit.in);
|
||||
protected static Size NUT_SIZE = new Size(0.4d, SizeUnit.in);
|
||||
protected static Size SHAFT_SIZE = new Size(1 / 4d, SizeUnit.in);
|
||||
protected static Size MARKER_OFFSET = new Size(0.05d, SizeUnit.in);
|
||||
protected static Color BODY_COLOR = Color.lightGray;
|
||||
protected static Color WAFER_COLOR = Color.decode("#CD8500");
|
||||
protected static Color BORDER_COLOR = Color.gray;
|
||||
protected static Color NUT_COLOR = Color.decode("#CBD5DB");
|
||||
protected static Color MARKER_COLOR_DEFAULT = Color.gray;
|
||||
|
||||
|
||||
protected Size bodyDiameter = BODY_DIAMETER;
|
||||
@@ -83,6 +88,8 @@ public class PotentiometerPanel extends AbstractPotentiometer implements ILayere
|
||||
@Deprecated
|
||||
protected boolean showShaft = false;
|
||||
protected View view;
|
||||
private Boolean showMarkers;
|
||||
private Color markerColor;
|
||||
// Array of 7 elements: 3 lug connectors, 1 pot body and 3 lugs
|
||||
transient protected Area[] body = null;
|
||||
|
||||
@@ -353,6 +360,50 @@ public class PotentiometerPanel extends AbstractPotentiometer implements ILayere
|
||||
y = panelHeight / 2 + fontMetrics.getAscent();
|
||||
|
||||
g2d.drawString(getValueForDisplay(), (int) (bodyRect.getX() + x), (int) (bodyRect.getY() + y));
|
||||
|
||||
// Draw markers if enabled.
|
||||
if (getShowMarkers()) {
|
||||
g2d.setColor(outlineMode ? theme.getOutlineColor() : getMarkerColor());
|
||||
|
||||
// Get circle center and radius
|
||||
Rectangle2D circleBounds = body[3].getBounds2D();
|
||||
double centerX = circleBounds.getCenterX();
|
||||
double centerY = circleBounds.getCenterY();
|
||||
double radius = getBodyDiameter().convertToPixels() / 2;
|
||||
|
||||
// Fixed distance from circle edge (inside the circle)
|
||||
double markerOffset = MARKER_OFFSET.convertToPixels() + 2;
|
||||
double markerDistance = radius - markerOffset;
|
||||
|
||||
// Draw markers "1", "2", "3" along radial line from center to each lug, close to circle edge
|
||||
// Reverse numbering when view is ShaftDown
|
||||
boolean reverseOrder = getView() == View.ShaftDown;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Point2D lugPoint = controlPoints[i];
|
||||
|
||||
// Calculate direction from center to lug
|
||||
double dx = lugPoint.getX() - centerX;
|
||||
double dy = lugPoint.getY() - centerY;
|
||||
double distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance > 0) {
|
||||
// Normalize direction vector
|
||||
double unitX = dx / distance;
|
||||
double unitY = dy / distance;
|
||||
|
||||
// Position marker at fixed distance from circle edge along radial line
|
||||
double markerX = centerX + unitX * markerDistance;
|
||||
double markerY = centerY + unitY * markerDistance;
|
||||
|
||||
// Determine marker number based on view
|
||||
int markerNumber = reverseOrder ? (3 - i) : (i + 1);
|
||||
|
||||
StringUtils.drawCenteredText(g2d, Integer.toString(markerNumber), markerX, markerY,
|
||||
HorizontalAlignment.CENTER, VerticalAlignment.CENTER);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -474,6 +525,30 @@ public class PotentiometerPanel extends AbstractPotentiometer implements ILayere
|
||||
public void setWaferColor(Color waferColor) {
|
||||
this.waferColor = waferColor;
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Markers")
|
||||
public Boolean getShowMarkers() {
|
||||
if (showMarkers == null) {
|
||||
showMarkers = false;
|
||||
}
|
||||
return showMarkers;
|
||||
}
|
||||
|
||||
public void setShowMarkers(Boolean showMarkers) {
|
||||
this.showMarkers = showMarkers;
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Marker Color")
|
||||
public Color getMarkerColor() {
|
||||
if (markerColor == null) {
|
||||
markerColor = MARKER_COLOR_DEFAULT;
|
||||
}
|
||||
return markerColor;
|
||||
}
|
||||
|
||||
public void setMarkerColor(Color markerColor) {
|
||||
this.markerColor = markerColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInternalLinkName(int index1, int index2) {
|
||||
|
||||
@@ -23,7 +23,7 @@ package org.diylc.components.passive;
|
||||
|
||||
public enum Taper {
|
||||
|
||||
LIN, LOG, REV_LOG, W, S;
|
||||
LIN, LOG, REV_LOG, W, S, M, N;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
@@ -157,6 +157,6 @@ public class TransformerCore extends AbstractComponent<Void> {
|
||||
|
||||
@Override
|
||||
public boolean canPointMoveFreely(int pointIndex) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+693
@@ -0,0 +1,693 @@
|
||||
/*
|
||||
|
||||
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.components.semiconductors;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.Color;
|
||||
import java.awt.Composite;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
|
||||
import org.diylc.common.Display;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.common.ObjectCache;
|
||||
import org.diylc.common.Orientation;
|
||||
import org.diylc.components.AbstractTransparentComponent;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.IDrawingObserver;
|
||||
import org.diylc.core.Project;
|
||||
import org.diylc.core.Theme;
|
||||
import org.diylc.core.VisibilityPolicy;
|
||||
import org.diylc.core.annotations.ComponentDescriptor;
|
||||
import org.diylc.core.annotations.EditableProperty;
|
||||
import org.diylc.core.annotations.KeywordPolicy;
|
||||
import org.diylc.core.gerber.IGerberComponentSimple;
|
||||
import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
import org.diylc.utils.Constants;
|
||||
|
||||
@ComponentDescriptor(name = "Multiwatt IC", author = "Branislav Stojkovic", category = "Semiconductors",
|
||||
instanceNamePrefix = "IC", description = "Multiwatt package IC typically used for audio amplifiers of high power applications",
|
||||
zOrder = IDIYComponent.COMPONENT, keywordPolicy = KeywordPolicy.SHOW_VALUE,
|
||||
enableCache = true)
|
||||
public class MultiwattIC extends AbstractTransparentComponent<String> implements IGerberComponentSimple {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static Color BODY_COLOR = Color.gray;
|
||||
public static Color BORDER_COLOR = Color.gray.darker();
|
||||
public static Color PIN_COLOR = Color.decode("#00B2EE");
|
||||
public static Color PIN_BORDER_COLOR = PIN_COLOR.darker();
|
||||
public static Color LABEL_COLOR = Color.white;
|
||||
public static Color TAB_COLOR = Color.lightGray;
|
||||
public static Color TAB_BORDER_COLOR = TAB_COLOR.darker();
|
||||
public static int EDGE_RADIUS = 6;
|
||||
public static Size PIN_SIZE = new Size(0.7d, SizeUnit.mm);
|
||||
public static Size THICKNESS = new Size(5d, SizeUnit.mm);
|
||||
public static Size TAB_THICKNESS = new Size(1.6d, SizeUnit.mm);
|
||||
// Multiwatt packages use 0.1 inch (2.54mm) spacing between pins in the same row
|
||||
public static Size PIN_SPACING = new Size(0.1d, SizeUnit.in);
|
||||
// Multiwatt packages have staggered pins with 0.2 inch (5.08mm) row spacing
|
||||
public static Size ROW_SPACING = new Size(0.2d, SizeUnit.in);
|
||||
// Lead offset due to bent leads
|
||||
public static Size LEAD_OFFSET = new Size(4d, SizeUnit.mm);
|
||||
|
||||
private String value = "";
|
||||
private Orientation orientation = Orientation.DEFAULT;
|
||||
private MultiwattType type = MultiwattType.MULTIWATT_11;
|
||||
private Point2D[] controlPoints = new Point2D[] {new Point2D.Double(0, 0)};
|
||||
protected Display display = Display.NAME;
|
||||
private Color bodyColor = BODY_COLOR;
|
||||
private Color borderColor = BORDER_COLOR;
|
||||
private Color labelColor = LABEL_COLOR;
|
||||
private Color tabColor = TAB_COLOR;
|
||||
private Color tabBorderColor = TAB_BORDER_COLOR;
|
||||
transient private Area[] body;
|
||||
|
||||
public MultiwattIC() {
|
||||
super();
|
||||
updateControlPoints();
|
||||
alpha = 100;
|
||||
}
|
||||
|
||||
@EditableProperty
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@EditableProperty
|
||||
public Orientation getOrientation() {
|
||||
return orientation;
|
||||
}
|
||||
|
||||
public void setOrientation(Orientation orientation) {
|
||||
this.orientation = orientation;
|
||||
updateControlPoints();
|
||||
// Reset body shape.
|
||||
body = null;
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Type")
|
||||
public MultiwattType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(MultiwattType type) {
|
||||
this.type = type;
|
||||
updateControlPoints();
|
||||
// Reset body shape;
|
||||
body = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPointMoveFreely(int pointIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@EditableProperty
|
||||
public Display getDisplay() {
|
||||
if (display == null) {
|
||||
display = Display.VALUE;
|
||||
}
|
||||
return display;
|
||||
}
|
||||
|
||||
public void setDisplay(Display display) {
|
||||
this.display = display;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getControlPointCount() {
|
||||
return controlPoints.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2D getControlPoint(int index) {
|
||||
return controlPoints[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isControlPointSticky(int index) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VisibilityPolicy getControlPointVisibilityPolicy(int index) {
|
||||
return VisibilityPolicy.NEVER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setControlPoint(Point2D point, int index) {
|
||||
controlPoints[index].setLocation(point);
|
||||
body = null;
|
||||
}
|
||||
|
||||
private void updateControlPoints() {
|
||||
Point2D firstPoint = controlPoints[0];
|
||||
int pinCount = type.getPinCount();
|
||||
int pinsLeft = type.getPinsLeft();
|
||||
int pinsRight = type.getPinsRight();
|
||||
controlPoints = new Point2D.Double[pinCount];
|
||||
controlPoints[0] = firstPoint;
|
||||
double pinSpacing = PIN_SPACING.convertToPixels();
|
||||
double rowSpacing = ROW_SPACING.convertToPixels();
|
||||
// Update control points for staggered layout
|
||||
double dx1, dy1, dx2, dy2;
|
||||
|
||||
// Left side pins (first row)
|
||||
for (int i = 0; i < pinsLeft; i++) {
|
||||
switch (orientation) {
|
||||
case DEFAULT:
|
||||
dx1 = 0;
|
||||
dy1 = i * pinSpacing;
|
||||
break;
|
||||
case _90:
|
||||
dx1 = -i * pinSpacing;
|
||||
dy1 = 0;
|
||||
break;
|
||||
case _180:
|
||||
dx1 = 0;
|
||||
dy1 = -i * pinSpacing;
|
||||
break;
|
||||
case _270:
|
||||
dx1 = i * pinSpacing;
|
||||
dy1 = 0;
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Unexpected orientation: " + orientation);
|
||||
}
|
||||
controlPoints[i] = new Point2D.Double((int) (firstPoint.getX() + dx1), (int) (firstPoint.getY() + dy1));
|
||||
}
|
||||
|
||||
// Right side pins (second row, staggered - offset by half pin spacing)
|
||||
for (int i = 0; i < pinsRight; i++) {
|
||||
switch (orientation) {
|
||||
case DEFAULT:
|
||||
dx2 = rowSpacing;
|
||||
dy2 = (i + 0.5) * pinSpacing; // Offset by half spacing for staggered effect
|
||||
break;
|
||||
case _90:
|
||||
dx2 = -(i + 0.5) * pinSpacing; // Offset by half spacing for staggered effect
|
||||
dy2 = rowSpacing;
|
||||
break;
|
||||
case _180:
|
||||
dx2 = -rowSpacing;
|
||||
dy2 = -(i + 0.5) * pinSpacing; // Offset by half spacing for staggered effect
|
||||
break;
|
||||
case _270:
|
||||
dx2 = (i + 0.5) * pinSpacing; // Offset by half spacing for staggered effect
|
||||
dy2 = -rowSpacing;
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Unexpected orientation: " + orientation);
|
||||
}
|
||||
controlPoints[i + pinsLeft] = new Point2D.Double((int) (firstPoint.getX() + dx2), (int) (firstPoint.getY() + dy2));
|
||||
}
|
||||
}
|
||||
|
||||
public Area[] getBody() {
|
||||
if (body == null) {
|
||||
body = new Area[2];
|
||||
double x = controlPoints[0].getX();
|
||||
double y = controlPoints[0].getY();
|
||||
double pinSize = PIN_SIZE.convertToPixels();
|
||||
double pinSpacing = PIN_SPACING.convertToPixels();
|
||||
double rowSpacing = ROW_SPACING.convertToPixels();
|
||||
double tabThickness = TAB_THICKNESS.convertToPixels();
|
||||
double leadOffset = LEAD_OFFSET.convertToPixels();
|
||||
int pinsLeft = type.getPinsLeft();
|
||||
int pinsRight = type.getPinsRight();
|
||||
// Calculate height accounting for staggered pins (right row offset by half spacing)
|
||||
double leftRowHeight = (pinsLeft - 1) * pinSpacing;
|
||||
double rightRowHeight = (pinsRight - 1 + 0.5) * pinSpacing; // Account for half-spacing offset
|
||||
double maxHeight = Math.max(leftRowHeight, rightRowHeight) + pinSpacing; // Add spacing for last pin
|
||||
double bodyX, bodyY, bodyWidth, bodyHeight;
|
||||
double tabX, tabY, tabWidth, tabHeight;
|
||||
|
||||
switch (orientation) {
|
||||
case DEFAULT:
|
||||
bodyWidth = rowSpacing - pinSize;
|
||||
bodyHeight = maxHeight;
|
||||
// Offset body to the left by lead offset (bent leads)
|
||||
bodyX = x + pinSize / 2 - leadOffset;
|
||||
bodyY = y - pinSpacing / 2;
|
||||
// Tab on the left side (where there are more pins) inside the body bounds
|
||||
tabX = bodyX;
|
||||
tabY = bodyY;
|
||||
tabWidth = tabThickness;
|
||||
tabHeight = bodyHeight;
|
||||
break;
|
||||
case _90:
|
||||
bodyWidth = maxHeight;
|
||||
bodyHeight = rowSpacing - pinSize;
|
||||
// Body positioned between the two pin rows
|
||||
// Left pins extend to negative X, body starts from leftmost pin
|
||||
// For _90: left pins go left (negative X), so leftmost is at x - (pinsLeft-1)*pinSpacing
|
||||
// Offset body upward by lead offset (bent leads)
|
||||
bodyX = x - (pinsLeft - 1) * pinSpacing - pinSpacing / 2;
|
||||
bodyY = y + pinSize / 2 - leadOffset;
|
||||
// Tab on the top side (where there are more pins - left row at y) inside the body bounds
|
||||
tabX = bodyX;
|
||||
tabY = bodyY;
|
||||
tabWidth = bodyWidth;
|
||||
tabHeight = tabThickness;
|
||||
break;
|
||||
case _180:
|
||||
bodyWidth = rowSpacing - pinSize;
|
||||
bodyHeight = maxHeight;
|
||||
// Body positioned between the two pin rows
|
||||
// For _180: left pins go up (negative Y), right pins at x - rowSpacing
|
||||
// Left row is at x, right row is at x - rowSpacing
|
||||
// Pins extend upward from y, so body should start from top of pin array
|
||||
// Offset body to the right by lead offset (bent leads)
|
||||
bodyX = x - rowSpacing + pinSize / 2 + leadOffset;
|
||||
bodyY = y - (pinsLeft - 1) * pinSpacing - pinSpacing / 2;
|
||||
// Tab on the right side (where there are more pins - left row at x) inside the body bounds
|
||||
tabX = bodyX + bodyWidth - tabThickness;
|
||||
tabY = bodyY;
|
||||
tabWidth = tabThickness;
|
||||
tabHeight = bodyHeight;
|
||||
break;
|
||||
case _270:
|
||||
bodyWidth = maxHeight;
|
||||
bodyHeight = rowSpacing - pinSize;
|
||||
// Body positioned between the two pin rows
|
||||
// For _270: left pins go right (positive X), so leftmost is at x
|
||||
// Left row is at y, right row is at y - rowSpacing
|
||||
// Similar to _90 but mirrored
|
||||
// Offset body downward by lead offset (bent leads)
|
||||
bodyX = x - pinSpacing / 2;
|
||||
bodyY = y - rowSpacing + pinSize / 2 + leadOffset;
|
||||
// Tab on the bottom side (where there are more pins - left row at y) inside the body bounds
|
||||
tabX = bodyX;
|
||||
tabY = bodyY + bodyHeight - tabThickness;
|
||||
tabWidth = bodyWidth;
|
||||
tabHeight = tabThickness;
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Unexpected orientation: " + orientation);
|
||||
}
|
||||
body[0] = new Area(new Rectangle2D.Double(bodyX, bodyY, bodyWidth, bodyHeight));
|
||||
body[1] = new Area(new Rectangle2D.Double(tabX, tabY, tabWidth, tabHeight));
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project,
|
||||
IDrawingObserver drawingObserver) {
|
||||
if (checkPointsClipped(g2d.getClip())) {
|
||||
return;
|
||||
}
|
||||
Area mainArea = getBody()[0];
|
||||
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1f));
|
||||
if (!outlineMode) {
|
||||
int pinSize = (int) PIN_SIZE.convertToPixels() / 2 * 2;
|
||||
Rectangle2D bodyBounds = mainArea.getBounds2D();
|
||||
double leadThickness = pinSize; // Lead width matches pin size
|
||||
|
||||
for (Point2D point : controlPoints) {
|
||||
double pinX = point.getX();
|
||||
double pinY = point.getY();
|
||||
|
||||
// Check if pin is inside the body
|
||||
boolean insideBody = mainArea.contains(pinX, pinY);
|
||||
|
||||
if (insideBody) {
|
||||
// Draw pin as square inside body
|
||||
Rectangle2D rect = new Rectangle2D.Double(pinX - pinSize / 2,
|
||||
pinY - pinSize / 2, pinSize, pinSize);
|
||||
g2d.setColor(PIN_COLOR);
|
||||
drawingObserver.startTracking();
|
||||
g2d.fill(rect);
|
||||
drawingObserver.stopTracking();
|
||||
g2d.setColor(PIN_BORDER_COLOR);
|
||||
g2d.draw(rect);
|
||||
} else {
|
||||
// Draw rectangular lead from body edge to pin position
|
||||
double bodyX = bodyBounds.getX();
|
||||
double bodyY = bodyBounds.getY();
|
||||
double bodyWidth = bodyBounds.getWidth();
|
||||
double bodyHeight = bodyBounds.getHeight();
|
||||
|
||||
// Find the closest point on body edge to the pin
|
||||
double edgeX, edgeY;
|
||||
|
||||
// Determine which side of the body the pin is on
|
||||
if (pinX < bodyX) {
|
||||
// Pin is to the left of body
|
||||
edgeX = bodyX;
|
||||
edgeY = Math.max(bodyY, Math.min(bodyY + bodyHeight, pinY));
|
||||
} else if (pinX > bodyX + bodyWidth) {
|
||||
// Pin is to the right of body
|
||||
edgeX = bodyX + bodyWidth;
|
||||
edgeY = Math.max(bodyY, Math.min(bodyY + bodyHeight, pinY));
|
||||
} else if (pinY < bodyY) {
|
||||
// Pin is above body
|
||||
edgeX = Math.max(bodyX, Math.min(bodyX + bodyWidth, pinX));
|
||||
edgeY = bodyY;
|
||||
} else {
|
||||
// Pin is below body
|
||||
edgeX = Math.max(bodyX, Math.min(bodyX + bodyWidth, pinX));
|
||||
edgeY = bodyY + bodyHeight;
|
||||
}
|
||||
|
||||
// Draw rectangular lead
|
||||
double leadX = Math.min(edgeX, pinX) - leadThickness / 2;
|
||||
double leadY = Math.min(edgeY, pinY) - leadThickness / 2;
|
||||
double leadWidth = Math.abs(pinX - edgeX) + leadThickness;
|
||||
double leadHeight = Math.abs(pinY - edgeY) + leadThickness;
|
||||
|
||||
// If lead is horizontal or vertical, draw as rectangle
|
||||
if (Math.abs(pinX - edgeX) < 0.1) {
|
||||
// Vertical lead
|
||||
leadWidth = leadThickness;
|
||||
leadX = edgeX - leadThickness / 2;
|
||||
} else if (Math.abs(pinY - edgeY) < 0.1) {
|
||||
// Horizontal lead
|
||||
leadHeight = leadThickness;
|
||||
leadY = edgeY - leadThickness / 2;
|
||||
}
|
||||
|
||||
Rectangle2D leadRect = new Rectangle2D.Double(leadX, leadY, leadWidth, leadHeight);
|
||||
g2d.setColor(PIN_COLOR);
|
||||
drawingObserver.startTracking();
|
||||
g2d.fill(leadRect);
|
||||
drawingObserver.stopTracking();
|
||||
g2d.setColor(PIN_BORDER_COLOR);
|
||||
g2d.draw(leadRect);
|
||||
|
||||
// Draw pin at the end of the lead
|
||||
Rectangle2D pinRect = new Rectangle2D.Double(pinX - pinSize / 2,
|
||||
pinY - pinSize / 2, pinSize, pinSize);
|
||||
g2d.setColor(PIN_COLOR);
|
||||
drawingObserver.startTracking();
|
||||
g2d.fill(pinRect);
|
||||
drawingObserver.stopTracking();
|
||||
g2d.setColor(PIN_BORDER_COLOR);
|
||||
g2d.draw(pinRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawingObserver.startTracking();
|
||||
Composite oldComposite = g2d.getComposite();
|
||||
if (alpha < MAX_ALPHA) {
|
||||
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f * alpha / MAX_ALPHA));
|
||||
}
|
||||
g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : getBodyColor());
|
||||
g2d.fill(mainArea);
|
||||
drawingObserver.stopTracking();
|
||||
|
||||
// Draw heat sink tab
|
||||
Area tabArea = getBody()[1];
|
||||
Color finalTabColor;
|
||||
if (outlineMode) {
|
||||
Theme theme =
|
||||
(Theme) ConfigurationManager.getInstance().readObject(IPlugInPort.THEME_KEY, Constants.DEFAULT_THEME);
|
||||
finalTabColor = theme.getOutlineColor();
|
||||
} else {
|
||||
finalTabColor = tabColor;
|
||||
}
|
||||
g2d.setColor(finalTabColor);
|
||||
drawingObserver.startTracking();
|
||||
g2d.fill(tabArea);
|
||||
drawingObserver.stopTracking();
|
||||
g2d.setComposite(oldComposite);
|
||||
|
||||
if (!outlineMode) {
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1));
|
||||
g2d.setColor(tabBorderColor);
|
||||
g2d.draw(tabArea);
|
||||
}
|
||||
|
||||
Color finalBorderColor;
|
||||
if (outlineMode) {
|
||||
Theme theme =
|
||||
(Theme) ConfigurationManager.getInstance().readObject(IPlugInPort.THEME_KEY, Constants.DEFAULT_THEME);
|
||||
finalBorderColor =
|
||||
componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING ? SELECTION_COLOR
|
||||
: theme.getOutlineColor();
|
||||
} else {
|
||||
finalBorderColor =
|
||||
componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING ? SELECTION_COLOR
|
||||
: getBorderColor();
|
||||
}
|
||||
g2d.setColor(finalBorderColor);
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1));
|
||||
g2d.draw(mainArea);
|
||||
// Draw label.
|
||||
g2d.setFont(project.getFont());
|
||||
Color finalLabelColor;
|
||||
if (outlineMode) {
|
||||
Theme theme =
|
||||
(Theme) ConfigurationManager.getInstance().readObject(IPlugInPort.THEME_KEY, Constants.DEFAULT_THEME);
|
||||
finalLabelColor =
|
||||
componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING ? LABEL_COLOR_SELECTED
|
||||
: theme.getOutlineColor();
|
||||
} else {
|
||||
finalLabelColor =
|
||||
componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING ? LABEL_COLOR_SELECTED
|
||||
: getLabelColor();
|
||||
}
|
||||
g2d.setColor(finalLabelColor);
|
||||
FontMetrics fontMetrics = g2d.getFontMetrics(g2d.getFont());
|
||||
String label = "";
|
||||
label = (getDisplay() == Display.NAME) ? getName() : getValue();
|
||||
if (getDisplay() == Display.NONE) {
|
||||
label = "";
|
||||
}
|
||||
if (getDisplay() == Display.BOTH) {
|
||||
label = getName() + " " + (getValue() == null ? "" : getValue().toString());
|
||||
}
|
||||
Rectangle2D rect = fontMetrics.getStringBounds(label, g2d);
|
||||
int textHeight = (int) (rect.getHeight());
|
||||
int textWidth = (int) (rect.getWidth());
|
||||
// Center text horizontally and vertically
|
||||
Rectangle bounds = mainArea.getBounds();
|
||||
int x = (int) (bounds.getX() + (bounds.width - textWidth) / 2);
|
||||
int y = (int) (bounds.getY() + (bounds.height - textHeight) / 2 + fontMetrics.getAscent());
|
||||
g2d.drawString(label, x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawIcon(Graphics2D g2d, int width, int height) {
|
||||
int margin = 2;
|
||||
int pinSize = 2 * width / 32;
|
||||
int leftPinX = width / 6 + 9; // Move left pins 9px to the right (4px + 3px + 2px)
|
||||
int rightPinX = 5 * width / 6;
|
||||
int leadOffset = (int) (LEAD_OFFSET.convertToPixels() * width / 100); // Scale lead offset for icon
|
||||
int bodyX = leftPinX + pinSize - leadOffset + 2; // Offset body to the left, then move 2px to the right
|
||||
int bodyWidth = (rightPinX - bodyX - pinSize) / 2 + 2; // Thinner body, make 2px thicker
|
||||
int bodyHeight = height - 2 * margin;
|
||||
int bodyY = margin;
|
||||
|
||||
// Calculate pin spacing to use all vertical space
|
||||
// Use same spacing for both rows, with front row offset by half spacing
|
||||
int backPins = 4;
|
||||
int frontPins = 3;
|
||||
// Use the spacing that fits the back row (more pins)
|
||||
int pinSpacing = (bodyHeight - pinSize) / (backPins - 1);
|
||||
int backStartY = margin + pinSize / 2 + 1; // Move pins 1px down
|
||||
// Front pins start with half spacing offset for staggered effect
|
||||
int frontStartY = margin + pinSize / 2 + pinSpacing / 2 + 1; // Move pins 1px down
|
||||
|
||||
// Draw thin body between pin rows
|
||||
g2d.setColor(BODY_COLOR);
|
||||
g2d.fillRect(bodyX, bodyY, bodyWidth, bodyHeight);
|
||||
g2d.setColor(BORDER_COLOR);
|
||||
g2d.drawRect(bodyX, bodyY, bodyWidth, bodyHeight);
|
||||
|
||||
// Draw heat sink tab on left side (where there are more pins)
|
||||
int tabWidth = pinSize * 2;
|
||||
g2d.setColor(TAB_COLOR);
|
||||
g2d.fillRect(bodyX, bodyY, tabWidth, bodyHeight);
|
||||
g2d.setColor(TAB_BORDER_COLOR);
|
||||
g2d.drawRect(bodyX, bodyY, tabWidth, bodyHeight);
|
||||
|
||||
// Draw 4 pins in the back (left row, more pins) as squares - inside body
|
||||
g2d.setColor(PIN_COLOR);
|
||||
for (int i = 0; i < backPins; i++) {
|
||||
int pinY = backStartY + i * pinSpacing - pinSize / 2;
|
||||
g2d.fillRect(leftPinX - pinSize, pinY, pinSize, pinSize);
|
||||
g2d.setColor(PIN_BORDER_COLOR);
|
||||
g2d.drawRect(leftPinX - pinSize, pinY, pinSize, pinSize);
|
||||
g2d.setColor(PIN_COLOR);
|
||||
}
|
||||
|
||||
// Draw 3 pins in the front (right row, fewer pins) with leads going left to body
|
||||
// Use same spacing as back row, just offset by half spacing
|
||||
g2d.setColor(PIN_COLOR);
|
||||
for (int i = 0; i < frontPins; i++) {
|
||||
int pinY = frontStartY + i * pinSpacing;
|
||||
int pinX = rightPinX;
|
||||
int bodyRightEdge = bodyX + bodyWidth;
|
||||
|
||||
// Draw lead from body edge to pin (horizontal lead going left)
|
||||
if (pinX > bodyRightEdge) {
|
||||
int leadX = bodyRightEdge;
|
||||
int leadY = pinY - pinSize / 2;
|
||||
int leadWidth = pinX - bodyRightEdge;
|
||||
int leadHeight = pinSize;
|
||||
|
||||
g2d.fillRect(leadX, leadY, leadWidth, leadHeight);
|
||||
g2d.setColor(PIN_BORDER_COLOR);
|
||||
g2d.drawRect(leadX, leadY, leadWidth, leadHeight);
|
||||
g2d.setColor(PIN_COLOR);
|
||||
}
|
||||
|
||||
// Draw pin at the end of the lead
|
||||
g2d.fillRect(pinX - pinSize / 2, pinY - pinSize / 2, pinSize, pinSize);
|
||||
g2d.setColor(PIN_BORDER_COLOR);
|
||||
g2d.drawRect(pinX - pinSize / 2, pinY - pinSize / 2, pinSize, pinSize);
|
||||
g2d.setColor(PIN_COLOR);
|
||||
}
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Body")
|
||||
public Color getBodyColor() {
|
||||
if (bodyColor == null) {
|
||||
bodyColor = BODY_COLOR;
|
||||
}
|
||||
return bodyColor;
|
||||
}
|
||||
|
||||
public void setBodyColor(Color bodyColor) {
|
||||
this.bodyColor = bodyColor;
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Border")
|
||||
public Color getBorderColor() {
|
||||
if (borderColor == null) {
|
||||
borderColor = BORDER_COLOR;
|
||||
}
|
||||
return borderColor;
|
||||
}
|
||||
|
||||
public void setBorderColor(Color borderColor) {
|
||||
this.borderColor = borderColor;
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Label")
|
||||
public Color getLabelColor() {
|
||||
if (labelColor == null) {
|
||||
labelColor = LABEL_COLOR;
|
||||
}
|
||||
return labelColor;
|
||||
}
|
||||
|
||||
public void setLabelColor(Color labelColor) {
|
||||
this.labelColor = labelColor;
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Tab")
|
||||
public Color getTabColor() {
|
||||
if (tabColor == null) {
|
||||
tabColor = TAB_COLOR;
|
||||
}
|
||||
return tabColor;
|
||||
}
|
||||
|
||||
public void setTabColor(Color tabColor) {
|
||||
this.tabColor = tabColor;
|
||||
}
|
||||
|
||||
@EditableProperty(name = "Tab Border")
|
||||
public Color getTabBorderColor() {
|
||||
if (tabBorderColor == null) {
|
||||
tabBorderColor = TAB_BORDER_COLOR;
|
||||
}
|
||||
return tabBorderColor;
|
||||
}
|
||||
|
||||
public void setTabBorderColor(Color tabBorderColor) {
|
||||
this.tabBorderColor = tabBorderColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle2D getCachingBounds() {
|
||||
double minX = Integer.MAX_VALUE;
|
||||
double maxX = Integer.MIN_VALUE;
|
||||
double minY = Integer.MAX_VALUE;
|
||||
double maxY = Integer.MIN_VALUE;
|
||||
int margin = 50;
|
||||
for (int i = 0; i < getControlPointCount(); i++) {
|
||||
Point2D p = getControlPoint(i);
|
||||
if (p.getX() < minX)
|
||||
minX = p.getX();
|
||||
if (p.getX() > maxX)
|
||||
maxX = p.getX();
|
||||
if (p.getY() < minY)
|
||||
minY = p.getY();
|
||||
if (p.getY() > maxY)
|
||||
maxY = p.getY();
|
||||
}
|
||||
|
||||
return new Rectangle2D.Double(minX - margin, minY - margin, maxX - minX + 2 * margin, maxY - minY + 2 * margin);
|
||||
}
|
||||
|
||||
public static enum MultiwattType {
|
||||
// Multiwatt-11: 6 pins on left, 5 pins on right (staggered)
|
||||
MULTIWATT_11(11, 6, 5),
|
||||
// Multiwatt-15: 8 pins on left, 7 pins on right (staggered)
|
||||
MULTIWATT_15(15, 8, 7);
|
||||
|
||||
private final int pinCount;
|
||||
private final int pinsLeft;
|
||||
private final int pinsRight;
|
||||
|
||||
MultiwattType(int pinCount, int pinsLeft, int pinsRight) {
|
||||
this.pinCount = pinCount;
|
||||
this.pinsLeft = pinsLeft;
|
||||
this.pinsRight = pinsRight;
|
||||
}
|
||||
|
||||
public int getPinCount() {
|
||||
return pinCount;
|
||||
}
|
||||
|
||||
public int getPinsLeft() {
|
||||
return pinsLeft;
|
||||
}
|
||||
|
||||
public int getPinsRight() {
|
||||
return pinsRight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Multiwatt " + pinCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ package org.diylc.components.semiconductors;
|
||||
|
||||
public enum TransistorPinout {
|
||||
|
||||
BJT_EBC, BJT_CBE, BJT_BCE, JFET_DSG, JFET_GSD, JFET_DGS, JFET_SGD, JFET_GDS, MOSFET_DSG, MOSFET_GSD, MOSFET_DGS, MOSFET_SGD, MOSFET_GDS, REGULATOR_IGO, REGULATOR_OGI, REGULATOR_GOI, REGULATOR_AOI, REGULATOR_GIO;
|
||||
BJT_EBC, BJT_CBE, BJT_BCE, BJT_ECB, JFET_DSG, JFET_GSD, JFET_DGS, JFET_SGD, JFET_GDS, MOSFET_DSG, MOSFET_GSD, MOSFET_DGS, MOSFET_SGD, MOSFET_GDS, REGULATOR_IGO, REGULATOR_OGI, REGULATOR_GOI, REGULATOR_AOI, REGULATOR_GIO;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
@@ -36,7 +36,7 @@ import org.diylc.core.annotations.ComponentDescriptor;
|
||||
|
||||
@ComponentDescriptor(name = "Ellipse", author = "Branislav Stojkovic", category = "Shapes", instanceNamePrefix = "ELL",
|
||||
description = "Elliptical area", zOrder = IDIYComponent.COMPONENT, flexibleZOrder = true,
|
||||
bomPolicy = BomPolicy.SHOW_ALL_NAMES, transformer = SimpleComponentTransformer.class)
|
||||
bomPolicy = BomPolicy.NEVER_SHOW, transformer = SimpleComponentTransformer.class)
|
||||
public class Ellipse extends AbstractShapeWithDimensions {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -40,7 +40,7 @@ import org.diylc.core.annotations.EditableProperty;
|
||||
|
||||
@ComponentDescriptor(name = "Polygon", author = "Branislav Stojkovic", category = "Shapes",
|
||||
instanceNamePrefix = "POLY", description = "Polygonal area", zOrder = IDIYComponent.COMPONENT,
|
||||
flexibleZOrder = true, bomPolicy = BomPolicy.SHOW_ALL_NAMES,
|
||||
flexibleZOrder = true, bomPolicy = BomPolicy.NEVER_SHOW,
|
||||
transformer = SimpleComponentTransformer.class)
|
||||
public class Polygon extends AbstractShape {
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@ import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
|
||||
@ComponentDescriptor(name = "Rectangle", author = "Branislav Stojkovic", category = "Shapes",
|
||||
instanceNamePrefix = "RECT", description = "Ractangular area, with or withouth rounded edges",
|
||||
zOrder = IDIYComponent.COMPONENT, flexibleZOrder = true, bomPolicy = BomPolicy.SHOW_ALL_NAMES,
|
||||
instanceNamePrefix = "RECT", description = "Rectangular area, with or without rounded edges",
|
||||
zOrder = IDIYComponent.COMPONENT, flexibleZOrder = true, bomPolicy = BomPolicy.NEVER_SHOW,
|
||||
transformer = SimpleComponentTransformer.class)
|
||||
public class Rectangle extends AbstractShapeWithDimensions {
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ public class TapeMeasure extends AbstractLeadedComponent<String> {
|
||||
g2d.setColor(COLOR);
|
||||
g2d.drawLine(1, height - 2, width / 4, height * 3 / 4 - 1);
|
||||
g2d.drawLine(width * 3 / 4, height / 4 - 1, width - 2, 1);
|
||||
g2d.setBackground(Color.black);
|
||||
g2d.setColor(Color.black);
|
||||
g2d.fillPolygon(new int[] {1, 1, 4}, new int[] {height - 4, height - 1, height - 1}, 3);
|
||||
g2d.fillPolygon(new int[] {width - 4, width - 1, width - 1}, new int[] {1, 1, 4}, 3);
|
||||
g2d.rotate(-Math.PI / 4, width / 2, height / 2);
|
||||
@@ -174,6 +174,7 @@ public class TapeMeasure extends AbstractLeadedComponent<String> {
|
||||
g2d.rotate(theta);
|
||||
|
||||
g2d.setFont(font);
|
||||
g2d.setColor(componentState == ComponentState.SELECTED ? SELECTION_COLOR : labelColor);
|
||||
g2d.drawString(text, x, y);
|
||||
}
|
||||
|
||||
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
package org.diylc.components.transform;
|
||||
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
import org.diylc.common.IComponentTransformer;
|
||||
import org.diylc.common.Orientation;
|
||||
import org.diylc.components.passive.BoxTrimmer;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
|
||||
public class BoxTrimmerTransformer implements IComponentTransformer {
|
||||
|
||||
private enum Corner { TL, TR, BL, BR }
|
||||
|
||||
// ---------- public API ----------
|
||||
|
||||
@Override
|
||||
public boolean mirroringChangesCircuit() {
|
||||
// Pins are by index (0=CCW,1=Wiper,2=CW); mirror doesn't swap indices.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canMirror(IDIYComponent<?> component) {
|
||||
return component instanceof BoxTrimmer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRotate(IDIYComponent<?> component) {
|
||||
return component instanceof BoxTrimmer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rotate(IDIYComponent<?> component, Point2D center, int direction) {
|
||||
if (!(component instanceof BoxTrimmer t)) return;
|
||||
if (direction != 1 && direction != -1) return;
|
||||
|
||||
// capture old state
|
||||
Orientation oldO = t.getOrientation();
|
||||
Corner oldCorner = flagsToCorner(oldO, t.getScrewFlippedHorizontally(), t.getScrewFlippedVertically());
|
||||
|
||||
// rotate CP0 about center (clockwise for direction=+1)
|
||||
Point2D p0 = t.getControlPoint(0);
|
||||
AffineTransform rot = AffineTransform.getRotateInstance((Math.PI / 2) * -direction, center.getX(), center.getY());
|
||||
Point2D p0r = new Point2D.Double();
|
||||
rot.transform(p0, p0r);
|
||||
|
||||
// compute new orientation and new corner
|
||||
Orientation newO = rotateOrientation(oldO, direction);
|
||||
Corner newCorner = (direction == 1) ? rotateCornerCW(oldCorner) : rotateCornerCCW(oldCorner);
|
||||
|
||||
// decode corner back to flags in new orientation
|
||||
boolean[] hv = cornerToFlags(newO, newCorner);
|
||||
|
||||
// apply (CP0 first, then orientation regenerates pins)
|
||||
t.setControlPoint(p0r, 0);
|
||||
t.setOrientation(newO);
|
||||
t.setScrewFlippedHorizontally(hv[0]);
|
||||
t.setScrewFlippedVertically(hv[1]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mirror(IDIYComponent<?> component, Point2D center, int direction) {
|
||||
if (!(component instanceof BoxTrimmer t)) return;
|
||||
|
||||
Orientation oldO = t.getOrientation();
|
||||
Corner oldCorner = flagsToCorner(oldO, t.getScrewFlippedHorizontally(), t.getScrewFlippedVertically());
|
||||
|
||||
Point2D p0 = t.getControlPoint(0);
|
||||
Point2D p0m;
|
||||
|
||||
Orientation newO;
|
||||
Corner newCorner;
|
||||
|
||||
if (direction == IComponentTransformer.HORIZONTAL) {
|
||||
// mirror left-right
|
||||
p0m = new Point2D.Double(2 * center.getX() - p0.getX(), p0.getY());
|
||||
newO = mirrorOrientationHorizontal(oldO);
|
||||
newCorner = mirrorCornerHorizontal(oldCorner);
|
||||
} else {
|
||||
// mirror top-bottom
|
||||
p0m = new Point2D.Double(p0.getX(), 2 * center.getY() - p0.getY());
|
||||
newO = mirrorOrientationVertical(oldO);
|
||||
newCorner = mirrorCornerVertical(oldCorner);
|
||||
}
|
||||
|
||||
boolean[] hv = cornerToFlags(newO, newCorner);
|
||||
|
||||
t.setControlPoint(p0m, 0);
|
||||
t.setOrientation(newO);
|
||||
t.setScrewFlippedHorizontally(hv[0]);
|
||||
t.setScrewFlippedVertically(hv[1]);
|
||||
}
|
||||
|
||||
// ---------- orientation index + transforms (table-driven) ----------
|
||||
|
||||
private static int oIdx(Orientation o) {
|
||||
return switch (o) {
|
||||
case DEFAULT -> 0;
|
||||
case _90 -> 1;
|
||||
case _180 -> 2;
|
||||
case _270 -> 3;
|
||||
};
|
||||
}
|
||||
|
||||
// rotate orientation clockwise / counterclockwise
|
||||
// index order: DEFAULT, _90, _180, _270
|
||||
private static final Orientation[] ORIENTATIONS = {
|
||||
Orientation.DEFAULT, Orientation._90, Orientation._180, Orientation._270
|
||||
};
|
||||
|
||||
private static Orientation rotateOrientation(Orientation o, int direction) {
|
||||
int i = oIdx(o);
|
||||
int ni = (direction == 1) ? (i + 1) : (i + 3); // -1 == +3 mod 4
|
||||
return ORIENTATIONS[ni & 3];
|
||||
}
|
||||
|
||||
// mirror tables: new orientation index after mirror
|
||||
private static final int[] ORIENT_MIRROR_H = { 2, 1, 0, 3 }; // DEFAULT<->_180, _90 stays, _270 stays
|
||||
private static final int[] ORIENT_MIRROR_V = { 0, 3, 2, 1 }; // _90<->_270, DEFAULT stays, _180 stays
|
||||
|
||||
private static Orientation mirrorOrientationHorizontal(Orientation o) {
|
||||
return ORIENTATIONS[ORIENT_MIRROR_H[oIdx(o)]];
|
||||
}
|
||||
|
||||
private static Orientation mirrorOrientationVertical(Orientation o) {
|
||||
return ORIENTATIONS[ORIENT_MIRROR_V[oIdx(o)]];
|
||||
}
|
||||
|
||||
// ---------- corner mapping (table-driven, matches BoxTrimmer.draw()) ----------
|
||||
|
||||
/**
|
||||
* CORNER_BY_FLAGS[orientationIndex][hvIndex] -> Corner
|
||||
* hvIndex encodes (h,v) as:
|
||||
* bit0 = h (0/1)
|
||||
* bit1 = v (0/1)
|
||||
* so:
|
||||
* 0: h=0,v=0
|
||||
* 1: h=1,v=0
|
||||
* 2: h=0,v=1
|
||||
* 3: h=1,v=1
|
||||
*
|
||||
* This matches draw() logic:
|
||||
* - DEFAULT, _90: normal (h: left->right, v: bottom->top)
|
||||
* - _180: horizontal meaning inverted
|
||||
* - _270: vertical meaning inverted
|
||||
*/
|
||||
private static final Corner[][] CORNER_BY_FLAGS = {
|
||||
// DEFAULT
|
||||
{ Corner.BL, Corner.BR, Corner.TL, Corner.TR },
|
||||
// _90
|
||||
{ Corner.BL, Corner.BR, Corner.TL, Corner.TR },
|
||||
// _180
|
||||
{ Corner.BR, Corner.BL, Corner.TR, Corner.TL },
|
||||
// _270
|
||||
{ Corner.TL, Corner.TR, Corner.BL, Corner.BR }
|
||||
};
|
||||
|
||||
private static final boolean[][][] FLAGS_BY_CORNER = buildFlagsByCorner();
|
||||
|
||||
private static boolean[][][] buildFlagsByCorner() {
|
||||
boolean[][][] out = new boolean[4][4][2]; // [ori][corner][h/v]
|
||||
for (int oi = 0; oi < 4; oi++) {
|
||||
for (int hv = 0; hv < 4; hv++) {
|
||||
Corner c = CORNER_BY_FLAGS[oi][hv];
|
||||
boolean h = (hv & 1) != 0;
|
||||
boolean v = (hv & 2) != 0;
|
||||
out[oi][c.ordinal()][0] = h;
|
||||
out[oi][c.ordinal()][1] = v;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static Corner flagsToCorner(Orientation o, boolean h, boolean v) {
|
||||
int oi = oIdx(o);
|
||||
int hv = (h ? 1 : 0) | (v ? 2 : 0);
|
||||
return CORNER_BY_FLAGS[oi][hv];
|
||||
}
|
||||
|
||||
private static boolean[] cornerToFlags(Orientation o, Corner c) {
|
||||
int oi = oIdx(o);
|
||||
boolean h = FLAGS_BY_CORNER[oi][c.ordinal()][0];
|
||||
boolean v = FLAGS_BY_CORNER[oi][c.ordinal()][1];
|
||||
return new boolean[] { h, v };
|
||||
}
|
||||
|
||||
// ---------- corner transforms (table-driven) ----------
|
||||
|
||||
// ordinal order: TL=0, TR=1, BL=2, BR=3
|
||||
private static final Corner[] CORNER_ROT_CW = {
|
||||
Corner.TR, // TL -> TR
|
||||
Corner.BR, // TR -> BR
|
||||
Corner.TL, // BL -> TL
|
||||
Corner.BL // BR -> BL
|
||||
};
|
||||
|
||||
private static final Corner[] CORNER_ROT_CCW = {
|
||||
Corner.BL, // TL -> BL
|
||||
Corner.TL, // TR -> TL
|
||||
Corner.BR, // BL -> BR
|
||||
Corner.TR // BR -> TR
|
||||
};
|
||||
|
||||
private static final Corner[] CORNER_MIRROR_H = {
|
||||
Corner.TR, // TL <-> TR
|
||||
Corner.TL,
|
||||
Corner.BR, // BL <-> BR
|
||||
Corner.BL
|
||||
};
|
||||
|
||||
private static final Corner[] CORNER_MIRROR_V = {
|
||||
Corner.BL, // TL <-> BL
|
||||
Corner.BR, // TR <-> BR
|
||||
Corner.TL,
|
||||
Corner.TR
|
||||
};
|
||||
|
||||
private static Corner rotateCornerCW(Corner c) {
|
||||
return CORNER_ROT_CW[c.ordinal()];
|
||||
}
|
||||
|
||||
private static Corner rotateCornerCCW(Corner c) {
|
||||
return CORNER_ROT_CCW[c.ordinal()];
|
||||
}
|
||||
|
||||
private static Corner mirrorCornerHorizontal(Corner c) {
|
||||
return CORNER_MIRROR_H[c.ordinal()];
|
||||
}
|
||||
|
||||
private static Corner mirrorCornerVertical(Corner c) {
|
||||
return CORNER_MIRROR_V[c.ordinal()];
|
||||
}
|
||||
}
|
||||
+47
-10
@@ -27,18 +27,19 @@ import java.awt.geom.Point2D;
|
||||
import org.diylc.common.IComponentTransformer;
|
||||
import org.diylc.common.Orientation;
|
||||
import org.diylc.components.electromechanical.CliffJack1_4;
|
||||
import org.diylc.components.electromechanical.CliffJack1_8;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
|
||||
public class CliffJackTransformer implements IComponentTransformer {
|
||||
|
||||
@Override
|
||||
public boolean canRotate(IDIYComponent<?> component) {
|
||||
return component.getClass().equals(CliffJack1_4.class);
|
||||
return component.getClass().equals(CliffJack1_4.class) || component.getClass().equals(CliffJack1_8.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canMirror(IDIYComponent<?> component) {
|
||||
return component.getClass().equals(CliffJack1_4.class);
|
||||
return component.getClass().equals(CliffJack1_4.class) || component.getClass().equals(CliffJack1_8.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,8 +56,15 @@ public class CliffJackTransformer implements IComponentTransformer {
|
||||
component.setControlPoint(p, index);
|
||||
}
|
||||
|
||||
CliffJack1_4 jack = (CliffJack1_4) component;
|
||||
Orientation o = jack.getOrientation();
|
||||
Orientation o;
|
||||
if (component instanceof CliffJack1_4) {
|
||||
o = ((CliffJack1_4) component).getOrientation();
|
||||
} else if (component instanceof CliffJack1_8) {
|
||||
o = ((CliffJack1_8) component).getOrientation();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
int oValue = o.ordinal();
|
||||
oValue += direction;
|
||||
if (oValue < 0)
|
||||
@@ -64,18 +72,33 @@ public class CliffJackTransformer implements IComponentTransformer {
|
||||
if (oValue >= Orientation.values().length)
|
||||
oValue = 0;
|
||||
o = Orientation.values()[oValue];
|
||||
jack.setOrientation(o);
|
||||
|
||||
if (component instanceof CliffJack1_4) {
|
||||
((CliffJack1_4) component).setOrientation(o);
|
||||
} else if (component instanceof CliffJack1_8) {
|
||||
((CliffJack1_8) component).setOrientation(o);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mirror(IDIYComponent<?> component, Point2D center, int direction) {
|
||||
CliffJack1_4 ic = (CliffJack1_4) component;
|
||||
if (!(component instanceof CliffJack1_4) && !(component instanceof CliffJack1_8)) {
|
||||
return;
|
||||
}
|
||||
|
||||
IDIYComponent<?> ic = component;
|
||||
|
||||
if (direction == IComponentTransformer.HORIZONTAL) {
|
||||
double dx = 2 * (center.getX() - ic.getControlPoint(1).getX());
|
||||
double dy = 0;
|
||||
|
||||
Orientation o = ic.getOrientation();
|
||||
Orientation o;
|
||||
if (component instanceof CliffJack1_4) {
|
||||
o = ((CliffJack1_4) component).getOrientation();
|
||||
} else {
|
||||
o = ((CliffJack1_8) component).getOrientation();
|
||||
}
|
||||
|
||||
switch (o) {
|
||||
case DEFAULT:
|
||||
dy += (ic.getControlPoint(0).getY() - ic.getControlPoint(ic.getControlPointCount() - 1).getY());
|
||||
@@ -98,12 +121,22 @@ public class CliffJackTransformer implements IComponentTransformer {
|
||||
ic.setControlPoint(new Point2D.Double(p.getX() + dx, p.getY() + dy), i);
|
||||
}
|
||||
|
||||
ic.setOrientation(o);
|
||||
if (component instanceof CliffJack1_4) {
|
||||
((CliffJack1_4) component).setOrientation(o);
|
||||
} else {
|
||||
((CliffJack1_8) component).setOrientation(o);
|
||||
}
|
||||
} else {
|
||||
double dx = 0;
|
||||
double dy = 2 * (center.getY() - ic.getControlPoint(1).getY());
|
||||
|
||||
Orientation o = ic.getOrientation();
|
||||
Orientation o;
|
||||
if (component instanceof CliffJack1_4) {
|
||||
o = ((CliffJack1_4) component).getOrientation();
|
||||
} else {
|
||||
o = ((CliffJack1_8) component).getOrientation();
|
||||
}
|
||||
|
||||
switch (o) {
|
||||
case DEFAULT:
|
||||
// dx += ic.getControlPoint(0).getX() - ic.getControlPoint(ic.getControlPointCount() - 1).getX();
|
||||
@@ -128,7 +161,11 @@ public class CliffJackTransformer implements IComponentTransformer {
|
||||
ic.setControlPoint(new Point2D.Double(p.getX() + dx, p.getY() + dy), i);
|
||||
}
|
||||
|
||||
ic.setOrientation(o);
|
||||
if (component instanceof CliffJack1_4) {
|
||||
((CliffJack1_4) component).setOrientation(o);
|
||||
} else {
|
||||
((CliffJack1_8) component).setOrientation(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
|
||||
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.components.transform;
|
||||
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
import org.diylc.common.IComponentTransformer;
|
||||
import org.diylc.components.electromechanical.OpenJack1_4;
|
||||
import org.diylc.components.electromechanical.OpenJack1_8;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
|
||||
public class OpenJackTransformer implements IComponentTransformer {
|
||||
|
||||
@Override
|
||||
public boolean canRotate(IDIYComponent<?> component) {
|
||||
return component.getClass().equals(OpenJack1_4.class) || component.getClass().equals(OpenJack1_8.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canMirror(IDIYComponent<?> component) {
|
||||
return component.getClass().equals(OpenJack1_4.class) || component.getClass().equals(OpenJack1_8.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mirroringChangesCircuit() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rotate(IDIYComponent<?> component, Point2D center, int direction) {
|
||||
AffineTransform rotate = AffineTransform.getRotateInstance(Math.PI / 2 * direction, center.getX(), center.getY());
|
||||
for (int index = 0; index < component.getControlPointCount(); index++) {
|
||||
Point2D p = new Point2D.Double();
|
||||
rotate.transform(component.getControlPoint(index), p);
|
||||
component.setControlPoint(p, index);
|
||||
}
|
||||
|
||||
Integer angle;
|
||||
if (component instanceof OpenJack1_4) {
|
||||
angle = ((OpenJack1_4) component).getAngle();
|
||||
} else if (component instanceof OpenJack1_8) {
|
||||
angle = ((OpenJack1_8) component).getAngle();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (angle == null) {
|
||||
angle = 0;
|
||||
}
|
||||
|
||||
int newAngle = angle + direction * 90;
|
||||
// Normalize to 0-360 range
|
||||
while (newAngle < 0) {
|
||||
newAngle += 360;
|
||||
}
|
||||
while (newAngle >= 360) {
|
||||
newAngle -= 360;
|
||||
}
|
||||
|
||||
if (component instanceof OpenJack1_4) {
|
||||
((OpenJack1_4) component).setAngle(newAngle);
|
||||
} else if (component instanceof OpenJack1_8) {
|
||||
((OpenJack1_8) component).setAngle(newAngle);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mirror(IDIYComponent<?> component, Point2D center, int direction) {
|
||||
if (!(component instanceof OpenJack1_4) && !(component instanceof OpenJack1_8)) {
|
||||
return;
|
||||
}
|
||||
|
||||
IDIYComponent<?> ic = component;
|
||||
|
||||
if (direction == IComponentTransformer.HORIZONTAL) {
|
||||
// Mirror control points horizontally
|
||||
for (int i = 0; i < ic.getControlPointCount(); i++) {
|
||||
Point2D p = ic.getControlPoint(i);
|
||||
double dx = 2 * (center.getX() - p.getX());
|
||||
ic.setControlPoint(new Point2D.Double(p.getX() + dx, p.getY()), i);
|
||||
}
|
||||
|
||||
Integer angle;
|
||||
if (component instanceof OpenJack1_4) {
|
||||
angle = ((OpenJack1_4) component).getAngle();
|
||||
} else {
|
||||
angle = ((OpenJack1_8) component).getAngle();
|
||||
}
|
||||
|
||||
if (angle == null) {
|
||||
angle = 0;
|
||||
}
|
||||
|
||||
// Mirror horizontally: reflect angle across vertical axis
|
||||
int newAngle = 360 - angle;
|
||||
while (newAngle >= 360) {
|
||||
newAngle -= 360;
|
||||
}
|
||||
|
||||
if (component instanceof OpenJack1_4) {
|
||||
((OpenJack1_4) component).setAngle(newAngle);
|
||||
} else {
|
||||
((OpenJack1_8) component).setAngle(newAngle);
|
||||
}
|
||||
} else {
|
||||
// Mirror control points vertically
|
||||
for (int i = 0; i < ic.getControlPointCount(); i++) {
|
||||
Point2D p = ic.getControlPoint(i);
|
||||
double dy = 2 * (center.getY() - p.getY());
|
||||
ic.setControlPoint(new Point2D.Double(p.getX(), p.getY() + dy), i);
|
||||
}
|
||||
|
||||
Integer angle;
|
||||
if (component instanceof OpenJack1_4) {
|
||||
angle = ((OpenJack1_4) component).getAngle();
|
||||
} else {
|
||||
angle = ((OpenJack1_8) component).getAngle();
|
||||
}
|
||||
|
||||
if (angle == null) {
|
||||
angle = 0;
|
||||
}
|
||||
|
||||
// Mirror vertically: reflect angle across horizontal axis
|
||||
int newAngle = 180 - angle;
|
||||
while (newAngle < 0) {
|
||||
newAngle += 360;
|
||||
}
|
||||
while (newAngle >= 360) {
|
||||
newAngle -= 360;
|
||||
}
|
||||
|
||||
if (component instanceof OpenJack1_4) {
|
||||
((OpenJack1_4) component).setAngle(newAngle);
|
||||
} else {
|
||||
((OpenJack1_8) component).setAngle(newAngle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
@@ -70,6 +70,20 @@ public class PinHeaderTransformer implements IComponentTransformer {
|
||||
@Override
|
||||
public void mirror(IDIYComponent<?> component, Point2D center, int direction) {
|
||||
PinHeader header = (PinHeader) component;
|
||||
int controlPointCount = header.getControlPointCount();
|
||||
|
||||
// Handle single-pin case
|
||||
if (controlPointCount == 1) {
|
||||
Point2D p = header.getControlPoint(0);
|
||||
if (direction == IComponentTransformer.HORIZONTAL) {
|
||||
double dx = 2 * (center.getX() - p.getX());
|
||||
header.setControlPoint(new Point2D.Double(p.getX() + dx, p.getY()), 0);
|
||||
} else {
|
||||
double dy = 2 * (center.getY() - p.getY());
|
||||
header.setControlPoint(new Point2D.Double(p.getX(), p.getY() + dy), 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (direction == IComponentTransformer.HORIZONTAL) {
|
||||
double dx = 2 * (center.getX() - header.getControlPoint(1).getX());
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
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.awt;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class StringUtilsTest {
|
||||
|
||||
private FontMetrics fontMetrics;
|
||||
private Graphics2D g2d;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
|
||||
g2d = image.createGraphics();
|
||||
g2d.setFont(new Font("Arial", Font.PLAIN, 12));
|
||||
fontMetrics = g2d.getFontMetrics();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrapWithNewline() {
|
||||
String text = "Line 1\nLine 2\nLine 3";
|
||||
int maxWidth = 1000; // Large enough to not wrap
|
||||
|
||||
List<String> lines = StringUtils.wrap(text, fontMetrics, maxWidth);
|
||||
|
||||
assertEquals("Should split into 3 lines", 3, lines.size());
|
||||
assertEquals("Line 1", lines.get(0));
|
||||
assertEquals("Line 2", lines.get(1));
|
||||
assertEquals("Line 3", lines.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrapWithCarriageReturn() {
|
||||
String text = "Line 1\rLine 2\rLine 3";
|
||||
int maxWidth = 1000;
|
||||
|
||||
List<String> lines = StringUtils.wrap(text, fontMetrics, maxWidth);
|
||||
|
||||
assertEquals("Should split into 3 lines", 3, lines.size());
|
||||
assertEquals("Line 1", lines.get(0));
|
||||
assertEquals("Line 2", lines.get(1));
|
||||
assertEquals("Line 3", lines.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrapWithCarriageReturnLineFeed() {
|
||||
String text = "Line 1\r\nLine 2\r\nLine 3";
|
||||
int maxWidth = 1000;
|
||||
|
||||
List<String> lines = StringUtils.wrap(text, fontMetrics, maxWidth);
|
||||
|
||||
assertEquals("Should split into 3 lines", 3, lines.size());
|
||||
assertEquals("Line 1", lines.get(0));
|
||||
assertEquals("Line 2", lines.get(1));
|
||||
assertEquals("Line 3", lines.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrapWithMixedNewlines() {
|
||||
String text = "Line 1\nLine 2\rLine 3\r\nLine 4";
|
||||
int maxWidth = 1000;
|
||||
|
||||
List<String> lines = StringUtils.wrap(text, fontMetrics, maxWidth);
|
||||
|
||||
assertEquals("Should split into 4 lines", 4, lines.size());
|
||||
assertEquals("Line 1", lines.get(0));
|
||||
assertEquals("Line 2", lines.get(1));
|
||||
assertEquals("Line 3", lines.get(2));
|
||||
assertEquals("Line 4", lines.get(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrapWithNewlineAndBrTag() {
|
||||
String text = "Line 1\nLine 2<br>Line 3";
|
||||
int maxWidth = 1000;
|
||||
|
||||
List<String> lines = StringUtils.wrap(text, fontMetrics, maxWidth);
|
||||
|
||||
assertEquals("Should split into 3 lines", 3, lines.size());
|
||||
assertEquals("Line 1", lines.get(0));
|
||||
assertEquals("Line 2", lines.get(1));
|
||||
assertEquals("Line 3", lines.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrapWithNewlineAndWrapping() {
|
||||
String text = "First line\nSecond line with very long text that should wrap";
|
||||
int maxWidth = fontMetrics.stringWidth("Second line with very long text that should"); // Force wrapping
|
||||
|
||||
List<String> lines = StringUtils.wrap(text, fontMetrics, maxWidth);
|
||||
|
||||
assertTrue("Should have at least 2 lines (newline + wrapped)", lines.size() >= 2);
|
||||
assertEquals("First line", lines.get(0));
|
||||
// The second line should be wrapped, so we should have more lines
|
||||
assertTrue("Second line should be wrapped", lines.size() >= 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrapWithEmptyLines() {
|
||||
String text = "Line 1\n\nLine 3";
|
||||
int maxWidth = 1000;
|
||||
|
||||
List<String> lines = StringUtils.wrap(text, fontMetrics, maxWidth);
|
||||
|
||||
assertEquals("Should split into 3 lines including empty line", 3, lines.size());
|
||||
assertEquals("Line 1", lines.get(0));
|
||||
assertEquals("", lines.get(1));
|
||||
assertEquals("Line 3", lines.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrapWithNewlineAtEnd() {
|
||||
String text = "Line 1\nLine 2\n";
|
||||
int maxWidth = 1000;
|
||||
|
||||
List<String> lines = StringUtils.wrap(text, fontMetrics, maxWidth);
|
||||
|
||||
assertEquals("Should split into 2 lines (no trailing empty line)", 2, lines.size());
|
||||
assertEquals("Line 1", lines.get(0));
|
||||
assertEquals("Line 2", lines.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrapWithNoNewlines() {
|
||||
String text = "This is a single line of text";
|
||||
int maxWidth = 1000;
|
||||
|
||||
List<String> lines = StringUtils.wrap(text, fontMetrics, maxWidth);
|
||||
|
||||
assertEquals("Should have 1 line", 1, lines.size());
|
||||
assertEquals("This is a single line of text", lines.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrapWithBrTagOnly() {
|
||||
String text = "Line 1<br>Line 2<br>Line 3";
|
||||
int maxWidth = 1000;
|
||||
|
||||
List<String> lines = StringUtils.wrap(text, fontMetrics, maxWidth);
|
||||
|
||||
assertEquals("Should split into 3 lines", 3, lines.size());
|
||||
assertEquals("Line 1", lines.get(0));
|
||||
assertEquals("Line 2", lines.get(1));
|
||||
assertEquals("Line 3", lines.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrapWithNewlineAndBrTagMixed() {
|
||||
String text = "Line 1\nLine 2<br>Line 3\nLine 4<br>Line 5";
|
||||
int maxWidth = 1000;
|
||||
|
||||
List<String> lines = StringUtils.wrap(text, fontMetrics, maxWidth);
|
||||
|
||||
assertEquals("Should split into 5 lines", 5, lines.size());
|
||||
assertEquals("Line 1", lines.get(0));
|
||||
assertEquals("Line 2", lines.get(1));
|
||||
assertEquals("Line 3", lines.get(2));
|
||||
assertEquals("Line 4", lines.get(3));
|
||||
assertEquals("Line 5", lines.get(4));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrapWithMultipleConsecutiveNewlines() {
|
||||
String text = "Line 1\n\n\nLine 4";
|
||||
int maxWidth = 1000;
|
||||
|
||||
List<String> lines = StringUtils.wrap(text, fontMetrics, maxWidth);
|
||||
|
||||
assertEquals("Should split into 4 lines with 2 empty lines", 4, lines.size());
|
||||
assertEquals("Line 1", lines.get(0));
|
||||
assertEquals("", lines.get(1));
|
||||
assertEquals("", lines.get(2));
|
||||
assertEquals("Line 4", lines.get(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrapWithMultipleConsecutiveNewlinesAtStart() {
|
||||
String text = "\n\nLine 3";
|
||||
int maxWidth = 1000;
|
||||
|
||||
List<String> lines = StringUtils.wrap(text, fontMetrics, maxWidth);
|
||||
|
||||
assertEquals("Should split into 3 lines with 2 empty lines at start", 3, lines.size());
|
||||
assertEquals("", lines.get(0));
|
||||
assertEquals("", lines.get(1));
|
||||
assertEquals("Line 3", lines.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrapWithMultipleConsecutiveNewlinesAtEnd() {
|
||||
String text = "Line 1\n\n\n";
|
||||
int maxWidth = 1000;
|
||||
|
||||
List<String> lines = StringUtils.wrap(text, fontMetrics, maxWidth);
|
||||
|
||||
// Trailing newlines: the last newline at end doesn't create an empty line
|
||||
// So \n\n\n at end creates 2 empty lines (from first 2 newlines)
|
||||
assertEquals("Should split into 3 lines (2 empty from trailing newlines)", 3, lines.size());
|
||||
assertEquals("Line 1", lines.get(0));
|
||||
assertEquals("", lines.get(1));
|
||||
assertEquals("", lines.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrapWithMultipleConsecutiveNewlinesMixed() {
|
||||
String text = "Line 1\n\nLine 3\n\n\nLine 6";
|
||||
int maxWidth = 1000;
|
||||
|
||||
List<String> lines = StringUtils.wrap(text, fontMetrics, maxWidth);
|
||||
|
||||
assertEquals("Should split into 6 lines", 6, lines.size());
|
||||
assertEquals("Line 1", lines.get(0));
|
||||
assertEquals("", lines.get(1));
|
||||
assertEquals("Line 3", lines.get(2));
|
||||
assertEquals("", lines.get(3));
|
||||
assertEquals("", lines.get(4));
|
||||
assertEquals("Line 6", lines.get(5));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.diylc.netlist;
|
||||
|
||||
import org.diylc.components.guitar.Freeway3x3_05;
|
||||
import org.junit.Test;
|
||||
|
||||
public class Freeway3x3_05Tests extends AbstractSwitchTests {
|
||||
|
||||
@Test
|
||||
public void testFreeway3x3_05() {
|
||||
Freeway3x3_05 sw = new Freeway3x3_05();
|
||||
|
||||
String[] validCombinations = new String[]{
|
||||
"0,0,6", "0,6,11", "0,0,11", "0,17,21", "0,21,22", "0,17,22",
|
||||
"1,1,6", "1,6,12", "1,1,12", "1,18,21", "1,21,23", "1,18,23",
|
||||
"2,2,6", "2,6,13", "2,2,13", "2,19,21", "2,21,24", "2,19,24",
|
||||
"3,5,6", "3,6,10", "3,5,10", "3,16,21", "3,21,27", "3,16,27",
|
||||
"4,4,6", "4,6,9", "4,4,9", "4,15,21", "4,21,26", "4,15,26",
|
||||
"5,3,6", "5,6,8", "5,3,8", "5,14,21", "5,21,25", "5,14,25",
|
||||
};
|
||||
|
||||
testSwitch(sw, validCombinations);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,117 @@
|
||||
package org.diylc.netlist;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.diylc.components.guitar.LPSwitch;
|
||||
import org.diylc.components.guitar.LPSwitch.MiddleTerminalConfiguration;
|
||||
import org.diylc.common.Orientation;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
|
||||
public class LPSwitchTests extends AbstractSwitchTests {
|
||||
|
||||
@Test
|
||||
public void testLPSwitch() {
|
||||
public void testLPSwitchConnected() {
|
||||
LPSwitch lpSwitch = new LPSwitch();
|
||||
// Default configuration is Connected
|
||||
assertEquals(MiddleTerminalConfiguration.Connected, lpSwitch.getMiddleTerminalConfiguration());
|
||||
// Connected has 4 control points (0, 1, 2, 3)
|
||||
assertEquals(4, ((IDIYComponent<?>) lpSwitch).getControlPointCount());
|
||||
// Position 0 (Treble): 1-2 connected
|
||||
// Position 1 (Middle): 1-2, 1-3, 2-3 connected (all terminals > 0 connected)
|
||||
// Position 2 (Rhythm): 2-3 connected
|
||||
String[] validCombinations = new String[] {"0,1,2", "1,1,2", "1,2,3", "1,1,3", "2,2,3"};
|
||||
testSwitch(lpSwitch, validCombinations);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLPSwitchDisconnected() {
|
||||
LPSwitch lpSwitch = new LPSwitch();
|
||||
lpSwitch.setMiddleTerminalConfiguration(MiddleTerminalConfiguration.Disconnected);
|
||||
assertEquals(MiddleTerminalConfiguration.Disconnected, lpSwitch.getMiddleTerminalConfiguration());
|
||||
// Disconnected has 5 control points (0, 1, 2, 3, 4)
|
||||
assertEquals(5, ((IDIYComponent<?>) lpSwitch).getControlPointCount());
|
||||
// Position 0 (Treble): 1-2 connected
|
||||
// Position 1 (Middle): 1-2 and 3-4 connected (both pairs)
|
||||
// Position 2 (Rhythm): 3-4 connected
|
||||
String[] validCombinations = new String[] {"0,1,2", "1,1,2", "1,3,4", "2,3,4"};
|
||||
testSwitch(lpSwitch, validCombinations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testControlPointCountChange() {
|
||||
LPSwitch lpSwitch = new LPSwitch();
|
||||
// Start with Connected (4 points)
|
||||
assertEquals(4, ((IDIYComponent<?>) lpSwitch).getControlPointCount());
|
||||
|
||||
// Switch to Disconnected (5 points)
|
||||
lpSwitch.setMiddleTerminalConfiguration(MiddleTerminalConfiguration.Disconnected);
|
||||
assertEquals(5, ((IDIYComponent<?>) lpSwitch).getControlPointCount());
|
||||
|
||||
// Switch back to Connected (4 points)
|
||||
lpSwitch.setMiddleTerminalConfiguration(MiddleTerminalConfiguration.Connected);
|
||||
assertEquals(4, ((IDIYComponent<?>) lpSwitch).getControlPointCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPositionNames() {
|
||||
LPSwitch lpSwitch = new LPSwitch();
|
||||
assertEquals(3, lpSwitch.getPositionCount());
|
||||
assertEquals("Treble", lpSwitch.getPositionName(0));
|
||||
assertEquals("Middle", lpSwitch.getPositionName(1));
|
||||
assertEquals("Rhythm", lpSwitch.getPositionName(2));
|
||||
assertNull(lpSwitch.getPositionName(3)); // Invalid position
|
||||
assertNull(lpSwitch.getPositionName(-1)); // Invalid position
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrientationDoesNotAffectConnectivity() {
|
||||
LPSwitch lpSwitch = new LPSwitch();
|
||||
// Test with default Connected configuration
|
||||
String[] validCombinations = new String[] {"0,1,2", "1,1,2", "1,2,3", "1,1,3", "2,2,3"};
|
||||
|
||||
// Test all orientations - connectivity should remain the same
|
||||
for (Orientation orientation : Orientation.values()) {
|
||||
lpSwitch.setOrientation(orientation);
|
||||
assertEquals(orientation, lpSwitch.getOrientation());
|
||||
// Connectivity should be unchanged regardless of orientation
|
||||
testSwitch(lpSwitch, validCombinations);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrientationWithDisconnectedConfiguration() {
|
||||
LPSwitch lpSwitch = new LPSwitch();
|
||||
lpSwitch.setMiddleTerminalConfiguration(MiddleTerminalConfiguration.Disconnected);
|
||||
String[] validCombinations = new String[] {"0,1,2", "1,1,2", "1,3,4", "2,3,4"};
|
||||
|
||||
// Test all orientations with Disconnected configuration
|
||||
for (Orientation orientation : Orientation.values()) {
|
||||
lpSwitch.setOrientation(orientation);
|
||||
assertEquals(orientation, lpSwitch.getOrientation());
|
||||
// Connectivity should be unchanged regardless of orientation
|
||||
testSwitch(lpSwitch, validCombinations);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigurationSwitchPreservesConnectivity() {
|
||||
LPSwitch lpSwitch = new LPSwitch();
|
||||
|
||||
// Test Connected configuration
|
||||
lpSwitch.setMiddleTerminalConfiguration(MiddleTerminalConfiguration.Connected);
|
||||
String[] connectedCombinations = new String[] {"0,1,2", "1,1,2", "1,2,3", "1,1,3", "2,2,3"};
|
||||
testSwitch(lpSwitch, connectedCombinations);
|
||||
|
||||
// Switch to Disconnected
|
||||
lpSwitch.setMiddleTerminalConfiguration(MiddleTerminalConfiguration.Disconnected);
|
||||
String[] disconnectedCombinations = new String[] {"0,1,2", "1,1,2", "1,3,4", "2,3,4"};
|
||||
testSwitch(lpSwitch, disconnectedCombinations);
|
||||
|
||||
// Switch back to Connected
|
||||
lpSwitch.setMiddleTerminalConfiguration(MiddleTerminalConfiguration.Connected);
|
||||
testSwitch(lpSwitch, connectedCombinations);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.diylc.netlist;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.diylc.components.electromechanical.TactileMicroSwitch;
|
||||
import org.diylc.components.electromechanical.TactileMicroSwitch.PinPairingMode;
|
||||
|
||||
public class TactileMicroSwitchTests extends AbstractSwitchTests {
|
||||
|
||||
@Test
|
||||
public void testTactileMicroSwitchOpposite() {
|
||||
TactileMicroSwitch tactileSwitch = new TactileMicroSwitch();
|
||||
tactileSwitch.setPinPairingMode(PinPairingMode.Opposite);
|
||||
// Position 0 (Released): no connections
|
||||
// Position 1 (Pressed): pins on opposite sides (same row): 0-2 and 1-3
|
||||
String[] validCombinations = new String[] {
|
||||
"1,0,2", "1,2,0", // Position 1: 0-2 (both directions)
|
||||
"1,1,3", "1,3,1" // Position 1: 1-3 (both directions)
|
||||
};
|
||||
testSwitch(tactileSwitch, validCombinations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTactileMicroSwitchDiagonal() {
|
||||
TactileMicroSwitch tactileSwitch = new TactileMicroSwitch();
|
||||
tactileSwitch.setPinPairingMode(PinPairingMode.Diagonal);
|
||||
// Position 0 (Released): no connections
|
||||
// Position 1 (Pressed): diagonally opposite pins: 0-3 and 1-2
|
||||
String[] validCombinations = new String[] {
|
||||
"1,0,3", "1,3,0", // Position 1: 0-3 (both directions)
|
||||
"1,1,2", "1,2,1" // Position 1: 1-2 (both directions)
|
||||
};
|
||||
testSwitch(tactileSwitch, validCombinations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTactileMicroSwitchAdjacent() {
|
||||
TactileMicroSwitch tactileSwitch = new TactileMicroSwitch();
|
||||
tactileSwitch.setPinPairingMode(PinPairingMode.Adjacent);
|
||||
// Position 0 (Released): no connections
|
||||
// Position 1 (Pressed): pins next to each other (same column): 0-1 and 2-3
|
||||
String[] validCombinations = new String[] {
|
||||
"1,0,1", "1,1,0", // Position 1: 0-1 (both directions)
|
||||
"1,2,3", "1,3,2" // Position 1: 2-3 (both directions)
|
||||
};
|
||||
testSwitch(tactileSwitch, validCombinations);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.diylc</groupId>
|
||||
<artifactId>diylc-parent</artifactId>
|
||||
<version>5.7.0</version>
|
||||
<version>5.10.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>diylc-swing</artifactId>
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.diylc.serialization.ProjectFileManager;
|
||||
import org.diylc.swing.gui.MainFrame;
|
||||
import org.diylc.swing.gui.TemplateDialog;
|
||||
import org.diylc.swing.plugins.file.ProjectDrawingProvider;
|
||||
import org.diylc.utils.SwingUtils;
|
||||
|
||||
/**
|
||||
* Main class that runs DIYLC.
|
||||
@@ -137,6 +138,12 @@ public class DIYLCStarter {
|
||||
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
|
||||
// Fix tooltip colors for Linux systems where they may be unreadable
|
||||
SwingUtils.fixTooltipColors();
|
||||
|
||||
// Apply configurable font scaling from VM argument: -Dorg.diylc.fontScaleFactor=X
|
||||
org.diylc.utils.FontScalingUtils.applyFontScaling();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Could not set Look&Feel", e);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.diylc.swing.actions;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.Objects;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
@@ -26,7 +28,7 @@ public class FindAction extends AbstractAction {
|
||||
putValue(AbstractAction.NAME, "Find");
|
||||
putValue(AbstractAction.SMALL_ICON, IconLoader.SearchBox.getIcon());
|
||||
putValue(AbstractAction.ACCELERATOR_KEY,
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_F, ActionEvent.CTRL_MASK));
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_F, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -35,13 +37,13 @@ public class FindAction extends AbstractAction {
|
||||
FindDialog dialog = DialogFactory.getInstance().createFindDialog();
|
||||
dialog.setVisible(true);
|
||||
|
||||
if (dialog.getSelectedButtonCaption() == FindDialog.OK) {
|
||||
if (Objects.equals(dialog.getSelectedButtonCaption(), FindDialog.OK)) {
|
||||
String criteria = dialog.getCriteria();
|
||||
plugInPort.selectMatching(criteria);
|
||||
if (plugInPort.getSelectedComponents().size() == 0) {
|
||||
if (plugInPort.getSelectedComponents().isEmpty()) {
|
||||
swingUI.showMessage("No matching components found.", "Find",
|
||||
ISwingUI.INFORMATION_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public class CopyAction extends AbstractAction {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ActionFactory.LOG.info("Copy triggered");
|
||||
clipboard.setContents(ComponentTransferableFactory.getInstance()
|
||||
.build(plugInPort.getSelectedComponents(), plugInPort.getCurrentProject().getGroups()),
|
||||
.build(plugInPort.getSelectedComponents(), plugInPort.getCurrentProject().getGroupsEx()),
|
||||
clipboardOwner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public class CutAction extends AbstractAction {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ActionFactory.LOG.info("Cut triggered");
|
||||
clipboard.setContents(ComponentTransferableFactory.getInstance()
|
||||
.build(plugInPort.getSelectedComponents(), plugInPort.getCurrentProject().getGroups()),
|
||||
.build(plugInPort.getSelectedComponents(), plugInPort.getCurrentProject().getGroupsEx()),
|
||||
clipboardOwner);
|
||||
plugInPort.deleteSelectedComponents();
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public class NudgeAction extends AbstractAction {
|
||||
putValue(AbstractAction.NAME, "Nudge");
|
||||
putValue(AbstractAction.SMALL_ICON, IconLoader.FitToSize.getIcon());
|
||||
putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_Q,
|
||||
Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
|
||||
ActionEvent.ALT_MASK));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
-7
@@ -5,11 +5,7 @@ import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import javax.swing.AbstractAction;
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.swingframework.CheckBoxListDialog;
|
||||
@@ -69,7 +65,7 @@ public class ExportBlocksAction extends AbstractAction {
|
||||
|
||||
dialog.setVisible(true);
|
||||
|
||||
if (dialog.getSelectedButtonCaption() != "OK")
|
||||
if (!Objects.equals(dialog.getSelectedButtonCaption(), "OK"))
|
||||
return;
|
||||
|
||||
Object[] selected = dialog.getSelectedOptions();
|
||||
@@ -133,4 +129,4 @@ public class ExportBlocksAction extends AbstractAction {
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-11
@@ -5,13 +5,7 @@ import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.*;
|
||||
import javax.swing.AbstractAction;
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.swingframework.CheckBoxListDialog;
|
||||
@@ -73,7 +67,7 @@ public class ExportVariantsAction extends AbstractAction {
|
||||
ActionFactory.LOG.warn("Could not find type for: " + className);
|
||||
}
|
||||
|
||||
Collections.sort(types, new Comparator<ComponentType>() {
|
||||
types.sort(new Comparator<ComponentType>() {
|
||||
|
||||
@Override
|
||||
public int compare(ComponentType o1, ComponentType o2) {
|
||||
@@ -86,7 +80,7 @@ public class ExportVariantsAction extends AbstractAction {
|
||||
|
||||
dialog.setVisible(true);
|
||||
|
||||
if (dialog.getSelectedButtonCaption() != "OK")
|
||||
if (!Objects.equals(dialog.getSelectedButtonCaption(), "OK"))
|
||||
return;
|
||||
|
||||
Object[] selected = dialog.getSelectedOptions();
|
||||
@@ -131,7 +125,7 @@ public class ExportVariantsAction extends AbstractAction {
|
||||
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));
|
||||
ProjectFileManager.xStreamSerializer.toXML(variantPkg, out);
|
||||
out.close();
|
||||
ActionFactory.LOG.info("Exported variants succesfully");
|
||||
ActionFactory.LOG.info("Exported variants successfully");
|
||||
} catch (IOException e) {
|
||||
ActionFactory.LOG.error("Could not export variants", e);
|
||||
}
|
||||
@@ -153,4 +147,4 @@ public class ExportVariantsAction extends AbstractAction {
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,8 @@ public class ImportAction extends AbstractAction {
|
||||
}
|
||||
}, InMemoryConfigurationManager.getInstance());
|
||||
putValue(AbstractAction.NAME, "Import");
|
||||
putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_I,
|
||||
Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
|
||||
// putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_I,
|
||||
// Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
|
||||
putValue(AbstractAction.SMALL_ICON, IconLoader.ElementInto.getIcon());
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ public class ImportAction extends AbstractAction {
|
||||
// presenter
|
||||
plugInPort.pasteComponents(ComponentTransferableFactory.getInstance().build(
|
||||
presenter.getCurrentProject().getComponents(),
|
||||
presenter.getCurrentProject().getGroups()), false, false);
|
||||
presenter.getCurrentProject().getGroupsEx()), false, false);
|
||||
// Cleanup components in the temp presenter, don't need
|
||||
// them anymore
|
||||
presenter.selectAll(0);
|
||||
|
||||
+2
-2
@@ -113,7 +113,7 @@ public class ImportNetlistAction extends AbstractAction {
|
||||
if (!outputWarnings.isEmpty())
|
||||
ActionFactory.LOG.warn("Component creation produced warnings:\n"
|
||||
+ String.join("\n", outputWarnings));
|
||||
plugInPort.pasteComponents(new ComponentTransferable(components), false, false);
|
||||
plugInPort.pasteComponents(new ComponentTransferable(components, "Imported Netlist"), false, false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
swingUI.showMessage("Could not import netlist file: " + e.getMessage(), "Error",
|
||||
@@ -130,4 +130,4 @@ public class ImportNetlistAction extends AbstractAction {
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-1
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
package org.diylc.swing.plugins.canvas;
|
||||
|
||||
import java.awt.Cursor;
|
||||
import java.awt.dnd.DragGestureEvent;
|
||||
import java.awt.dnd.DragGestureListener;
|
||||
import java.awt.event.InputEvent;
|
||||
@@ -54,7 +55,17 @@ class CanvasDragGestureListener implements DragGestureListener {
|
||||
}
|
||||
canvasPanel.setClickInProgress(false);
|
||||
presenter.dragStarted(dge.getDragOrigin(), dge.getDragAction(), forceReSelection);
|
||||
dge.startDrag(presenter.getCursorAt(dge.getDragOrigin(), false, false, false), new EmptyTransferable(), new CanvasSourceListener(
|
||||
// Get the appropriate cursor from the presenter (e.g., HAND_CURSOR over components)
|
||||
// Preserve the cursor context (HAND_CURSOR, CROSSHAIR_CURSOR, etc.) for drag operations
|
||||
// On Linux, DEFAULT_CURSOR during drag can show "No" cursor, so use MOVE_CURSOR as fallback
|
||||
Cursor requestedCursor = presenter.getCursorAt(dge.getDragOrigin(), false, false, false);
|
||||
Cursor dragCursor = requestedCursor;
|
||||
// Only replace DEFAULT_CURSOR with MOVE_CURSOR on Linux to prevent "No" cursor issue
|
||||
// Preserve HAND_CURSOR, CROSSHAIR_CURSOR, etc. as they are appropriate for drag operations
|
||||
if (requestedCursor.getType() == Cursor.DEFAULT_CURSOR) {
|
||||
dragCursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR);
|
||||
}
|
||||
dge.startDrag(dragCursor, new EmptyTransferable(), new CanvasSourceListener(
|
||||
presenter));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -271,10 +274,21 @@ public class CanvasPlugin implements IPlugIn{
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
if (scrollPane.isMouseScrollMode())
|
||||
return;
|
||||
// if needed uncomment this code, it forces the cursor to refresh when it's stuck
|
||||
// canvasPanel.setCursor(null);
|
||||
canvasPanel.setCursor(plugInPort.getCursorAt(e.getPoint(), Utils.isMac() ? e.isMetaDown() : e.isControlDown(),
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -503,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();
|
||||
@@ -517,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;
|
||||
@@ -561,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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+31
-2
@@ -21,11 +21,16 @@
|
||||
*/
|
||||
package org.diylc.swing.plugins.canvas;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Point;
|
||||
import java.awt.dnd.DragSourceDragEvent;
|
||||
import java.awt.dnd.DragSourceDropEvent;
|
||||
import java.awt.dnd.DragSourceEvent;
|
||||
import java.awt.dnd.DragSourceListener;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import org.diylc.common.IPlugInPort;
|
||||
|
||||
@@ -50,13 +55,16 @@ class CanvasSourceListener implements DragSourceListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dragEnter(DragSourceDragEvent dsde) {}
|
||||
public void dragEnter(DragSourceDragEvent dsde) {
|
||||
updateCursor(dsde);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dragExit(DragSourceEvent dse) {}
|
||||
|
||||
@Override
|
||||
public void dragOver(DragSourceDragEvent dsde) {
|
||||
updateCursor(dsde);
|
||||
Point p = dsde.getDragSourceContext().getComponent().getMousePosition();
|
||||
if (p != null) {
|
||||
dsde.getDragSourceContext().getComponent().firePropertyChange("dragPoint", p.x, p.y);
|
||||
@@ -64,5 +72,26 @@ class CanvasSourceListener implements DragSourceListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dropActionChanged(DragSourceDragEvent dsde) {}
|
||||
public void dropActionChanged(DragSourceDragEvent dsde) {
|
||||
updateCursor(dsde);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the cursor during drag operations using the presenter as the single source of truth.
|
||||
* If the presenter returns DEFAULT_CURSOR, uses MOVE_CURSOR to prevent "No" cursor issues.
|
||||
*/
|
||||
private void updateCursor(DragSourceDragEvent dsde) {
|
||||
Point mousePos = dsde.getDragSourceContext().getComponent().getMousePosition();
|
||||
if (mousePos != null) {
|
||||
// Use presenter as single source of truth for cursor
|
||||
Cursor requestedCursor = presenter.getCursorAt(mousePos, false, false, false);
|
||||
Cursor dragCursor = requestedCursor;
|
||||
// If DEFAULT_CURSOR, use MOVE_CURSOR to prevent "No" cursor during drag operations
|
||||
if (requestedCursor.getType() == Cursor.DEFAULT_CURSOR) {
|
||||
dragCursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR);
|
||||
}
|
||||
dsde.getDragSourceContext().setCursor(null);
|
||||
dsde.getDragSourceContext().setCursor(dragCursor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-6
@@ -21,15 +21,13 @@
|
||||
*/
|
||||
package org.diylc.swing.plugins.canvas;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.awt.dnd.DnDConstants;
|
||||
import java.awt.dnd.DropTargetDragEvent;
|
||||
import java.awt.dnd.DropTargetDropEvent;
|
||||
import java.awt.dnd.DropTargetEvent;
|
||||
import java.awt.dnd.DropTargetListener;
|
||||
import java.awt.*;
|
||||
import java.awt.dnd.*;
|
||||
|
||||
import org.diylc.common.IPlugInPort;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
/**
|
||||
* {@link DropTargetListener} for {@link CanvasPanel}.
|
||||
@@ -106,10 +104,35 @@ class CanvasTargetListener implements DropTargetListener {
|
||||
@Override
|
||||
public void drop(DropTargetDropEvent dtde) {
|
||||
presenter.dragEnded(dtde.getLocation());
|
||||
updateCursor(dtde);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dropActionChanged(DropTargetDragEvent dtde) {
|
||||
presenter.dragActionChanged(dtde.getDropAction());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the cursor during drag operations using the presenter as the single source of truth.
|
||||
* If the presenter returns DEFAULT_CURSOR, uses MOVE_CURSOR to prevent "No" cursor issues.
|
||||
*/
|
||||
private void updateCursor(DropTargetDropEvent dsde) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Point mousePos = dsde.getDropTargetContext().getComponent().getMousePosition();
|
||||
if (mousePos != null) {
|
||||
// Use presenter as single source of truth for cursor
|
||||
Cursor requestedCursor = presenter.getCursorAt(mousePos, false, false, false);
|
||||
Cursor dragCursor = requestedCursor;
|
||||
// If DEFAULT_CURSOR, use MOVE_CURSOR to prevent "No" cursor during drag operations
|
||||
if (requestedCursor.getType() == Cursor.DEFAULT_CURSOR) {
|
||||
dragCursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR);
|
||||
}
|
||||
dsde.getDropTargetContext().getComponent().setCursor(null);
|
||||
dsde.getDropTargetContext().getComponent().setCursor(dragCursor);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+225
@@ -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
|
||||
* <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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("<html>");
|
||||
|
||||
// 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("<br>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("<br>L: %s %s", SIZE_FORMAT.format(length.getValue()), lengthUnit));
|
||||
}
|
||||
|
||||
sb.append("</html>");
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import org.diylc.plugins.chatbot.model.SubscriptionEntity;
|
||||
import org.diylc.plugins.chatbot.service.ChatbotService;
|
||||
import org.diylc.plugins.cloud.service.NotLoggedInException;
|
||||
import org.diylc.swing.ISwingUI;
|
||||
import org.diylc.swing.gui.DialogFactory;
|
||||
import org.diylc.swing.plugins.file.FileFilterEnum;
|
||||
import org.diylc.utils.FileUtils;
|
||||
|
||||
import javax.swing.*;
|
||||
@@ -18,6 +20,11 @@ import javax.swing.event.HyperlinkListener;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
@@ -60,6 +67,7 @@ public class ChatbotPane extends JPanel {
|
||||
private JEditorPane chatEditorPane;
|
||||
private JButton askButton;
|
||||
private JButton clearButton;
|
||||
private JButton exportButton;
|
||||
private JButton premiumButton;
|
||||
|
||||
private boolean loggedIn = false;
|
||||
@@ -138,19 +146,22 @@ public class ChatbotPane extends JPanel {
|
||||
JPanel buttonPanel = new JPanel(new BorderLayout(10, 0)); // 10px horizontal gap between components
|
||||
buttonPanel.setBackground(TERMINAL_BG);
|
||||
|
||||
// Left panel for Clear button
|
||||
// Left panel for Clear and Export (10px only between them; Clear flush left)
|
||||
JPanel leftPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
|
||||
leftPanel.setBackground(TERMINAL_BG);
|
||||
leftPanel.add(getClearButton());
|
||||
leftPanel.add(Box.createHorizontalStrut(10));
|
||||
leftPanel.add(getExportButton());
|
||||
|
||||
// Center panel for Premium button
|
||||
JPanel centerPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
|
||||
centerPanel.setBackground(TERMINAL_BG);
|
||||
centerPanel.add(getPremiumButton());
|
||||
|
||||
// Right panel for Ask button
|
||||
// Right panel for Premium and Ask buttons
|
||||
JPanel rightPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0));
|
||||
rightPanel.setBackground(TERMINAL_BG);
|
||||
rightPanel.add(getPremiumButton());
|
||||
rightPanel.add(getAskButton());
|
||||
|
||||
// Add all panels to the main button panel
|
||||
@@ -488,6 +499,33 @@ public class ChatbotPane extends JPanel {
|
||||
return clearButton;
|
||||
}
|
||||
|
||||
public JButton getExportButton() {
|
||||
if (exportButton == null) {
|
||||
exportButton = new JButton(LangUtil.translate("Export"));
|
||||
styleButton(exportButton);
|
||||
exportButton.addActionListener(e -> {
|
||||
String projectName = FileUtils.extractFileName(plugInPort.getCurrentFileName());
|
||||
File initialFile = new File(projectName + " - chat history.html");
|
||||
File file = DialogFactory.getInstance().showSaveDialog(swingUI.getOwnerFrame(),
|
||||
FileFilterEnum.HTML.getFilter(), initialFile,
|
||||
FileFilterEnum.HTML.getExtensions()[0], null);
|
||||
if (file != null) {
|
||||
try {
|
||||
String content = getChatEditorPane().getText();
|
||||
Files.writeString(Paths.get(file.getAbsolutePath()), content);
|
||||
swingUI.showMessage(LangUtil.translate("Chat history exported successfully."), "Export",
|
||||
ISwingUI.INFORMATION_MESSAGE);
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Failed to export chat history to file", ex);
|
||||
swingUI.showMessage("Could not save to file. " + ex.getMessage(), "Error",
|
||||
ISwingUI.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return exportButton;
|
||||
}
|
||||
|
||||
public JButton getPremiumButton() {
|
||||
if (premiumButton == null) {
|
||||
premiumButton = new JButton(LangUtil.translate("Get Premium"));
|
||||
|
||||
@@ -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),
|
||||
|
||||
+363
-61
@@ -18,6 +18,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.swing.AbstractListModel;
|
||||
import javax.swing.DefaultListCellRenderer;
|
||||
@@ -36,6 +37,7 @@ import javax.swing.event.ListSelectionListener;
|
||||
|
||||
import org.diylc.common.ComponentType;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.core.ComponentGroup;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.lang.LangUtil;
|
||||
import org.diylc.swing.ISwingUI;
|
||||
@@ -60,9 +62,10 @@ public class ExplorerPane extends JPanel {
|
||||
private JTextField searchField;
|
||||
private JScrollPane componentScrollPane;
|
||||
private ComponentListModel componentListModel;
|
||||
private JList<IDIYComponent<?>> componentList;
|
||||
private JList<ComponentGroupItem> componentList;
|
||||
private ComponentPopupMenu popupMenu;
|
||||
private JComboBox<Sort> sortBox;
|
||||
private ListSelectionListener selectionListener;
|
||||
|
||||
public ExplorerPane(ISwingUI swingUI, IPlugInPort plugInPort) {
|
||||
super();
|
||||
@@ -144,9 +147,9 @@ public class ExplorerPane extends JPanel {
|
||||
return componentScrollPane;
|
||||
}
|
||||
|
||||
public JList<IDIYComponent<?>> getComponentList() {
|
||||
public JList<ComponentGroupItem> getComponentList() {
|
||||
if (componentList == null) {
|
||||
componentList = new DragDropList<IDIYComponent<?>>(new IDragDropListListener() {
|
||||
componentList = new DragDropList<ComponentGroupItem>(new IDragDropListListener() {
|
||||
|
||||
@Override
|
||||
public boolean dropComplete(int selectedIndex, int dropTargetIndex) {
|
||||
@@ -154,7 +157,82 @@ public class ExplorerPane extends JPanel {
|
||||
swingUI.showMessage(REORDERING_NOT_POSSIBLE, "Error", ISwingUI.ERROR_MESSAGE);
|
||||
return false;
|
||||
}
|
||||
plugInPort.moveSelectionToZIndex(getComponentListModel().getSize() - dropTargetIndex);
|
||||
// The list is displayed in reverse order (highest z-index first, index 0 = last in component list)
|
||||
// Component list is in normal order (lowest z-index first, index 0 = first component)
|
||||
// In INSERT mode, dropTargetIndex is where the item will be inserted AFTER removal
|
||||
int listSize = getComponentListModel().getSize();
|
||||
int componentCount = plugInPort.getCurrentProject().getComponents().size();
|
||||
|
||||
// The list size may be different from component count due to groups
|
||||
// listSize = componentCount - groupedComponents + groups
|
||||
// We need to find which component will be at dropTargetIndex in the final list
|
||||
// and convert that to a component index
|
||||
|
||||
// Find the ComponentGroupItem that will be at dropTargetIndex after removal
|
||||
// We want to insert before the component that will be at dropTargetIndex after insertion
|
||||
List<ComponentGroupItem> components = getComponentListModel().getComponents();
|
||||
|
||||
int componentAtTargetListIndex;
|
||||
|
||||
if (dropTargetIndex >= listSize - 1) {
|
||||
// Dropping at the end
|
||||
plugInPort.moveSelectionToZIndex(componentCount);
|
||||
return true;
|
||||
} else if (selectedIndex < dropTargetIndex) {
|
||||
// Dragging up: removal happens before target
|
||||
// After removal, the component that was at dropTargetIndex is now at dropTargetIndex-1
|
||||
// After insertion at dropTargetIndex, it will be at dropTargetIndex+1
|
||||
// So we want to insert before the component currently at dropTargetIndex
|
||||
componentAtTargetListIndex = dropTargetIndex;
|
||||
} else {
|
||||
// Dragging down: removal happens after target
|
||||
// After removal, the component at dropTargetIndex stays at dropTargetIndex
|
||||
// After insertion at dropTargetIndex, it will be at dropTargetIndex+1
|
||||
// So we want to insert before the component currently at dropTargetIndex
|
||||
componentAtTargetListIndex = dropTargetIndex;
|
||||
}
|
||||
|
||||
if (componentAtTargetListIndex >= listSize) {
|
||||
// Insert at the end
|
||||
plugInPort.moveSelectionToZIndex(componentCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the ComponentGroupItem at that position
|
||||
ComponentGroupItem targetItem = components.get(componentAtTargetListIndex);
|
||||
|
||||
// Find the highest z-index component in this group (or the component itself if single)
|
||||
// Since the list is sorted by z-index (reversed), this is the component that determines
|
||||
// the position of this group in the list
|
||||
IDIYComponent<?> targetComponent = null;
|
||||
int maxComponentIndex = -1;
|
||||
List<IDIYComponent<?>> allComponents = plugInPort.getCurrentProject().getComponents();
|
||||
|
||||
for (IDIYComponent<?> comp : targetItem.getGroup()) {
|
||||
int compIndex = allComponents.indexOf(comp);
|
||||
if (compIndex > maxComponentIndex) {
|
||||
maxComponentIndex = compIndex;
|
||||
targetComponent = comp;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetComponent == null || maxComponentIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We want to insert before the component at maxComponentIndex
|
||||
// moveSelectionToZIndex will automatically decrement zIndex for each selected component
|
||||
// that's before the target, so we can use maxComponentIndex directly
|
||||
// However, we need to add 1 because we want to insert at dropTargetIndex, not before it
|
||||
int targetComponentIndex = maxComponentIndex + 1;
|
||||
|
||||
if (targetComponentIndex < 0) {
|
||||
targetComponentIndex = 0;
|
||||
} else if (targetComponentIndex > componentCount) {
|
||||
targetComponentIndex = componentCount;
|
||||
}
|
||||
|
||||
plugInPort.moveSelectionToZIndex(targetComponentIndex);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@@ -162,20 +240,28 @@ public class ExplorerPane extends JPanel {
|
||||
componentList.setFont(DEFAULT_FONT);
|
||||
componentList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||
componentList.setCellRenderer(new ComponentListCellRenderer(plugInPort.getComponentTypes()));
|
||||
componentList.addListSelectionListener(new ListSelectionListener() {
|
||||
selectionListener = new ListSelectionListener() {
|
||||
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
if (e.getValueIsAdjusting())
|
||||
return;
|
||||
|
||||
List<IDIYComponent<?>> selectedValuesList = componentList.getSelectedValuesList();
|
||||
if (selectedValuesList.size() > 0 && !new HashSet<IDIYComponent<?>>(selectedValuesList)
|
||||
List<ComponentGroupItem> selectedValuesList = componentList.getSelectedValuesList();
|
||||
Set<IDIYComponent<?>> expandedSelection = new HashSet<>();
|
||||
|
||||
for (ComponentGroupItem selected : selectedValuesList) {
|
||||
// Always add all components from the group item
|
||||
expandedSelection.addAll(selected.getGroup());
|
||||
}
|
||||
|
||||
if (expandedSelection.size() > 0 && !expandedSelection
|
||||
.equals(new HashSet<IDIYComponent<?>>(plugInPort.getSelectedComponents()))) {
|
||||
plugInPort.setSelection(selectedValuesList, true);
|
||||
plugInPort.setSelection(expandedSelection, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
componentList.addListSelectionListener(selectionListener);
|
||||
|
||||
componentList.addMouseListener(new MouseAdapter() {
|
||||
|
||||
@@ -205,8 +291,13 @@ public class ExplorerPane extends JPanel {
|
||||
if (plugInPort.getNewComponentTypeSlot() == null && (e.isPopupTrigger()
|
||||
|| (pressedEvent != null && pressedEvent.isPopupTrigger()))) {
|
||||
|
||||
final List<IDIYComponent<?>> selectedValues = componentList.getSelectedValuesList();
|
||||
getComponentPopupMenu().prepareAndShowAt(selectedValues, selectedValues, e.getX(),
|
||||
final List<ComponentGroupItem> selectedValues = componentList.getSelectedValuesList();
|
||||
// Extract actual components from selected items
|
||||
List<IDIYComponent<?>> components = new ArrayList<>();
|
||||
for (ComponentGroupItem item : selectedValues) {
|
||||
components.addAll(item.getGroup());
|
||||
}
|
||||
getComponentPopupMenu().prepareAndShowAt(components, components, e.getX(),
|
||||
e.getY());
|
||||
}
|
||||
}
|
||||
@@ -238,46 +329,105 @@ public class ExplorerPane extends JPanel {
|
||||
}
|
||||
|
||||
public void setSelection(Set<IDIYComponent<?>> selectedComponents) {
|
||||
List<IDIYComponent<?>> selectedValuesList = componentList.getSelectedValuesList();
|
||||
if (new HashSet<IDIYComponent<?>>(selectedValuesList)
|
||||
.equals(new HashSet<IDIYComponent<?>>(plugInPort.getSelectedComponents())))
|
||||
List<ComponentGroupItem> selectedValuesList = componentList.getSelectedValuesList();
|
||||
|
||||
// Extract components from selected values
|
||||
Set<IDIYComponent<?>> currentSelected = new HashSet<>();
|
||||
for (ComponentGroupItem item : selectedValuesList) {
|
||||
currentSelected.addAll(item.getGroup());
|
||||
}
|
||||
|
||||
if (currentSelected.equals(selectedComponents))
|
||||
return;
|
||||
|
||||
final JList<IDIYComponent<?>> list = this.getComponentList();
|
||||
final JList<ComponentGroupItem> list = this.getComponentList();
|
||||
|
||||
if (selectedComponents.size() == 0) {
|
||||
list.clearSelection();
|
||||
} else {
|
||||
// Temporarily remove listener to prevent infinite loop
|
||||
list.removeListSelectionListener(selectionListener);
|
||||
try {
|
||||
if (selectedComponents.size() == 0) {
|
||||
list.clearSelection();
|
||||
} else {
|
||||
// Build a map from component to its group (if any)
|
||||
Set<ComponentGroup> groups = plugInPort.getComponentGroups();
|
||||
Map<IDIYComponent<?>, Set<IDIYComponent<?>>> componentToGroup = new java.util.HashMap<>();
|
||||
if (groups != null && !groups.isEmpty()) {
|
||||
// Build a map from UUID to component for quick lookup
|
||||
List<IDIYComponent<?>> allComponents = plugInPort.getCurrentProject().getComponents();
|
||||
Map<UUID, IDIYComponent<?>> idToComponent = allComponents.stream()
|
||||
.collect(Collectors.toMap(IDIYComponent::getId, comp -> comp));
|
||||
|
||||
for (ComponentGroup group : groups) {
|
||||
// Convert ComponentGroup (with UUIDs) to Set<IDIYComponent<?>>
|
||||
Set<IDIYComponent<?>> componentSet = group.getComponentIds().stream()
|
||||
.map(idToComponent::get)
|
||||
.filter(java.util.Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
for (IDIYComponent<?> component : componentSet) {
|
||||
componentToGroup.put(component, componentSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int[] indices = new int[selectedComponents.size()];
|
||||
int n = 0;
|
||||
List<IDIYComponent<?>> components = this.getComponentListModel().getComponents();
|
||||
for (int i = 0; i < components.size(); i++) {
|
||||
indices[n] = -1; // this should not happen
|
||||
if (selectedComponents.contains(components.get(i)))
|
||||
indices[n++] = i;
|
||||
if (n == indices.length)
|
||||
break;
|
||||
// Find indices of items to select
|
||||
List<ComponentGroupItem> components = this.getComponentListModel().getComponents();
|
||||
Set<Integer> selectedIndices = new HashSet<>();
|
||||
Set<Set<IDIYComponent<?>>> selectedGroups = new HashSet<>();
|
||||
|
||||
for (IDIYComponent<?> selected : selectedComponents) {
|
||||
Set<IDIYComponent<?>> group = componentToGroup.get(selected);
|
||||
if (group != null) {
|
||||
// Component is in a group
|
||||
if (!selectedGroups.contains(group)) {
|
||||
// Find the ComponentGroupItem for this group
|
||||
for (int i = 0; i < components.size(); i++) {
|
||||
ComponentGroupItem groupItem = components.get(i);
|
||||
if (groupItem.getGroup().equals(group)) {
|
||||
selectedIndices.add(i);
|
||||
selectedGroups.add(group);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Component is not in any group, find the ComponentGroupItem that contains only this component
|
||||
for (int i = 0; i < components.size(); i++) {
|
||||
ComponentGroupItem groupItem = components.get(i);
|
||||
if (groupItem.getGroup().size() == 1 && groupItem.getGroup().contains(selected)) {
|
||||
selectedIndices.add(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int[] indices = selectedIndices.stream().mapToInt(i -> i).toArray();
|
||||
|
||||
list.getSelectionModel().setValueIsAdjusting(true);
|
||||
list.setSelectedIndices(indices);
|
||||
list.getSelectionModel().setValueIsAdjusting(false);
|
||||
if (indices.length > 0) {
|
||||
int firstVisibleIndex = list.getFirstVisibleIndex();
|
||||
int lastVisibleIndex = list.getLastVisibleIndex();
|
||||
// if none of the selected items is visible, jump to the first one
|
||||
if (!Arrays.stream(indices)
|
||||
.anyMatch(index -> firstVisibleIndex <= index && index <= lastVisibleIndex))
|
||||
list.ensureIndexIsVisible(indices[0]);
|
||||
}
|
||||
}
|
||||
|
||||
list.getSelectionModel().setValueIsAdjusting(true);
|
||||
list.setSelectedIndices(indices);
|
||||
list.getSelectionModel().setValueIsAdjusting(false);
|
||||
int firstVisibleIndex = list.getFirstVisibleIndex();
|
||||
int lastVisibleIndex = list.getFirstVisibleIndex();
|
||||
// if none of the selected items is visible, jump to the first one
|
||||
if (!Arrays.stream(indices)
|
||||
.anyMatch(index -> firstVisibleIndex <= index && index <= lastVisibleIndex))
|
||||
list.ensureIndexIsVisible(indices[0]);
|
||||
} finally {
|
||||
// Re-add listener
|
||||
list.addListSelectionListener(selectionListener);
|
||||
}
|
||||
}
|
||||
|
||||
private class ComponentListModel extends AbstractListModel<IDIYComponent<?>> {
|
||||
private class ComponentListModel extends AbstractListModel<ComponentGroupItem> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private List<IDIYComponent<?>> componentsRaw;
|
||||
private List<IDIYComponent<?>> components;
|
||||
private List<ComponentGroupItem> components;
|
||||
|
||||
private String searchText;
|
||||
private Sort sort = Sort.Z_INDEX;
|
||||
@@ -289,7 +439,7 @@ public class ExplorerPane extends JPanel {
|
||||
refresh(oldSize);
|
||||
}
|
||||
|
||||
public List<IDIYComponent<?>> getComponents() {
|
||||
public List<ComponentGroupItem> getComponents() {
|
||||
return components;
|
||||
}
|
||||
|
||||
@@ -310,27 +460,32 @@ public class ExplorerPane extends JPanel {
|
||||
}
|
||||
|
||||
private void refresh(int oldSize) {
|
||||
List<IDIYComponent<?>> sortedComponents;
|
||||
|
||||
if (sort == Sort.NAME) {
|
||||
this.components =
|
||||
sortedComponents =
|
||||
componentsRaw.stream().sorted(Comparator.comparing(x -> x.toString().toLowerCase()))
|
||||
.collect(Collectors.toList());
|
||||
} else if (sort == Sort.TYPE) {
|
||||
this.components = componentsRaw.stream()
|
||||
sortedComponents = componentsRaw.stream()
|
||||
.sorted(Comparator.comparing(x -> x.getClass().getName().toLowerCase())
|
||||
.thenComparing(x -> x.toString().toLowerCase()))
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
List<IDIYComponent<?>> componentsClone = new ArrayList<IDIYComponent<?>>(componentsRaw);
|
||||
Collections.reverse(componentsClone);
|
||||
this.components = componentsClone;
|
||||
sortedComponents = componentsClone;
|
||||
}
|
||||
|
||||
if (searchText != null && !searchText.trim().isEmpty()) {
|
||||
this.components = this.components.stream().filter(component -> {
|
||||
sortedComponents = sortedComponents.stream().filter(component -> {
|
||||
return component.getName().toLowerCase().contains(searchText);
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// Build list with groups and ungrouped components
|
||||
this.components = buildComponentListWithGroups(sortedComponents);
|
||||
|
||||
int newSize = this.components.size();
|
||||
|
||||
fireContentsChanged(this, 0, Math.min(newSize, oldSize));
|
||||
@@ -339,6 +494,67 @@ public class ExplorerPane extends JPanel {
|
||||
else if (newSize > oldSize)
|
||||
fireIntervalAdded(this, oldSize, newSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the component list, using ComponentGroupItem for all items.
|
||||
* For grouped components, creates one ComponentGroupItem per group.
|
||||
* For ungrouped components, creates a ComponentGroupItem with a single component.
|
||||
*/
|
||||
private List<ComponentGroupItem> buildComponentListWithGroups(List<IDIYComponent<?>> sortedComponents) {
|
||||
Set<ComponentGroup> groups = plugInPort.getComponentGroups();
|
||||
|
||||
// Build a map from component to its group (if any) and group name
|
||||
Map<IDIYComponent<?>, Set<IDIYComponent<?>>> componentToGroup = new java.util.HashMap<>();
|
||||
Map<Set<IDIYComponent<?>>, String> groupToName = new java.util.HashMap<>();
|
||||
if (groups != null && !groups.isEmpty()) {
|
||||
// Build a map from UUID to component for quick lookup
|
||||
List<IDIYComponent<?>> allComponents = plugInPort.getCurrentProject().getComponents();
|
||||
Map<UUID, IDIYComponent<?>> idToComponent = allComponents.stream()
|
||||
.collect(Collectors.toMap(IDIYComponent::getId, comp -> comp));
|
||||
|
||||
for (ComponentGroup group : groups) {
|
||||
// Convert ComponentGroup (with UUIDs) to Set<IDIYComponent<?>>
|
||||
Set<IDIYComponent<?>> componentSet = group.getComponentIds().stream()
|
||||
.map(idToComponent::get)
|
||||
.filter(java.util.Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
for (IDIYComponent<?> component : componentSet) {
|
||||
componentToGroup.put(component, componentSet);
|
||||
}
|
||||
|
||||
// Store the group name
|
||||
if (group.getName() != null && !group.getName().isEmpty()) {
|
||||
groupToName.put(componentSet, group.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Track which groups we've already added
|
||||
Set<Set<IDIYComponent<?>>> addedGroups = new HashSet<>();
|
||||
List<ComponentGroupItem> result = new ArrayList<>();
|
||||
|
||||
for (IDIYComponent<?> component : sortedComponents) {
|
||||
Set<IDIYComponent<?>> group = componentToGroup.get(component);
|
||||
if (group == null) {
|
||||
// Component is not in any group, create a ComponentGroupItem with just this component
|
||||
Set<IDIYComponent<?>> singleComponentGroup = new HashSet<>();
|
||||
singleComponentGroup.add(component);
|
||||
result.add(new ComponentGroupItem(singleComponentGroup, null));
|
||||
} else {
|
||||
// Component is in a group
|
||||
if (!addedGroups.contains(group)) {
|
||||
// This is the first component from this group we've seen, add the group item
|
||||
String groupName = groupToName.get(group);
|
||||
result.add(new ComponentGroupItem(group, groupName));
|
||||
addedGroups.add(group);
|
||||
}
|
||||
// Otherwise, skip this component as we already have a group item for it
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
@@ -346,12 +562,12 @@ public class ExplorerPane extends JPanel {
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDIYComponent<?> getElementAt(int index) {
|
||||
public ComponentGroupItem getElementAt(int index) {
|
||||
return this.components.get(index);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ComponentListCellRenderer implements ListCellRenderer<IDIYComponent<?>> {
|
||||
private static class ComponentListCellRenderer implements ListCellRenderer<ComponentGroupItem> {
|
||||
|
||||
// private static Color[] layerColors = new Color[] { Color.gray, Color.decode("#ff6363"),
|
||||
// Color.decode("#293462"), Color.decode("#138588"),
|
||||
@@ -367,32 +583,70 @@ public class ExplorerPane extends JPanel {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<? extends IDIYComponent<?>> list,
|
||||
IDIYComponent<?> value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
|
||||
ComponentType componentType = componentTypes.get(value.getClass().getCanonicalName());
|
||||
public Component getListCellRendererComponent(JList<? extends ComponentGroupItem> list,
|
||||
ComponentGroupItem value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
|
||||
JLabel renderer = (JLabel) defaultRenderer.getListCellRendererComponent(list, value, index,
|
||||
isSelected, cellHasFocus);
|
||||
|
||||
int layerId = (int) Math.round(componentType.getZOrder());
|
||||
ComponentGroupItem groupItem = value;
|
||||
|
||||
if (groupItem.getComponentCount() == 1) {
|
||||
// Render as individual component (ungrouped)
|
||||
IDIYComponent<?> component = groupItem.getGroup().iterator().next();
|
||||
ComponentType componentType = componentTypes.get(component.getClass().getCanonicalName());
|
||||
|
||||
if (componentType == null) {
|
||||
renderer.setText(component.toString());
|
||||
return renderer;
|
||||
}
|
||||
|
||||
String valueForDisplay = value.getValueForDisplay();
|
||||
if (isSelected) {
|
||||
if (valueForDisplay == null || valueForDisplay.trim().isEmpty()) {
|
||||
renderer.setText(String.format("[L%s] %s", layerId, value.getName()));
|
||||
int layerId = (int) Math.round(componentType.getZOrder());
|
||||
|
||||
String valueForDisplay = component.getValueForDisplay();
|
||||
if (isSelected) {
|
||||
if (valueForDisplay == null || valueForDisplay.trim().isEmpty()) {
|
||||
renderer.setText(String.format("[L%s] %s", layerId, component.getName()));
|
||||
} else {
|
||||
renderer
|
||||
.setText(String.format("[L%s] %s (%s)", layerId, component.getName(), valueForDisplay));
|
||||
}
|
||||
} else {
|
||||
renderer
|
||||
.setText(String.format("[L%s] %s (%s)", layerId, value.getName(), valueForDisplay));
|
||||
if (valueForDisplay == null || valueForDisplay.trim().isEmpty()) {
|
||||
renderer.setText(String.format("<html><font color='#c0c0c0'>[L%s]</font> %s</html>",
|
||||
layerId, component.getName()));
|
||||
} else {
|
||||
renderer.setText(String.format(
|
||||
"<html><font color='#c0c0c0'>[L%s]</font> %s (<font color='#666666'>%s</font>)</html>",
|
||||
layerId, component.getName(), valueForDisplay));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (valueForDisplay == null || valueForDisplay.trim().isEmpty()) {
|
||||
renderer.setText(String.format("<html><font color='#c0c0c0'>[L%s]</font> %s</html>",
|
||||
layerId, value.getName()));
|
||||
// Render as group
|
||||
// Calculate maximum layer from components in the group
|
||||
int maxLayerId = 0;
|
||||
for (IDIYComponent<?> component : groupItem.getGroup()) {
|
||||
ComponentType componentType = componentTypes.get(component.getClass().getCanonicalName());
|
||||
if (componentType != null) {
|
||||
int layerId = (int) Math.round(componentType.getZOrder());
|
||||
if (layerId > maxLayerId) {
|
||||
maxLayerId = layerId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format: name (component count), similar to regular components showing name (value)
|
||||
String groupName = groupItem.getGroupName() != null && !groupItem.getGroupName().isEmpty()
|
||||
? groupItem.getGroupName()
|
||||
: "Group";
|
||||
String componentCount = String.valueOf(groupItem.getComponentCount());
|
||||
|
||||
if (isSelected) {
|
||||
renderer.setText(String.format("<html>[L%s] <b>%s</b> (<b>%s</b>)</html>", maxLayerId, groupName, componentCount));
|
||||
} else {
|
||||
renderer.setText(String.format(
|
||||
"<html><font color='#c0c0c0'>[L%s]</font> %s (<font color='#666666'>%s</font>)</html>",
|
||||
layerId, value.getName(), valueForDisplay));
|
||||
maxLayerId, groupName, componentCount));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,4 +668,52 @@ public class ExplorerPane extends JPanel {
|
||||
return LangUtil.translate(label);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class to represent a group of components in the explorer list.
|
||||
*/
|
||||
public static class ComponentGroupItem {
|
||||
private final Set<IDIYComponent<?>> group;
|
||||
private final String groupName;
|
||||
|
||||
public ComponentGroupItem(Set<IDIYComponent<?>> group, String groupName) {
|
||||
this.group = group;
|
||||
this.groupName = groupName;
|
||||
}
|
||||
|
||||
public Set<IDIYComponent<?>> getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public int getComponentCount() {
|
||||
return group.size();
|
||||
}
|
||||
|
||||
public String getGroupName() {
|
||||
return groupName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (groupName != null && !groupName.isEmpty()) {
|
||||
return groupName;
|
||||
}
|
||||
return "Group of " + group.size() + " components";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null || getClass() != obj.getClass())
|
||||
return false;
|
||||
ComponentGroupItem other = (ComponentGroupItem) obj;
|
||||
return group.equals(other.group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return group.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,11 +43,13 @@ public class FileMenuPlugin implements IPlugIn, IDynamicSubmenuHandler {
|
||||
|
||||
private static final String FILE_TITLE = "File";
|
||||
private static final String TRACE_MASK_TITLE = "Trace Mask";
|
||||
private static final String SILK_SCREEN_TITLE = "Silkscreen";
|
||||
private static final String INTEGRATION_TITLE = "Integration";
|
||||
private static final String EXPORT_TITLE = "Export";
|
||||
private static final String PROJECT_TITLE = "Project";
|
||||
|
||||
private ProjectDrawingProvider drawingProvider;
|
||||
private TraceMaskDrawingProvider traceMaskDrawingProvider;
|
||||
private SilkScreenDrawingProvider silkScreenDrawingProvider;
|
||||
|
||||
private ISwingUI swingUI;
|
||||
private IPlugInPort plugInPort;
|
||||
@@ -62,6 +64,7 @@ public class FileMenuPlugin implements IPlugIn, IDynamicSubmenuHandler {
|
||||
this.plugInPort = plugInPort;
|
||||
this.drawingProvider = new ProjectDrawingProvider(plugInPort, false, true, false);
|
||||
this.traceMaskDrawingProvider = new TraceMaskDrawingProvider(plugInPort);
|
||||
this.silkScreenDrawingProvider = new SilkScreenDrawingProvider(plugInPort);
|
||||
|
||||
ActionFactory actionFactory = ActionFactory.getInstance();
|
||||
swingUI.injectMenuAction(actionFactory.createNewAction(plugInPort), FILE_TITLE);
|
||||
@@ -73,15 +76,17 @@ public class FileMenuPlugin implements IPlugIn, IDynamicSubmenuHandler {
|
||||
swingUI.injectMenuAction(actionFactory.createSaveAsAction(plugInPort, swingUI), FILE_TITLE);
|
||||
swingUI.injectDynamicSubmenu("Recent Files", IconLoader.History.getIcon(), FILE_TITLE, this);
|
||||
swingUI.injectMenuAction(null, FILE_TITLE);
|
||||
swingUI.injectSubmenu(EXPORT_TITLE, IconLoader.Export.getIcon(), FILE_TITLE);
|
||||
|
||||
swingUI.injectSubmenu(PROJECT_TITLE, IconLoader.Project.getIcon(), FILE_TITLE);
|
||||
swingUI.injectMenuAction(
|
||||
actionFactory.createExportPDFAction(plugInPort, drawingProvider, swingUI, ""), EXPORT_TITLE);
|
||||
actionFactory.createExportPDFAction(plugInPort, drawingProvider, swingUI, ""), PROJECT_TITLE);
|
||||
swingUI.injectMenuAction(
|
||||
actionFactory.createExportPNGAction(plugInPort, drawingProvider, swingUI, ""), EXPORT_TITLE);
|
||||
actionFactory.createExportPNGAction(plugInPort, drawingProvider, swingUI, ""), PROJECT_TITLE);
|
||||
swingUI.injectMenuAction(
|
||||
actionFactory.createExportGerberAction(plugInPort, swingUI), EXPORT_TITLE);
|
||||
actionFactory.createExportGerberAction(plugInPort, swingUI), PROJECT_TITLE);
|
||||
swingUI.injectMenuAction(actionFactory.createPrintAction(drawingProvider, swingUI,
|
||||
Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()), FILE_TITLE);
|
||||
Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()), PROJECT_TITLE);
|
||||
|
||||
swingUI.injectSubmenu(TRACE_MASK_TITLE, IconLoader.TraceMask.getIcon(), FILE_TITLE);
|
||||
swingUI.injectMenuAction(actionFactory.createExportPDFAction(plugInPort,
|
||||
traceMaskDrawingProvider, swingUI, " (mask)"), TRACE_MASK_TITLE);
|
||||
@@ -92,6 +97,17 @@ public class FileMenuPlugin implements IPlugIn, IDynamicSubmenuHandler {
|
||||
Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx() | KeyEvent.SHIFT_DOWN_MASK),
|
||||
TRACE_MASK_TITLE);
|
||||
|
||||
swingUI.injectSubmenu(SILK_SCREEN_TITLE, IconLoader.Silkscreen.getIcon(), FILE_TITLE);
|
||||
swingUI.injectMenuAction(actionFactory.createExportPDFAction(plugInPort,
|
||||
silkScreenDrawingProvider, swingUI, " (silkscreen)"), SILK_SCREEN_TITLE);
|
||||
swingUI.injectMenuAction(actionFactory.createExportPNGAction(plugInPort,
|
||||
silkScreenDrawingProvider, swingUI, " (silkscreen)"), SILK_SCREEN_TITLE);
|
||||
swingUI.injectMenuAction(
|
||||
actionFactory.createPrintAction(silkScreenDrawingProvider, swingUI,
|
||||
Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx() | KeyEvent.ALT_DOWN_MASK),
|
||||
SILK_SCREEN_TITLE);
|
||||
|
||||
|
||||
swingUI.injectMenuAction(null, FILE_TITLE);
|
||||
swingUI.injectSubmenu(INTEGRATION_TITLE, IconLoader.Node.getIcon(), FILE_TITLE);
|
||||
swingUI.injectMenuAction(actionFactory.createImportBlocksAction(swingUI, plugInPort),
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
|
||||
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.swing.plugins.file;
|
||||
|
||||
import org.diylc.common.DrawOption;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.filter.PCBLayerFilter;
|
||||
import org.diylc.filter.SilkScreenFilter;
|
||||
import org.diylc.swingframework.IDrawingProvider;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* {@link IDrawingProvider} implementation that uses {@link IPlugInPort} to draw a project onto the
|
||||
* canvas.
|
||||
*
|
||||
* @author Branislav Stojkovic
|
||||
*/
|
||||
public class SilkScreenDrawingProvider implements IDrawingProvider {
|
||||
|
||||
private IPlugInPort plugInPort;
|
||||
|
||||
public SilkScreenDrawingProvider(IPlugInPort plugInPort) {
|
||||
super();
|
||||
this.plugInPort = plugInPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getSize() {
|
||||
return plugInPort.getCanvasDimensions(false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(int page, Graphics g, double zoomFactor) {
|
||||
plugInPort.draw((Graphics2D) g, EnumSet.of(DrawOption.ANTIALIASING, DrawOption.OUTLINE_MODE, DrawOption.MONOCHROMATIC),
|
||||
new SilkScreenFilter(), zoomFactor, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPageCount() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -37,7 +37,7 @@ import org.diylc.common.DrawOption;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.common.PCBLayer;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.presenter.PCBLayerFilter;
|
||||
import org.diylc.filter.PCBLayerFilter;
|
||||
|
||||
/**
|
||||
* {@link IDrawingProvider} implementation that uses {@link IPlugInPort} to draw a project onto the
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
*
|
||||
* 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.utils;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.util.Enumeration;
|
||||
import javax.swing.UIDefaults;
|
||||
import javax.swing.UIManager;
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
|
||||
/**
|
||||
* Utility class for applying a configurable font scaling factor to all UI components.
|
||||
*
|
||||
* The scaling factor is stored in the application configuration and can be set
|
||||
* programmatically or through the configuration system.
|
||||
* For example, a factor of 1.5 will make all fonts 50% larger.
|
||||
*
|
||||
* @author Branislav Stojkovic
|
||||
*/
|
||||
public class FontScalingUtils {
|
||||
|
||||
private static final double DEFAULT_FONT_SCALE_FACTOR = 1.0;
|
||||
private static final double MIN_FONT_SCALE_FACTOR = 0.5;
|
||||
private static final double MAX_FONT_SCALE_FACTOR = 3.0;
|
||||
|
||||
/**
|
||||
* Gets the font scaling factor from ConfigurationManager.
|
||||
*
|
||||
* @return The scaling factor (defaults to 1.0 if not set or invalid)
|
||||
*/
|
||||
public static double getFontScaleFactor() {
|
||||
try {
|
||||
Object factorObj = ConfigurationManager.getInstance().readObject(
|
||||
IPlugInPort.FONT_SCALE_FACTOR_KEY, DEFAULT_FONT_SCALE_FACTOR);
|
||||
|
||||
if (factorObj instanceof Number) {
|
||||
double factor = ((Number) factorObj).doubleValue();
|
||||
// Validate the factor is reasonable (between 0.5 and 3.0)
|
||||
if (factor >= MIN_FONT_SCALE_FACTOR && factor <= MAX_FONT_SCALE_FACTOR) {
|
||||
return factor;
|
||||
} else {
|
||||
System.err.println("Warning: Font scale factor " + factor + " is out of range ("
|
||||
+ MIN_FONT_SCALE_FACTOR + "-" + MAX_FONT_SCALE_FACTOR + "). Using default "
|
||||
+ DEFAULT_FONT_SCALE_FACTOR);
|
||||
return DEFAULT_FONT_SCALE_FACTOR;
|
||||
}
|
||||
} else if (factorObj instanceof String) {
|
||||
// Handle string values (for backward compatibility or manual config editing)
|
||||
try {
|
||||
double factor = Double.parseDouble((String) factorObj);
|
||||
if (factor >= MIN_FONT_SCALE_FACTOR && factor <= MAX_FONT_SCALE_FACTOR) {
|
||||
return factor;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
// Invalid string format, use default
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Warning: Could not read font scale factor from configuration: " + e.getMessage());
|
||||
}
|
||||
|
||||
return DEFAULT_FONT_SCALE_FACTOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the font scaling factor in ConfigurationManager.
|
||||
*
|
||||
* @param factor The scaling factor (must be between 0.5 and 3.0)
|
||||
* @return true if the factor was set successfully, false if invalid
|
||||
*/
|
||||
public static boolean setFontScaleFactor(double factor) {
|
||||
if (factor < MIN_FONT_SCALE_FACTOR || factor > MAX_FONT_SCALE_FACTOR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
ConfigurationManager.getInstance().writeValue(IPlugInPort.FONT_SCALE_FACTOR_KEY, factor);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
System.err.println("Warning: Could not write font scale factor to configuration: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the font scaling factor to all UIManager font defaults.
|
||||
* This should be called once during application initialization, after setting the
|
||||
* Look and Feel but before creating any UI components.
|
||||
*
|
||||
* The scaling factor is read from ConfigurationManager using the key
|
||||
* IPlugInPort.FONT_SCALE_FACTOR_KEY. The factor should be a number between 0.5 and 3.0
|
||||
* (e.g., 1.5 for 50% larger fonts). Use setFontScaleFactor() to change the value
|
||||
* programmatically.
|
||||
*/
|
||||
public static void applyFontScaling() {
|
||||
double scaleFactor = getFontScaleFactor();
|
||||
|
||||
// If factor is 1.0, no scaling needed
|
||||
if (scaleFactor == 1.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
UIDefaults defaults = UIManager.getDefaults();
|
||||
Enumeration<Object> keys = defaults.keys();
|
||||
|
||||
while (keys.hasMoreElements()) {
|
||||
Object key = keys.nextElement();
|
||||
Object value = defaults.get(key);
|
||||
|
||||
// Check if this is a font property
|
||||
if (value instanceof Font font) {
|
||||
float newSize = (float) (font.getSize() * scaleFactor);
|
||||
Font scaledFont = font.deriveFont(newSize);
|
||||
defaults.put(key, scaledFont);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Warning: Could not apply font scaling: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,8 @@ public enum IconLoader {
|
||||
ComponentLarge("component_large.png"), ImportNetlist("import_netlist.png"), SplashIC("splash_ic.png"),
|
||||
ComponentPreferences("component_preferences.png"), ComponentReplace("component_replace.png"), Barcode("barcode.png"),
|
||||
DocumentGerber("document_gerber.png"), DocumentX2("document_x2.png"), Export("export.png"), Multimeter("multimeter.png"),
|
||||
Patreon("patreon.png"), Brain("brain.png"), Tables("tables.png");
|
||||
Patreon("patreon.png"), Brain("brain.png"), Tables("tables.png"), Silkscreen("silkscreen.png"),
|
||||
Project("project.png");
|
||||
|
||||
protected String name;
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
*
|
||||
* 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.utils;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.function.Function;
|
||||
import javax.swing.UIManager;
|
||||
|
||||
/**
|
||||
* Utility class for Swing UI-related operations.
|
||||
*
|
||||
* @author Branislav Stojkovic
|
||||
*/
|
||||
public class SwingUtils {
|
||||
|
||||
/**
|
||||
* Fixes tooltip colors for Linux systems where they may be unreadable.
|
||||
* Some Linux L&F themes set white text on light backgrounds, making tooltips invisible.
|
||||
* This method ensures tooltips always have dark text on light background for readability.
|
||||
*
|
||||
* Should be called after UIManager.setLookAndFeel() to override system defaults.
|
||||
*/
|
||||
public static void fixTooltipColors() {
|
||||
Color tooltipBg = UIManager.getColor("ToolTip.background");
|
||||
Color tooltipFg = UIManager.getColor("ToolTip.foreground");
|
||||
|
||||
// Helper function to calculate brightness (0.0 = black, 1.0 = white)
|
||||
Function<Color, Double> getBrightness = (c) -> {
|
||||
return (0.299 * c.getRed() + 0.587 * c.getGreen() + 0.114 * c.getBlue()) / 255.0;
|
||||
};
|
||||
|
||||
// Check if we have a problematic color combination (light text on light background)
|
||||
boolean needsFix = false;
|
||||
if (tooltipFg != null && tooltipBg != null) {
|
||||
double fgBrightness = getBrightness.apply(tooltipFg);
|
||||
double bgBrightness = getBrightness.apply(tooltipBg);
|
||||
// If both foreground and background are light, it's unreadable
|
||||
if (fgBrightness > 0.7 && bgBrightness > 0.5) {
|
||||
needsFix = true;
|
||||
}
|
||||
} else if (tooltipFg == null || tooltipBg == null) {
|
||||
// Missing color definitions - set safe defaults
|
||||
needsFix = true;
|
||||
}
|
||||
|
||||
if (needsFix) {
|
||||
UIManager.put("ToolTip.background", new Color(255, 255, 220)); // Light yellow
|
||||
UIManager.put("ToolTip.foreground", Color.BLACK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 869 B |
Binary file not shown.
|
After Width: | Height: | Size: 906 B |
@@ -195,6 +195,7 @@ Image|Bild
|
||||
Immediate Only|Nur sofort
|
||||
Import|Importieren
|
||||
Import Building Blocks|Bausteine importieren
|
||||
Import Netlist|Netlist importieren
|
||||
Import Variants|Varianten importieren
|
||||
In-Line D-44|Inline D-44
|
||||
In-line|Inline
|
||||
@@ -231,6 +232,7 @@ Loadline Editor|Kennliniendiagramm
|
||||
Lock|Sperren
|
||||
Log|Log
|
||||
Log In|Anmelden
|
||||
Look for|Suchen
|
||||
Log Out|Abmelden
|
||||
Lug Size|Lötpadgröße
|
||||
Lugs|Lötpad
|
||||
@@ -303,6 +305,7 @@ Power Rating|Nennleistung
|
||||
Power Strip|Steckdosenleiste
|
||||
Primary CT|Primärer Mittelabgriff
|
||||
Print...|Drucken...
|
||||
Project|Projekt
|
||||
Project Cloud|Projekt Cloud
|
||||
Public Announcement|Öffentliche Ankündigung
|
||||
Radius|Radius
|
||||
@@ -360,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
|
||||
@@ -457,3 +461,34 @@ Zoom|Vergrößern
|
||||
base of 10|Zehntel
|
||||
base of 2|Achtel
|
||||
hold %s and drag to unstick from|Halten Sie X gedrückt und ziehen Sie, zum lösen von
|
||||
Are you sure you want to clear the chat history for this project? This action cannot be undone!|Sind Sie sicher, dass Sie den Chat-Verlauf für dieses Projekt löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden!
|
||||
Component reordering is possible only when Project Explorer is sorted by z-index.|Komponenten können nur neu angeordnet werden, wenn der Projekt-Explorer nach z-index sortiert ist.
|
||||
Could not apply changes. Check the log for details.|Änderungen konnten nicht angewendet werden. Überprüfen Sie die Protokolldatei für Details.
|
||||
Exporting outline for board %s to file %s|Exportiere Umriss für Platte %s in Datei %s
|
||||
Failed to export the project to gerber. Please check the log for details|Fehler beim Exportieren des Projekts zu Gerber. Bitte überprüfen Sie die Protokolldatei für Details
|
||||
Forgot Password?|Passwort vergessen?
|
||||
Gerber Export|Gerber Export
|
||||
Gerber export completed successfully. The following files were created:%s|Gerber-Export erfolgreich abgeschlossen. Die folgenden Dateien wurden erstellt:%s
|
||||
Get Premium|Premium erhalten
|
||||
Netlist files|Netlist-Dateien
|
||||
No proximity issues detected.|Keine Näherungsprobleme erkannt.
|
||||
Nothing to export.|Nichts zu exportieren.
|
||||
Password|Passwort
|
||||
Send|Senden
|
||||
There are some components that are outside of the bounds of boards.%sThey will be ignored unless you add a board component underneath them.%sDo you want to continue?|Es gibt einige Komponenten, die außerhalb der Grenzen der Platten liegen.%sSie werden ignoriert, es sei denn, Sie fügen eine Plattenkomponente darunter hinzu.%sMöchten Sie fortfahren?
|
||||
User Name|Benutzername
|
||||
AI Circuit Analyzer|KI-Schaltungsanalysator
|
||||
Cloud|Cloud
|
||||
Compare With...|Vergleichen mit...
|
||||
Connect to Patreon|Mit Patreon verbinden
|
||||
Export|Exportieren
|
||||
Export to Gerber|Als Gerber exportieren
|
||||
Generate DIYLC Netlist (excl. Switches)|DIYLC-Netzliste erstellen (ohne Schalter)
|
||||
Generate DIYLC Netlist (incl. Switches)|DIYLC-Netzliste erstellen (mit Schaltern)
|
||||
Generate Spice Netlist (excl. Switches)|Spice-Netzliste erstellen (ohne Schalter)
|
||||
Generate Spice Netlist (incl. Switches)|Spice-Netzliste erstellen (mit Schaltern)
|
||||
Silkscreen|Siebdruck
|
||||
Virtual Multimeter (excl. Switches)|Virtuelles Multimeter (ohne Schalter)
|
||||
Virtual Multimeter (incl. Switches)|Virtuelles Multimeter (mit Schaltern)
|
||||
Become a Patreon and enjoy perks|Werden Sie Patreon und genießen Sie Vorteile
|
||||
Join Patreon|Patreon beitreten
|
||||
|
||||
@@ -300,6 +300,7 @@ Power Rating
|
||||
Power Strip
|
||||
Primary CT
|
||||
Print...
|
||||
Project
|
||||
Project Cloud
|
||||
Public Announcement
|
||||
Radius
|
||||
@@ -351,6 +352,7 @@ Shape
|
||||
Shapes
|
||||
Show Bracket
|
||||
Show Grid
|
||||
Show Resize Dimensions
|
||||
Show Rulers
|
||||
Shroud
|
||||
Shrouded
|
||||
@@ -462,4 +464,35 @@ Update Axis
|
||||
Apply Variant
|
||||
Save as Building Block
|
||||
Select
|
||||
Unlock
|
||||
Unlock
|
||||
Are you sure you want to clear the chat history for this project? This action cannot be undone!
|
||||
Component reordering is possible only when Project Explorer is sorted by z-index.
|
||||
Could not apply changes. Check the log for details.
|
||||
Exporting outline for board %s to file %s
|
||||
Failed to export the project to gerber. Please check the log for details
|
||||
Forgot Password?
|
||||
Gerber Export
|
||||
Gerber export completed successfully. The following files were created:%s
|
||||
Get Premium
|
||||
Netlist files
|
||||
No proximity issues detected.
|
||||
Nothing to export.
|
||||
Password
|
||||
Send
|
||||
There are some components that are outside of the bounds of boards.%sThey will be ignored unless you add a board component underneath them.%sDo you want to continue?
|
||||
User Name
|
||||
AI Circuit Analyzer
|
||||
Cloud
|
||||
Compare With...
|
||||
Connect to Patreon
|
||||
Export
|
||||
Export to Gerber
|
||||
Generate DIYLC Netlist (excl. Switches)
|
||||
Generate DIYLC Netlist (incl. Switches)
|
||||
Generate Spice Netlist (excl. Switches)
|
||||
Generate Spice Netlist (incl. Switches)
|
||||
Silkscreen
|
||||
Virtual Multimeter (excl. Switches)
|
||||
Virtual Multimeter (incl. Switches)
|
||||
Become a Patreon and enjoy perks
|
||||
Join Patreon
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user