mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-07 12:24:58 +10:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c556910adb | |||
| cdcd712d60 | |||
| 07fe3b17b9 | |||
| bc64cdd0be | |||
| 28b12c71a3 | |||
| f334385967 |
@@ -11,6 +11,9 @@
|
||||
<option name="ENABLED" value="true" />
|
||||
</pattern>
|
||||
</extension>
|
||||
<RunnerSettings RunnerId="Profile ">
|
||||
<option name="yk-options-v2" value="<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties> <entry key="advanced-options">on_exit=snapshot</entry> <entry key="jfr">Off</entry> <entry key="cpu">Sampling</entry> <entry key="monitor">Off</entry> <entry key="telemetry">On</entry> <entry key="thread">States and stacks</entry> <entry key="time">Wall time</entry> <entry key="alloc">Off</entry> <entry key="launch-ui">true</entry> <entry key="exceptions">On</entry> </properties> " />
|
||||
</RunnerSettings>
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
|
||||
@@ -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.plugins.chatbot.model;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* The rectangle a component occupies on the canvas, in grid units like {@link AiPoint}.
|
||||
*/
|
||||
public record AiBounds(BigDecimal gridX, BigDecimal gridY, BigDecimal gridWidth,
|
||||
BigDecimal gridHeight) {
|
||||
}
|
||||
@@ -26,7 +26,9 @@ import java.util.Map;
|
||||
|
||||
/**
|
||||
* @param properties current values of the editable properties
|
||||
* @param bounds where the component is drawn, the only position given for components without
|
||||
* terminals, such as boards and labels
|
||||
*/
|
||||
public record AiComponent(String id, String type, String value, Map<String, String> properties,
|
||||
List<AiTerminal> terminals) {
|
||||
AiBounds bounds, List<AiTerminal> terminals) {
|
||||
}
|
||||
|
||||
@@ -4,8 +4,14 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @param selection ids of the components the user has selected, which a request such as "change
|
||||
* this resistor" refers to
|
||||
* @param components in z-order, the last one is drawn on top
|
||||
*/
|
||||
public record AiProject(Map<String, String> metadata,
|
||||
List<String> tags,
|
||||
List<String> selection,
|
||||
List<AiComponent> components,
|
||||
Map<String, Set<String>> nets,
|
||||
List<AiSwitch> switches) {
|
||||
|
||||
@@ -3,5 +3,9 @@ package org.diylc.plugins.chatbot.model;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @param connectedTerminalGroups terminals the switch ties together in this position, each group
|
||||
* holding every terminal of one connection
|
||||
*/
|
||||
public record AiSwitchPosition(String name, List<Set<Integer>> connectedTerminalGroups) {
|
||||
}
|
||||
|
||||
+8
-1
@@ -130,8 +130,15 @@ public class AiEditScriptEditor implements IProjectEditor {
|
||||
}
|
||||
|
||||
private void apply(AiEditOperation op, IAiEditHandler handler, AiEditContext context) {
|
||||
// an action this version does not know is skipped, as it always has been
|
||||
// the service may be newer than this build and propose actions it does not know yet; those
|
||||
// are skipped so that the rest of the script still applies
|
||||
if (handler == null) {
|
||||
if (op.getAction() != null) {
|
||||
warnings.add("Operation '" + op.getAction() + "'"
|
||||
+ (op.getComponentName() != null ? " on " + op.getComponentName() : "")
|
||||
+ " is not supported by this version of DIYLC and was skipped."
|
||||
+ " Please upgrade to the latest version.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
||||
+102
-21
@@ -23,10 +23,12 @@ package org.diylc.plugins.chatbot.service;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
@@ -37,6 +39,7 @@ import org.diylc.core.IContinuity;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.ISwitch;
|
||||
import org.diylc.core.Project;
|
||||
import org.diylc.core.measures.AbstractMeasure;
|
||||
import org.diylc.netlist.Group;
|
||||
import org.diylc.netlist.Netlist;
|
||||
import org.diylc.netlist.NetlistBuilder;
|
||||
@@ -52,7 +55,14 @@ public class AiProjectBuilder {
|
||||
public static final String ID_SEPARATOR = "#";
|
||||
private static final int MIN_ID_SUFFIX_LENGTH = 4;
|
||||
|
||||
public static AiProject build(Project project, List<ContinuityArea> continuityAreas) {
|
||||
/**
|
||||
* @param drawnBounds the outline each component was last drawn with, or null for one that has not
|
||||
* been drawn
|
||||
* @param selection components the user has selected
|
||||
*/
|
||||
public static AiProject build(Project project, List<ContinuityArea> continuityAreas,
|
||||
Function<IDIYComponent<?>, Rectangle2D> drawnBounds,
|
||||
Collection<IDIYComponent<?>> selection) {
|
||||
|
||||
Map<String, String> metadata = getMetadata(project);
|
||||
List<String> tags = getTags(project);
|
||||
@@ -67,7 +77,14 @@ public class AiProjectBuilder {
|
||||
|
||||
double gridSpacingPx = project.getGridSpacing().convertToPixels();
|
||||
List<AiComponent> components = project.getComponents().stream()
|
||||
.map(c -> mapComponent(c, ids.get(c), gridSpacingPx, contacts, wireEndNets))
|
||||
.map(c -> mapComponent(c, ids.get(c), gridSpacingPx, contacts, wireEndNets,
|
||||
drawnBounds.apply(c)))
|
||||
.toList();
|
||||
|
||||
Set<IDIYComponent<?>> selected = new HashSet<>(selection);
|
||||
List<String> selectedIds = project.getComponents().stream()
|
||||
.filter(selected::contains)
|
||||
.map(ids::get)
|
||||
.toList();
|
||||
|
||||
List<AiSwitch> switches =
|
||||
@@ -76,7 +93,8 @@ public class AiProjectBuilder {
|
||||
.map(x -> (ISwitch) x)
|
||||
.map(sw -> mapSwitch(sw, ids)).toList();
|
||||
|
||||
return new AiProject(metadata, tags, components, nets, switches);
|
||||
return new AiProject(metadata, tags, selectedIds.isEmpty() ? null : selectedIds, components,
|
||||
nets, switches);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,9 +152,9 @@ public class AiProjectBuilder {
|
||||
if (project.getTitle() != null) metadata.put("title", project.getTitle());
|
||||
if (project.getAuthor() != null) metadata.put("author", project.getAuthor());
|
||||
if (project.getDescription() != null) metadata.put("description", project.getDescription());
|
||||
metadata.put("gridSpacing", project.getGridSpacing().toString());
|
||||
metadata.put("width", project.getWidth().toString());
|
||||
metadata.put("height", project.getHeight().toString());
|
||||
metadata.put("gridSpacing", ComponentCatalogGenerator.formatMeasure(project.getGridSpacing()));
|
||||
metadata.put("width", ComponentCatalogGenerator.formatMeasure(project.getWidth()));
|
||||
metadata.put("height", ComponentCatalogGenerator.formatMeasure(project.getHeight()));
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@@ -313,21 +331,52 @@ public class AiProjectBuilder {
|
||||
List<AiSwitchPosition> positions = new ArrayList<>();
|
||||
IDIYComponent<?> c = (IDIYComponent<?>) sw;
|
||||
for (int p = 0; p < sw.getPositionCount(); p++) {
|
||||
List<Set<Integer>> connections = new ArrayList<>();
|
||||
for (int i = 0; i < c.getControlPointCount() - 1; i++) {
|
||||
for (int j = i + 1; j < c.getControlPointCount(); j++) {
|
||||
if (sw.arePointsConnected(i, j, p)) {
|
||||
connections.add(Set.of(i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
positions.add(new AiSwitchPosition(sw.getPositionName(p), connections));
|
||||
positions.add(new AiSwitchPosition(sw.getPositionName(p),
|
||||
getConnectedGroups(sw, c.getControlPointCount(), p)));
|
||||
}
|
||||
return new AiSwitch(ids.get(c), positions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminals that are connected in the given position, gathered into groups rather than listed in
|
||||
* pairs, which grow with the square of the terminals that a pole ties together.
|
||||
*/
|
||||
private static List<Set<Integer>> getConnectedGroups(ISwitch sw, int pointCount, int position) {
|
||||
List<Set<Integer>> groups = new ArrayList<>();
|
||||
Set<Integer> grouped = new HashSet<>();
|
||||
for (int i = 0; i < pointCount; i++) {
|
||||
if (grouped.contains(i)) {
|
||||
continue;
|
||||
}
|
||||
Set<Integer> group = new TreeSet<>();
|
||||
Deque<Integer> pending = new ArrayDeque<>();
|
||||
pending.push(i);
|
||||
while (!pending.isEmpty()) {
|
||||
int point = pending.pop();
|
||||
if (!group.add(point)) {
|
||||
continue;
|
||||
}
|
||||
for (int j = 0; j < pointCount; j++) {
|
||||
if (!group.contains(j)
|
||||
&& sw.arePointsConnected(Math.min(point, j), Math.max(point, j), position)) {
|
||||
pending.push(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
grouped.addAll(group);
|
||||
if (group.size() > 1) {
|
||||
groups.add(group);
|
||||
}
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param drawnBounds the outline the component was last drawn with, or null when it has not been
|
||||
* drawn
|
||||
*/
|
||||
static AiComponent mapComponent(IDIYComponent<?> component, String id, double gridSpacingPx,
|
||||
Map<Node, List<String>> contacts, Map<Node, String> wireEndNets) {
|
||||
Map<Node, List<String>> contacts, Map<Node, String> wireEndNets, Rectangle2D drawnBounds) {
|
||||
|
||||
boolean connectivity = isConnectivity(component);
|
||||
List<AiTerminal> terminals = new ArrayList<>();
|
||||
@@ -347,10 +396,15 @@ public class AiProjectBuilder {
|
||||
}
|
||||
|
||||
String compType = component.getClass().getCanonicalName().replace("org.diylc.components.", "");
|
||||
Object rawValue = component.getValue();
|
||||
String value = rawValue instanceof AbstractMeasure<?> measure
|
||||
? ComponentCatalogGenerator.formatMeasure(measure)
|
||||
: rawValue == null ? null : rawValue.toString();
|
||||
Map<String, String> properties = getProperties(component);
|
||||
return new AiComponent(id, compType,
|
||||
component.getValue() == null ? null : component.getValue().toString(),
|
||||
value == null || value.isEmpty() ? null : value,
|
||||
properties.isEmpty() ? null : properties,
|
||||
createAiBounds(component, drawnBounds, gridSpacingPx),
|
||||
terminals.isEmpty() ? null : terminals);
|
||||
}
|
||||
|
||||
@@ -379,11 +433,38 @@ public class AiProjectBuilder {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static AiPoint createAiPoint(Point2D controlPoint, double gridSpacingPx) {
|
||||
/**
|
||||
* Bounds from the last repaint, which cover everything the component draws, such as a body that
|
||||
* reaches past its leads or the text of a label, where its control points alone do not. A
|
||||
* component that has not been drawn yet falls back to the rectangle around its control points.
|
||||
*/
|
||||
private static AiBounds createAiBounds(IDIYComponent<?> component, Rectangle2D drawnBounds,
|
||||
double gridSpacingPx) {
|
||||
Rectangle2D bounds = drawnBounds;
|
||||
if (bounds == null) {
|
||||
for (int i = 0; i < component.getControlPointCount(); i++) {
|
||||
Point2D point = component.getControlPoint(i);
|
||||
if (bounds == null) {
|
||||
bounds = new Rectangle2D.Double(point.getX(), point.getY(), 0, 0);
|
||||
} else {
|
||||
bounds.add(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bounds == null) {
|
||||
return null;
|
||||
}
|
||||
return new AiBounds(toGrid(bounds.getX(), gridSpacingPx), toGrid(bounds.getY(), gridSpacingPx),
|
||||
toGrid(bounds.getWidth(), gridSpacingPx), toGrid(bounds.getHeight(), gridSpacingPx));
|
||||
}
|
||||
|
||||
BigDecimal gridX = BigDecimal.valueOf(controlPoint.getX() / gridSpacingPx).setScale(2, RoundingMode.HALF_UP);
|
||||
BigDecimal gridY = BigDecimal.valueOf(controlPoint.getY() / gridSpacingPx).setScale(2, RoundingMode.HALF_UP);
|
||||
return new AiPoint(gridX, gridY);
|
||||
private static AiPoint createAiPoint(Point2D controlPoint, double gridSpacingPx) {
|
||||
return new AiPoint(toGrid(controlPoint.getX(), gridSpacingPx),
|
||||
toGrid(controlPoint.getY(), gridSpacingPx));
|
||||
}
|
||||
|
||||
private static BigDecimal toGrid(double px, double gridSpacingPx) {
|
||||
return BigDecimal.valueOf(px / gridSpacingPx).setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
static String mapTerminal(Node terminalRef, Map<IDIYComponent<?>, String> ids) {
|
||||
|
||||
+9
-3
@@ -40,8 +40,10 @@ import org.diylc.plugins.chatbot.model.*;
|
||||
import org.diylc.plugins.cloud.model.IServiceAPI;
|
||||
import org.diylc.plugins.cloud.service.CloudException;
|
||||
import org.diylc.plugins.cloud.service.NotLoggedInException;
|
||||
import org.diylc.presenter.ComponentArea;
|
||||
import org.diylc.presenter.ComponentProcessor;
|
||||
import org.diylc.presenter.ContinuityArea;
|
||||
import org.diylc.presenter.DrawingManager;
|
||||
import org.diylc.utils.FileUtils;
|
||||
import org.diylc.utils.ReflectionUtils;
|
||||
|
||||
@@ -140,7 +142,7 @@ public class ChatbotService {
|
||||
plugInPort.getCloudService().getMachineId(), fileName, circuitFile, clientAnalysisFile, prompt);
|
||||
|
||||
// TODO: comment out before the release, responses are logged for testing only
|
||||
LOG.debug("Raw chatbot response: " + rawResponse);
|
||||
// LOG.debug("Raw chatbot response: " + rawResponse);
|
||||
|
||||
if (rawResponse == null || rawResponse.isBlank()) {
|
||||
throw new CloudException(SERVER_ERROR_PLEASE_TRY_AGAIN);
|
||||
@@ -309,8 +311,12 @@ public class ChatbotService {
|
||||
if (plugInPort.getCurrentProject().getComponents().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
List<ContinuityArea> continuityAreas = plugInPort.getDrawingManager().getContinuityAreas();
|
||||
return AiProjectBuilder.build(plugInPort.getCurrentProject(), continuityAreas);
|
||||
DrawingManager drawingManager = plugInPort.getDrawingManager();
|
||||
List<ContinuityArea> continuityAreas = drawingManager.getContinuityAreas();
|
||||
return AiProjectBuilder.build(plugInPort.getCurrentProject(), continuityAreas, component -> {
|
||||
ComponentArea area = drawingManager.getComponentArea(component);
|
||||
return area == null ? null : area.getOutlineBounds();
|
||||
}, plugInPort.getSelectedComponents());
|
||||
}
|
||||
|
||||
public SubscriptionEntity getSubscriptionInfo() throws NotLoggedInException {
|
||||
|
||||
+15
-1
@@ -350,7 +350,7 @@ public class ComponentCatalogGenerator {
|
||||
|
||||
// Measures - format with unit
|
||||
if (value instanceof AbstractMeasure) {
|
||||
return value.toString();
|
||||
return formatMeasure((AbstractMeasure<?>) value);
|
||||
}
|
||||
|
||||
// Enums
|
||||
@@ -369,4 +369,18 @@ public class ComponentCatalogGenerator {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a measure the way {@link AbstractMeasure#toString()} does, but always with a decimal
|
||||
* point, where the measure's own format follows the locale and writes a decimal comma in some.
|
||||
*/
|
||||
static String formatMeasure(AbstractMeasure<?> measure) {
|
||||
if (measure.getValue() == null) {
|
||||
return null;
|
||||
}
|
||||
// four decimals rounded half-even, like the "0.####" DecimalFormat the measure uses
|
||||
return java.math.BigDecimal.valueOf(measure.getValue())
|
||||
.setScale(4, java.math.RoundingMode.HALF_EVEN).stripTrailingZeros().toPlainString()
|
||||
+ measure.getUnit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -662,53 +662,21 @@
|
||||
<minor>9</minor>
|
||||
<build>0</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2026-10-17 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Rotating and mirroring the selection with 'Sticky Points' enabled now keeps the circuit connected, the same way moving it does - wires and leads stuck to the selection stay attached to their pins, and other rigid components stuck to it are rotated or mirrored along with it</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Improved sorting of switching combinations in 'Analyze Guitar Diagrams' tool</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Fixed Guitar Analyzer logic that merges switching configuration that produce the same outcome; in some cases it would remove switches from the list when it shouldn't</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Gerber export on computers set to a language that uses a decimal comma (e.g. German, French, Serbian) produced files in which drill holes, pads and traces had the wrong size - a 0.8mm hole came out as 0mm and traces and the board outline had no width, so the files could not be used for fabrication</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Zooming out until the drawing is smaller than the window made the toolbar above the canvas grow taller and squeeze the canvas; the toolbar now keeps its height at every zoom level</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Fixed a bug that makes it impossible to clear a property with dynamic values, e.g. 'Selected Position' of a switch</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Fixed a bug that makes 'Selected Position' list sometimes shows wrong values</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>6</major>
|
||||
<minor>10</minor>
|
||||
<build>0</build>
|
||||
</versionNumber>
|
||||
<releaseDate>2026-10-19 00:00:00.000 CET</releaseDate>
|
||||
<releaseDate>2026-10-30 00:00:00.000 CET</releaseDate>
|
||||
<name></name>
|
||||
<changes>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>AI Assistant can actively participate in circuit design, make changes to the project and designs circuits from scratch</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added 6PDT and 6PDT On/Off/On toggle switch types</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Rotating and mirroring the selection with 'Sticky Points' enabled now keeps the circuit connected, the same way moving it does - wires and leads stuck to the selection stay attached to their pins, and other rigid components stuck to it are rotated or mirrored along with it</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Allow active position selection for 'Rotary Switch Sealed', 'DIP Switch', 'Latching Switch Symbol', 'LP Switch', 'S1 Switch', allowing for more accurate circuit analysis and debugging</description>
|
||||
@@ -729,6 +697,22 @@
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>'IEC Socket', 'Plastic DC Jack', 'Multi-Section Capacitor' and 'Mini Relay' terminals are now labeled by function (Earth, Line, Neutral; Sleeve, Switch, Center; the shared negative and each section's positive; coil, COM, NC and NO) instead of plain numbers, so netlists show what each terminal does</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Improved quality of output from the 'AI Circuit Analyzer'</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>'AI Circuit Analyzer' offers to log in instead of just showing the error</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>'AI Assistant' is now aware of what is selected so natural language prompts can refer to selected components</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Improved sorting of switching combinations in 'Analyze Guitar Diagrams' tool</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Single coil pickups rotated by 90 or 270 degrees had their start and finish lugs swapped relative to the pickup body</description>
|
||||
@@ -737,6 +721,26 @@
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>'Analyze Guitar Diagrams' ignored 'Stingray Bass Pickup', so a diagram built around it was reported as having no pickups connected to the output jack, and its coils were left out of the hum-cancelling and phase checks</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Fixed 'Analyze Guitar Diagrams' logic that merges switching configuration that produce the same outcome; in some cases it would remove switches from the list when it shouldn't</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Gerber export on computers set to a language that uses a decimal comma (e.g. German, French, Serbian) produced files in which drill holes, pads and traces had the wrong size - a 0.8mm hole came out as 0mm and traces and the board outline had no width, so the files could not be used for fabrication</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Zooming out until the drawing is smaller than the window made the toolbar above the canvas grow taller and squeeze the canvas; the toolbar now keeps its height at every zoom level</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Fixed a bug that makes it impossible to clear a property with dynamic values, e.g. 'Selected Position' of a switch</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>BUG_FIX</changeType>
|
||||
<description>Fixed a bug that makes 'Selected Position' list sometimes shows wrong values</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
</changes>
|
||||
<url>https://github.com/bancika/diy-layout-creator/releases</url>
|
||||
</org.diylc.appframework.update.Version>
|
||||
|
||||
+30
@@ -27,6 +27,7 @@ import static org.junit.Assert.assertTrue;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -162,6 +163,35 @@ public class AiEditScriptEditorTest {
|
||||
assertTrue(editor.getWarnings().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsupportedOperationIsSkippedWithWarning() {
|
||||
DummyComponent comp = new DummyComponent();
|
||||
comp.setName("R1");
|
||||
project.getComponents().add(comp);
|
||||
|
||||
AiEditOperation unsupported = new AiEditOperation();
|
||||
unsupported.setAction("teleport");
|
||||
unsupported.setComponentName("R1");
|
||||
|
||||
AiEditOperation remove = new AiEditOperation();
|
||||
remove.setAction("remove");
|
||||
remove.setComponentName("R1");
|
||||
|
||||
AiEditScript script = new AiEditScript();
|
||||
script.setExplanation("Teleport and remove R1");
|
||||
script.setOperations(Arrays.asList(unsupported, remove));
|
||||
|
||||
AiEditScriptEditor editor = new AiEditScriptEditor(script);
|
||||
editor.edit(project, new HashSet<>());
|
||||
|
||||
assertTrue(project.getComponents().isEmpty());
|
||||
assertEquals(1, editor.getWarnings().size());
|
||||
String warning = editor.getWarnings().get(0);
|
||||
assertTrue(warning.contains("'teleport' on R1"));
|
||||
assertTrue(warning.contains("not supported"));
|
||||
assertTrue(warning.contains("upgrade"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModifyComponent() {
|
||||
DummyComponent comp = new DummyComponent();
|
||||
|
||||
+93
-1
@@ -27,9 +27,12 @@ import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
@@ -37,18 +40,27 @@ import java.util.UUID;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.diylc.common.Orientation;
|
||||
import org.diylc.components.boards.PerfBoard;
|
||||
import org.diylc.components.boards.TagStrip;
|
||||
import org.diylc.components.connectivity.HookupWire;
|
||||
import org.diylc.components.connectivity.SolderLug;
|
||||
import org.diylc.components.connectivity.SolderPad;
|
||||
import org.diylc.components.guitar.LPSwitch;
|
||||
import org.diylc.components.guitar.SingleCoilPickup;
|
||||
import org.diylc.components.passive.Resistor;
|
||||
import org.diylc.components.semiconductors.TransistorTO92;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.Project;
|
||||
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.plugins.chatbot.model.AiBounds;
|
||||
import org.diylc.plugins.chatbot.model.AiComponent;
|
||||
import org.diylc.plugins.chatbot.model.AiEditOperation;
|
||||
import org.diylc.plugins.chatbot.model.AiEditScript;
|
||||
import org.diylc.plugins.chatbot.model.AiProject;
|
||||
import org.diylc.plugins.chatbot.model.AiSwitch;
|
||||
import org.diylc.plugins.chatbot.model.AiTerminal;
|
||||
|
||||
/**
|
||||
@@ -240,6 +252,86 @@ public class AiProjectBuilderTest {
|
||||
assertEquals("_180", component(export, "Neck").properties().get("Orientation"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void measuresUseADecimalPointInAnyLocale() {
|
||||
Locale locale = Locale.getDefault();
|
||||
Locale.setDefault(Locale.GERMANY);
|
||||
try {
|
||||
Resistor resistor = resistor();
|
||||
resistor.setValue(new Resistance(4.7d, ResistanceUnit.K));
|
||||
PerfBoard board = new PerfBoard();
|
||||
board.setName("Board1");
|
||||
board.setSpacing(new Size(0.15d, SizeUnit.in));
|
||||
|
||||
AiProject export = export(resistor, board);
|
||||
|
||||
assertEquals("4.7K", component(export, "R1").value());
|
||||
assertEquals("0.15in", component(export, "Board1").properties().get("Spacing"));
|
||||
assertEquals("0.1in", export.metadata().get("gridSpacing"));
|
||||
} finally {
|
||||
Locale.setDefault(locale);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyValueIsLeftOut() {
|
||||
TransistorTO92 transistor = new TransistorTO92();
|
||||
transistor.setName("Q1");
|
||||
|
||||
assertNull(component(export(transistor), "Q1").value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void drawnBoundsAreExportedInGridUnits() {
|
||||
PerfBoard board = new PerfBoard();
|
||||
board.setName("Board1");
|
||||
Project project = project(board);
|
||||
double grid = project.getGridSpacing().convertToPixels();
|
||||
Rectangle2D drawn = new Rectangle2D.Double(2 * grid, 3 * grid, 10 * grid, 5.5 * grid);
|
||||
|
||||
AiProject export = AiProjectBuilder.build(project, new ArrayList<>(),
|
||||
c -> c == board ? drawn : null, List.of());
|
||||
|
||||
assertEquals(new AiBounds(new BigDecimal("2.00"), new BigDecimal("3.00"),
|
||||
new BigDecimal("10.00"), new BigDecimal("5.50")), component(export, "Board1").bounds());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void undrawnComponentIsBoundedByItsControlPoints() {
|
||||
Project project = project(resistor());
|
||||
double grid = project.getGridSpacing().convertToPixels();
|
||||
|
||||
AiBounds bounds = component(export(resistor()), "R1").bounds();
|
||||
|
||||
assertEquals(FIRST_LEAD.getX() / grid, bounds.gridX().doubleValue(), 0.01);
|
||||
assertEquals((SECOND_LEAD.getX() - FIRST_LEAD.getX()) / grid,
|
||||
bounds.gridWidth().doubleValue(), 0.01);
|
||||
assertEquals(0, bounds.gridHeight().doubleValue(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void selectionListsTheIdsOfSelectedComponents() {
|
||||
HookupWire wire = wire("W1", SECOND_LEAD, LOOSE_END);
|
||||
Project project = project(resistor(), wire);
|
||||
|
||||
assertEquals(List.of("W1"), AiProjectBuilder.build(project, new ArrayList<>(), c -> null,
|
||||
List.of(wire)).selection());
|
||||
assertNull(export(resistor()).selection());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void switchTerminalsTiedTogetherFormOneGroup() {
|
||||
LPSwitch lpSwitch = new LPSwitch();
|
||||
lpSwitch.setName("SW1");
|
||||
|
||||
AiSwitch exported = export(lpSwitch).switches().get(0);
|
||||
|
||||
assertEquals(List.of(Set.of(1, 2)), exported.positions().get(0).connectedTerminalGroups());
|
||||
assertEquals(List.of(Set.of(1, 2, 3)),
|
||||
exported.positions().get(1).connectedTerminalGroups());
|
||||
assertEquals(List.of(Set.of(2, 3)), exported.positions().get(2).connectedTerminalGroups());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void identityAndCosmeticPropertiesAreLeftOut() {
|
||||
Map<String, String> properties = component(export(resistor()), "R1").properties();
|
||||
@@ -267,7 +359,7 @@ public class AiProjectBuilderTest {
|
||||
}
|
||||
|
||||
private static AiProject export(IDIYComponent<?>... components) {
|
||||
return AiProjectBuilder.build(project(components), new ArrayList<>());
|
||||
return AiProjectBuilder.build(project(components), new ArrayList<>(), c -> null, List.of());
|
||||
}
|
||||
|
||||
private static Project project(IDIYComponent<?>... components) {
|
||||
|
||||
+3
-2
@@ -126,10 +126,11 @@ public class ProjectFingerprintTest {
|
||||
}
|
||||
|
||||
private static String fingerprint(Project project) {
|
||||
return ChatbotService.fingerprint(AiProjectBuilder.build(project, new ArrayList<>()));
|
||||
return ChatbotService.fingerprint(
|
||||
AiProjectBuilder.build(project, new ArrayList<>(), c -> null, List.of()));
|
||||
}
|
||||
|
||||
private static AiProject aiProject(Map<String, Set<String>> nets) {
|
||||
return new AiProject(Map.of(), List.of(), List.of(), nets, List.of());
|
||||
return new AiProject(Map.of(), List.of(), null, List.of(), nets, List.of());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,11 @@
|
||||
<artifactId>commons-compiler</artifactId>
|
||||
<version>3.1.12</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.formdev</groupId>
|
||||
<artifactId>flatlaf</artifactId>
|
||||
<version>3.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.java.balloontip</groupId>
|
||||
<artifactId>balloontip</artifactId>
|
||||
|
||||
@@ -25,7 +25,6 @@ import java.util.Arrays;
|
||||
import java.util.Properties;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.UIManager;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.log4j.PropertyConfigurator;
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
@@ -42,6 +41,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.LookAndFeelUtils;
|
||||
|
||||
/**
|
||||
* Main class that runs DIYLC.
|
||||
@@ -136,7 +136,7 @@ public class DIYLCStarter {
|
||||
LOG.info("Configuration dump end.");
|
||||
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
LookAndFeelUtils.applyLookAndFeel();
|
||||
|
||||
// Apply configurable font scaling from VM argument: -Dorg.diylc.fontScaleFactor=X
|
||||
org.diylc.utils.FontScalingUtils.applyFontScaling();
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package org.diylc.swing.actions.analyze;
|
||||
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.lang.LangUtil;
|
||||
import org.diylc.plugins.cloud.service.NotLoggedInException;
|
||||
import org.diylc.swing.ISwingUI;
|
||||
import org.diylc.swing.plugins.cloud.actions.LoginAction;
|
||||
import org.diylc.swingframework.TextDialog;
|
||||
import org.diylc.utils.IconLoader;
|
||||
|
||||
@@ -55,6 +58,10 @@ public class AiAnalyzeAction extends AbstractAction {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (!plugInPort.getCloudService().isLoggedIn()) {
|
||||
offerLogin(e);
|
||||
return;
|
||||
}
|
||||
|
||||
String message = WAITING_MESSAGES[(int) (Math.random() * WAITING_MESSAGES.length)];
|
||||
JOptionPane optionPane = new JOptionPane(message,
|
||||
@@ -86,6 +93,10 @@ public class AiAnalyzeAction extends AbstractAction {
|
||||
new TextDialog(swingUI.getOwnerFrame().getRootPane(), res, "AI Circuit Analyzer",
|
||||
new Dimension(800, 600)).setVisible(true);
|
||||
} catch (Exception ex) {
|
||||
if (ex.getCause() instanceof NotLoggedInException) {
|
||||
offerLogin(e);
|
||||
return;
|
||||
}
|
||||
String message =
|
||||
Optional.ofNullable(ex.getCause()).map(Throwable::getMessage).orElse(ex.getMessage());
|
||||
swingUI.showMessage(message, "AI Circuit Analyzer", ISwingUI.ERROR_MESSAGE);
|
||||
@@ -100,4 +111,15 @@ public class AiAnalyzeAction extends AbstractAction {
|
||||
worker.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void offerLogin(ActionEvent e) {
|
||||
int choice = swingUI.showConfirmDialog(
|
||||
LangUtil.translate(
|
||||
"AI Circuit Analyzer requires a DIYLC Cloud account. Would you like to log in now?"),
|
||||
LangUtil.translate("AI Circuit Analyzer"), ISwingUI.YES_NO_OPTION,
|
||||
ISwingUI.QUESTION_MESSAGE);
|
||||
if (choice == ISwingUI.YES_OPTION && LoginAction.logIn(plugInPort, swingUI)) {
|
||||
actionPerformed(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import javax.swing.SwingConstants;
|
||||
import javax.swing.border.MatteBorder;
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.appframework.miscutils.IConfigListener;
|
||||
import org.diylc.utils.LookAndFeelUtils;
|
||||
|
||||
/***
|
||||
* Panel that has a clickable line that can used to collapse and expand the contents of the panel.
|
||||
@@ -49,7 +50,8 @@ public class CollapsiblePanel extends JPanel {
|
||||
private MatteBorderEx border;
|
||||
private String visibilityConfigKey;
|
||||
|
||||
private Color defaultColor = Color.lightGray;
|
||||
private Color defaultColor =
|
||||
LookAndFeelUtils.themeColor(Color.lightGray, new Color(85, 88, 90));
|
||||
private Color highlightColor = Color.decode("#66CCFF");
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,11 +24,13 @@ import javax.swing.BorderFactory;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.UIManager;
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.swingframework.ButtonDialog;
|
||||
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.utils.IconLoader;
|
||||
import org.diylc.utils.LookAndFeelUtils;
|
||||
|
||||
public class InfoDialog extends ButtonDialog {
|
||||
|
||||
@@ -53,9 +55,10 @@ public class InfoDialog extends ButtonDialog {
|
||||
protected JComponent getMainComponent() {
|
||||
JLabel label = new JLabel("<html>" + message + "</html>");
|
||||
label.setIcon(IconLoader.Help.getIcon());
|
||||
label.setBackground(Color.white);
|
||||
label.setBackground(UIManager.getColor("List.background"));
|
||||
label.setOpaque(true);
|
||||
label.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.lightGray), BorderFactory.createEmptyBorder(16, 16, 16, 16)));
|
||||
label.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(0, 0, 1, 0,
|
||||
LookAndFeelUtils.themeColor(Color.lightGray, new Color(85, 88, 90))), BorderFactory.createEmptyBorder(16, 16, 16, 16)));
|
||||
label.setIconTextGap(8);
|
||||
return label;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.common.PropertyWrapper;
|
||||
import org.diylc.core.IView;
|
||||
import org.diylc.presenter.Presenter;
|
||||
import org.diylc.utils.LookAndFeelUtils;
|
||||
|
||||
public class TemplateDialog extends JDialog {
|
||||
|
||||
@@ -201,7 +202,8 @@ public class TemplateDialog extends JDialog {
|
||||
public JPanel getInfoPanel() {
|
||||
if (infoPanel == null) {
|
||||
infoPanel = new JPanel();
|
||||
infoPanel.setBackground(Color.decode("#FFFFCC"));
|
||||
infoPanel.setBackground(
|
||||
LookAndFeelUtils.themeColor(Color.decode("#FFFFCC"), Color.decode("#4B4B3A")));
|
||||
infoPanel.setBorder(new JTextField().getBorder());
|
||||
JLabel infoLabel =
|
||||
new JLabel(
|
||||
|
||||
+15
-11
@@ -23,6 +23,7 @@ package org.diylc.swing.plugins.cloud.actions;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import javax.swing.AbstractAction;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.common.ITask;
|
||||
import org.diylc.core.IView;
|
||||
import org.diylc.plugins.cloud.service.CloudException;
|
||||
@@ -46,27 +47,30 @@ public class LoginAction extends AbstractAction {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (logIn(cloudPlugIn.getPlugInPort(), cloudPlugIn.getSwingUI())) {
|
||||
cloudPlugIn.loggedIn();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean logIn(IPlugInPort plugInPort, ISwingUI swingUI) {
|
||||
LoginDialog dialog = DialogFactory.getInstance().createLoginDialog();
|
||||
do {
|
||||
dialog.setVisible(true);
|
||||
if (ButtonDialog.OK.equals(dialog.getSelectedButtonCaption())) {
|
||||
try {
|
||||
if (cloudPlugIn.getPlugInPort().getCloudService().logIn(dialog.getUserName(), dialog.getPassword())) {
|
||||
cloudPlugIn.getSwingUI().showMessage(
|
||||
"You have successfully logged into the system. You will remain logged in from this machine until logged out.",
|
||||
"Login Successful", IView.INFORMATION_MESSAGE);
|
||||
cloudPlugIn.loggedIn();
|
||||
break;
|
||||
if (plugInPort.getCloudService().logIn(dialog.getUserName(), dialog.getPassword())) {
|
||||
return true;
|
||||
} else {
|
||||
cloudPlugIn.getSwingUI().showMessage(
|
||||
swingUI.showMessage(
|
||||
"Could not login. Possible reasons are wrong credentials or lack of internet connection.",
|
||||
"Login Error", IView.ERROR_MESSAGE);
|
||||
}
|
||||
} catch (CloudException e1) {
|
||||
cloudPlugIn.getSwingUI().showMessage("Could not login. Error: " + e1.getMessage(), "Login Error", IView.ERROR_MESSAGE);
|
||||
swingUI.showMessage("Could not login. Error: " + e1.getMessage(), "Login Error", IView.ERROR_MESSAGE);
|
||||
}
|
||||
} else
|
||||
break;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -36,6 +36,7 @@ import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSeparator;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.UIManager;
|
||||
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.common.ITask;
|
||||
@@ -91,7 +92,7 @@ public class CommentDialog extends JDialog {
|
||||
private JPanel getListPanel() {
|
||||
if (listPanel == null) {
|
||||
listPanel = new JPanel(new GridBagLayout());
|
||||
listPanel.setBackground(Color.white);
|
||||
listPanel.setBackground(UIManager.getColor("List.background"));
|
||||
for (CommentEntity c : comments) {
|
||||
addComment(c);
|
||||
|
||||
@@ -107,7 +108,7 @@ public class CommentDialog extends JDialog {
|
||||
private JPanel getMainPanel() {
|
||||
if (mainPanel == null) {
|
||||
mainPanel = new JPanel(new GridBagLayout());
|
||||
mainPanel.setBackground(Color.white);
|
||||
mainPanel.setBackground(UIManager.getColor("List.background"));
|
||||
|
||||
GridBagConstraints gbc = new GridBagConstraints();
|
||||
gbc.gridx = 0;
|
||||
|
||||
+2
-2
@@ -21,7 +21,6 @@
|
||||
*/
|
||||
package org.diylc.swing.plugins.cloud.view.browser;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
@@ -37,6 +36,7 @@ import javax.swing.JRootPane;
|
||||
import javax.swing.JTabbedPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import org.apache.log4j.Logger;
|
||||
@@ -167,7 +167,7 @@ public class CloudBrowserFrame extends JFrame implements ISimpleView {
|
||||
if (searchPanel == null) {
|
||||
searchPanel = new JPanel(new GridBagLayout());
|
||||
GridBagConstraints gbc = new GridBagConstraints();
|
||||
searchPanel.setBackground(Color.white);
|
||||
searchPanel.setBackground(UIManager.getColor("List.background"));
|
||||
gbc.anchor = GridBagConstraints.NORTH;
|
||||
gbc.insets = new Insets(2, 2, 2, 2);
|
||||
gbc.gridx = 0;
|
||||
|
||||
+3
-3
@@ -18,7 +18,6 @@
|
||||
*/
|
||||
package org.diylc.swing.plugins.cloud.view.browser;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.Graphics;
|
||||
@@ -48,6 +47,7 @@ import javax.swing.JTextArea;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.text.DefaultCaret;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.diylc.appframework.miscutils.InMemoryConfigurationManager;
|
||||
@@ -507,7 +507,7 @@ public class ResultsScrollPanel extends JScrollPane {
|
||||
}
|
||||
});
|
||||
|
||||
buttonPanel.setBackground(Color.white);
|
||||
buttonPanel.setBackground(UIManager.getColor("List.background"));
|
||||
buttonPanel.add(downloadButton);
|
||||
if (showEditControls) {
|
||||
buttonPanel.add(editButton);
|
||||
@@ -651,7 +651,7 @@ public class ResultsScrollPanel extends JScrollPane {
|
||||
private JPanel getResultsPanel() {
|
||||
if (resultsPanel == null) {
|
||||
resultsPanel = new JPanel(new GridBagLayout());
|
||||
resultsPanel.setBackground(Color.white);
|
||||
resultsPanel.setBackground(UIManager.getColor("List.background"));
|
||||
}
|
||||
return resultsPanel;
|
||||
}
|
||||
|
||||
+2
-2
@@ -21,7 +21,6 @@
|
||||
*/
|
||||
package org.diylc.swing.plugins.cloud.view.browser;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.Insets;
|
||||
@@ -31,6 +30,7 @@ import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.UIManager;
|
||||
|
||||
import org.diylc.utils.IconLoader;
|
||||
|
||||
@@ -48,7 +48,7 @@ public class SearchHeaderPanel extends JPanel {
|
||||
public SearchHeaderPanel() {
|
||||
setLayout(new GridBagLayout());
|
||||
|
||||
this.setBackground(Color.white);
|
||||
this.setBackground(UIManager.getColor("List.background"));
|
||||
GridBagConstraints gbc = new GridBagConstraints();
|
||||
gbc.anchor = GridBagConstraints.LINE_START;
|
||||
gbc.insets = new Insets(2, 2, 2, 2);
|
||||
|
||||
@@ -43,6 +43,7 @@ import org.diylc.lang.Language;
|
||||
import org.diylc.swing.ActionFactory;
|
||||
import org.diylc.swing.ISwingUI;
|
||||
import org.diylc.utils.IconLoader;
|
||||
import org.diylc.utils.LookAndFeelUtils;
|
||||
import org.diylc.utils.ResourceUtils;
|
||||
|
||||
/**
|
||||
@@ -56,6 +57,7 @@ public class ConfigPlugin implements IPlugIn {
|
||||
|
||||
private static final String CONFIG_MENU = "Config";
|
||||
private static final String THEME_MENU = "Theme";
|
||||
private static final String UI_THEME_MENU = "UI Theme";
|
||||
private static final String RULER_MENU = "Ruler Inch Subdivision...";
|
||||
private static final String SNAP_MENU = "Snap To";
|
||||
private static final String CATEGORIES_MENU = "Component Categories";
|
||||
@@ -68,6 +70,7 @@ public class ConfigPlugin implements IPlugIn {
|
||||
private static final String LANGUAGE_MENU = LangUtil.translate("Language");
|
||||
|
||||
private static final String LANG_RESTART = LangUtil.translate("Language selection will be applied after the application is restarted.");
|
||||
private static final String UI_THEME_RESTART = LangUtil.translate("UI theme selection will be applied after the application is restarted.");
|
||||
public static final String THEMES_DIR = "themes";
|
||||
|
||||
private ISwingUI swingUI;
|
||||
@@ -138,6 +141,14 @@ public class ConfigPlugin implements IPlugIn {
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error while setting up language menu", e);
|
||||
}
|
||||
|
||||
swingUI.injectSubmenu(UI_THEME_MENU, IconLoader.WindowColors.getIcon(), CONFIG_MENU);
|
||||
for (String uiTheme : new String[] {LookAndFeelUtils.UI_THEME_SYSTEM,
|
||||
LookAndFeelUtils.UI_THEME_LIGHT, LookAndFeelUtils.UI_THEME_DARK}) {
|
||||
swingUI.injectMenuAction(ActionFactory.getInstance().createToggleAction(uiTheme,
|
||||
LookAndFeelUtils.UI_THEME_KEY, UI_THEME_MENU, LookAndFeelUtils.UI_THEME_DEFAULT),
|
||||
UI_THEME_MENU);
|
||||
}
|
||||
|
||||
|
||||
swingUI.injectMenuAction(
|
||||
@@ -231,6 +242,16 @@ public class ConfigPlugin implements IPlugIn {
|
||||
swingUI.showMessage(LANG_RESTART, LANGUAGE_MENU, ISwingUI.INFORMATION_MESSAGE);
|
||||
}
|
||||
});
|
||||
|
||||
ConfigurationManager.getInstance().addConfigListener(LookAndFeelUtils.UI_THEME_KEY,
|
||||
new IConfigListener() {
|
||||
|
||||
@Override
|
||||
public void valueChanged(String key, Object value) {
|
||||
swingUI.showMessage(UI_THEME_RESTART, LangUtil.translate(UI_THEME_MENU),
|
||||
ISwingUI.INFORMATION_MESSAGE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -47,6 +47,7 @@ import org.diylc.swing.gui.ComponentPopupMenu;
|
||||
import org.diylc.swing.gui.DragDropList;
|
||||
import org.diylc.swing.gui.IDragDropListListener;
|
||||
import org.diylc.swing.gui.SearchTextField;
|
||||
import org.diylc.utils.LookAndFeelUtils;
|
||||
|
||||
public class ExplorerPane extends JPanel {
|
||||
|
||||
@@ -588,6 +589,9 @@ public class ExplorerPane extends JPanel {
|
||||
|
||||
private Map<String, ComponentType> componentTypes;
|
||||
|
||||
private String layerColor = LookAndFeelUtils.themeHtmlColor("#c0c0c0", "#707070");
|
||||
private String valueColor = LookAndFeelUtils.themeHtmlColor("#666666", "#a0a0a0");
|
||||
|
||||
public ComponentListCellRenderer(Map<String, List<ComponentType>> componentTypeMap) {
|
||||
this.componentTypes = componentTypeMap.values().stream().flatMap(x -> x.stream())
|
||||
.collect(Collectors.toMap(x -> x.getInstanceClass().getCanonicalName(), x -> x));
|
||||
@@ -624,12 +628,12 @@ public class ExplorerPane extends JPanel {
|
||||
}
|
||||
} else {
|
||||
if (valueForDisplay == null || valueForDisplay.trim().isEmpty()) {
|
||||
renderer.setText(String.format("<html><font color='#c0c0c0'>[L%s]</font> %s</html>",
|
||||
layerId, component.getName()));
|
||||
renderer.setText(String.format("<html><font color='%s'>[L%s]</font> %s</html>",
|
||||
layerColor, 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));
|
||||
"<html><font color='%s'>[L%s]</font> %s (<font color='%s'>%s</font>)</html>",
|
||||
layerColor, layerId, component.getName(), valueColor, valueForDisplay));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -656,8 +660,8 @@ public class ExplorerPane extends JPanel {
|
||||
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>",
|
||||
maxLayerId, groupName, componentCount));
|
||||
"<html><font color='%s'>[L%s]</font> %s (<font color='%s'>%s</font>)</html>",
|
||||
layerColor, maxLayerId, groupName, valueColor, componentCount));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
|
||||
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 javax.swing.UIManager;
|
||||
import javax.swing.UnsupportedLookAndFeelException;
|
||||
|
||||
import com.formdev.flatlaf.FlatDarkLaf;
|
||||
import com.formdev.flatlaf.FlatLaf;
|
||||
import com.formdev.flatlaf.FlatLightLaf;
|
||||
import com.formdev.flatlaf.themes.FlatMacDarkLaf;
|
||||
import com.formdev.flatlaf.themes.FlatMacLightLaf;
|
||||
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.appframework.miscutils.Utils;
|
||||
|
||||
/**
|
||||
* Installs the Swing look and feel selected under Config > UI Theme. The native look and feel
|
||||
* remains the default; the light and dark themes are provided by FlatLaf.
|
||||
*
|
||||
* @author Branislav Stojkovic
|
||||
*/
|
||||
public class LookAndFeelUtils {
|
||||
|
||||
public static final String UI_THEME_KEY = "uiTheme";
|
||||
public static final String UI_THEME_SYSTEM = "System";
|
||||
public static final String UI_THEME_LIGHT = "Light";
|
||||
public static final String UI_THEME_DARK = "Dark";
|
||||
public static final String UI_THEME_DEFAULT = UI_THEME_SYSTEM;
|
||||
|
||||
/**
|
||||
* Installs the configured look and feel. Must run before any UI component is created, since
|
||||
* switching requires an application restart.
|
||||
*/
|
||||
public static void applyLookAndFeel() throws ClassNotFoundException, InstantiationException,
|
||||
IllegalAccessException, UnsupportedLookAndFeelException {
|
||||
String theme = ConfigurationManager.getInstance().readString(UI_THEME_KEY, UI_THEME_DEFAULT);
|
||||
if (UI_THEME_DARK.equalsIgnoreCase(theme)) {
|
||||
UIManager.setLookAndFeel(Utils.isMac() ? new FlatMacDarkLaf() : new FlatDarkLaf());
|
||||
} else if (UI_THEME_LIGHT.equalsIgnoreCase(theme)) {
|
||||
UIManager.setLookAndFeel(Utils.isMac() ? new FlatMacLightLaf() : new FlatLightLaf());
|
||||
} else {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isDark() {
|
||||
return FlatLaf.isLafDark();
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks between two variants of a color that the look and feel does not provide, such as an
|
||||
* accent or a highlight.
|
||||
*/
|
||||
public static Color themeColor(Color light, Color dark) {
|
||||
return isDark() ? dark : light;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #themeColor(Color, Color)} for colors embedded into HTML, in "#rrggbb" form.
|
||||
*/
|
||||
public static String themeHtmlColor(String light, String dark) {
|
||||
return isDark() ? dark : light;
|
||||
}
|
||||
}
|
||||
@@ -713,3 +713,7 @@ Replace %s with %s|%s durch %s ersetzen
|
||||
Reconnect %s to %s|%s mit %s neu verbinden
|
||||
Editing your circuit:|Der Bearbeitung Ihrer Schaltung:
|
||||
Generating new layouts:|Der Erstellung neuer Layouts:
|
||||
AI Circuit Analyzer requires a DIYLC Cloud account. Would you like to log in now?|Der KI-Schaltungsanalysator erfordert ein DIYLC-Cloud-Konto. Möchten Sie sich jetzt anmelden?
|
||||
UI Theme|UI-Design
|
||||
System|System
|
||||
UI theme selection will be applied after the application is restarted.|Das gewählte UI-Design wird nach Neustart der Anwendung verwendet.
|
||||
|
||||
@@ -706,3 +706,7 @@ Replace %s with %s
|
||||
Reconnect %s to %s
|
||||
Editing your circuit:
|
||||
Generating new layouts:
|
||||
AI Circuit Analyzer requires a DIYLC Cloud account. Would you like to log in now?
|
||||
UI Theme
|
||||
System
|
||||
UI theme selection will be applied after the application is restarted.
|
||||
|
||||
@@ -709,3 +709,7 @@ Replace %s with %s|Reemplazar %s por %s
|
||||
Reconnect %s to %s|Reconectar %s a %s
|
||||
Editing your circuit:|Edición de su circuito:
|
||||
Generating new layouts:|Generación de nuevos diseños:
|
||||
AI Circuit Analyzer requires a DIYLC Cloud account. Would you like to log in now?|El Analizador de Circuitos IA requiere una cuenta de DIYLC Cloud. ¿Desea iniciar sesión ahora?
|
||||
UI Theme|Tema de la interfaz
|
||||
System|Sistema
|
||||
UI theme selection will be applied after the application is restarted.|La selección del tema de la interfaz se aplicará después de reiniciar la aplicación.
|
||||
|
||||
@@ -708,3 +708,7 @@ Replace %s with %s|Remplacer %s par %s
|
||||
Reconnect %s to %s|Reconnecter %s à %s
|
||||
Editing your circuit:|La modification de votre circuit :
|
||||
Generating new layouts:|La génération de nouvelles implantations :
|
||||
AI Circuit Analyzer requires a DIYLC Cloud account. Would you like to log in now?|L'analyseur de circuit IA nécessite un compte DIYLC Cloud. Voulez-vous vous connecter maintenant ?
|
||||
UI Theme|Thème de l'interface
|
||||
System|Système
|
||||
UI theme selection will be applied after the application is restarted.|La sélection du thème de l'interface sera appliquée après le redémarrage de l'application.
|
||||
|
||||
@@ -710,3 +710,7 @@ Replace %s with %s|Sostituisci %s con %s
|
||||
Reconnect %s to %s|Ricollega %s a %s
|
||||
Editing your circuit:|Modifica del tuo circuito:
|
||||
Generating new layouts:|Generazione di nuovi layout:
|
||||
AI Circuit Analyzer requires a DIYLC Cloud account. Would you like to log in now?|L'Analizzatore di Circuiti IA richiede un account DIYLC Cloud. Vuoi accedere ora?
|
||||
UI Theme|Tema dell'interfaccia
|
||||
System|Sistema
|
||||
UI theme selection will be applied after the application is restarted.|La selezione del tema dell'interfaccia verrà applicata dopo il riavvio dell'applicazione.
|
||||
|
||||
@@ -714,3 +714,7 @@ Replace %s with %s|%s vervangen door %s
|
||||
Reconnect %s to %s|%s opnieuw verbinden met %s
|
||||
Editing your circuit:|Uw schakeling bewerken:
|
||||
Generating new layouts:|Nieuwe layouts genereren:
|
||||
AI Circuit Analyzer requires a DIYLC Cloud account. Would you like to log in now?|De AI Circuit Analysator vereist een DIYLC Cloud-account. Wilt u nu inloggen?
|
||||
UI Theme|Interfacethema
|
||||
System|Systeem
|
||||
UI theme selection will be applied after the application is restarted.|Interfacethema wordt toegepast nadat het programma opnieuw is opgestart.
|
||||
|
||||
@@ -709,3 +709,7 @@ Replace %s with %s|Substituir %s por %s
|
||||
Reconnect %s to %s|Reconectar %s a %s
|
||||
Editing your circuit:|Edição do seu circuito:
|
||||
Generating new layouts:|Geração de novos layouts:
|
||||
AI Circuit Analyzer requires a DIYLC Cloud account. Would you like to log in now?|O Analisador de Circuito IA requer uma conta DIYLC Cloud. Deseja entrar agora?
|
||||
UI Theme|Tema da interface
|
||||
System|Sistema
|
||||
UI theme selection will be applied after the application is restarted.|A seleção do tema da interface se aplicará depois de reiniciar o aplicativo.
|
||||
|
||||
@@ -705,3 +705,7 @@ Replace %s with %s|Zameni %s sa %s
|
||||
Reconnect %s to %s|Ponovo poveži %s sa %s
|
||||
Editing your circuit:|Izmenama vašeg kola:
|
||||
Generating new layouts:|Generisanju novih rasporeda:
|
||||
AI Circuit Analyzer requires a DIYLC Cloud account. Would you like to log in now?|AI Analizator Kola zahteva DIYLC Cloud nalog. Da li želite da se prijavite sada?
|
||||
UI Theme|Tema interfejsa
|
||||
System|Sistemska
|
||||
UI theme selection will be applied after the application is restarted.|Odabir teme interfejsa će biti primenjen nakon sledećeg pokretanja aplikacije.
|
||||
|
||||
@@ -707,3 +707,7 @@ Replace %s with %s|%s को %s से बदलें
|
||||
Reconnect %s to %s|%s को %s से फिर से जोड़ें
|
||||
Editing your circuit:|आपके सर्किट को संपादित करना:
|
||||
Generating new layouts:|नए लेआउट बनाना:
|
||||
AI Circuit Analyzer requires a DIYLC Cloud account. Would you like to log in now?|AI सर्किट विश्लेषक के लिए DIYLC Cloud खाते की आवश्यकता है। क्या आप अभी लॉग इन करना चाहेंगे?
|
||||
UI Theme|UI थीम
|
||||
System|सिस्टम
|
||||
UI theme selection will be applied after the application is restarted.|UI थीम चयन एप्लिकेशन रीस्टार्ट होने के बाद लागू होगा।
|
||||
|
||||
@@ -707,3 +707,7 @@ Replace %s with %s|将 %s 替换为 %s
|
||||
Reconnect %s to %s|将 %s 重新连接到 %s
|
||||
Editing your circuit:|编辑您的电路:
|
||||
Generating new layouts:|生成新的布局:
|
||||
AI Circuit Analyzer requires a DIYLC Cloud account. Would you like to log in now?|AI电路分析器需要 DIYLC Cloud 账户。是否立即登录?
|
||||
UI Theme|界面主题
|
||||
System|系统
|
||||
UI theme selection will be applied after the application is restarted.|界面主题选择将在应用程序重启后生效。
|
||||
|
||||
Reference in New Issue
Block a user