Compare commits

..

32 Commits

Author SHA1 Message Date
Bane Stojkovic 4d8bf5471a dark theme support 2025-12-25 14:10:30 +01:00
Bane Stojkovic 6fa0e7d56a Improved sensitivity of 'Multimeter Probe' that required pixel-level precision to detect connectivity; improved rendering of the probe in outline mode 2025-12-25 11:36:51 +01:00
Bane Stojkovic 12726338ef Fixed underside rendering and mirroring position issues that affected only SPDT 'Mini Toggle Switch' types 2025-12-25 10:59:31 +01:00
Bane Stojkovic f3a8e3cfb1 release 5.7.9 2025-12-21 22:07:21 +01:00
Bane Stojkovic 1ac8ba8a24 Merge pull request #980 from mm-rnd/Lever_4P3T
Lever 4P3T
2025-12-21 22:03:47 +01:00
Bane Stojkovic 7b5bb3a152 Merge branch 'master' into Lever_4P3T 2025-12-21 22:01:05 +01:00
Martin Morrell ce7753744a Lever 2502N import switch (#981)
* Add 4P3T AxLabs Lever Switch variant

* Add 2502N Lever Switch

* Bridge is now pos1 and neck pos5 as expected

Re-worked acording to the diagram here:
https://guitarpartscenter.eu/en_US/p/ALPHA-SW-2502N-5-Way-Lever-Switch-ADWH/23036
2025-12-21 21:58:20 +01:00
Martin Morrell 3adaa16537 Add 4P3T AxLabs Lever Switch variant 2025-12-20 07:46:36 +00:00
Bane Stojkovic aa4f4c9c66 'Drill Hole' changes color to red when selected 2025-12-19 20:20:07 +01:00
Bane Stojkovic ef01d52905 Update update.xml 2025-12-19 20:10:05 +01:00
Bane Stojkovic 892de712b2 Merge branch 'master' of https://github.com/bancika/diy-layout-creator 2025-12-19 20:09:53 +01:00
Martin Morrell 79aa25f14c Update positon names to match switch positions (#977) 2025-12-19 20:07:12 +01:00
Bane Stojkovic 165b61329c Merge branch 'master' of https://github.com/bancika/diy-layout-creator 2025-12-19 20:02:33 +01:00
Bane Stojkovic c251e5ace2 flip S1 position names 2025-12-19 20:02:24 +01:00
Martin Morrell 550e221265 Fix S1 Switch markers (#978) 2025-12-19 20:02:10 +01:00
Martin Morrell 7110897e9d Add 6P5T AxLabs Lever Switch variant (#979) 2025-12-19 19:54:15 +01:00
Bane Stojkovic 74badd8a01 Update DrillHole.java 2025-08-11 22:29:03 +02:00
Bane Stojkovic 95eccffa4a 'Connect to Patreon' option says that it connected account with username 'a b c' but doesn't do anything 2025-08-11 19:25:00 +02:00
Bane Stojkovic 35a190b02f Added 'Drill Hole' component 2025-06-21 22:45:46 +02:00
Bane Stojkovic 92457ae0fb Increase maximum size of the backup folder from 64MB to 1GB for deeper history 2025-06-18 21:35:14 +02:00
Bane Stojkovic 73d3dd1cd4 fix: Alignment issues of some visual elements in the resulting Gerber files, e.g. DIL IC pin designators 2025-06-16 23:33:56 +02:00
Bane Stojkovic cb9cd46261 fix: When a switch position is selected (i.e. set to a value other than None), DIYLC will treat the terminals that are internally connected in that switching logic as connected in analysis tools 2025-06-06 23:52:28 +02:00
Bane Stojkovic 4bd3943345 bigger cloud search window 2025-06-06 00:09:28 +02:00
Bane Stojkovic 30bdc7fc3d disable patreon connect when not logged in 2025-06-05 00:05:17 +02:00
Bane Stojkovic a1d31c1ae2 add join patreon link at the top and randomly show 2025-06-05 00:05:04 +02:00
Bane Stojkovic 90fb3095bd More intuitive user account creation dialog for DIYLC Cloud - showing exact requirements for password and marking optional entries 2025-06-04 23:33:50 +02:00
Bane Stojkovic 0c468369cd Simplify zoom in/out keyboard shortcuts to only plus/minus keys and zero numerical key for zoom reset to 100 2025-06-02 23:02:33 +02:00
Bane Stojkovic f88b0c642a mac build naming. Enable windows arm 2025-05-30 23:47:04 +02:00
Bane Stojkovic 1b8bdbe7ed Reduce logging 2025-05-29 23:45:40 +02:00
Bane Stojkovic 185b5da4d5 Bundle Java JRE with Linux AppImage to avoid having to install it 2025-05-29 23:32:27 +02:00
Bane Stojkovic cd92464467 fix auto-edit 2025-05-26 22:35:18 +02:00
Bane Stojkovic 59e18c6be2 Feature/compare (#929)
* add compare tool

* improvements

* diff improvements

* license

* diff logic and tests fixed

* update license info

* add isPolarized to component interface

* handle comparison of non-polarized components
2025-05-26 14:57:47 +02:00
62 changed files with 1240 additions and 223 deletions
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>org.diylc</groupId>
<artifactId>diylc-parent</artifactId>
<version>5.4.0</version>
<version>5.7.0</version>
</parent>
<artifactId>diylc-core</artifactId>
@@ -300,7 +300,7 @@ public class GerberG2DWrapper extends Graphics2D
@Override
public AffineTransform getTransform() {
return tx;
return new AffineTransform(tx);
}
@Override
@@ -101,23 +101,7 @@ public class NetlistBuilder {
// if there are no switches, make one with 1 position so we get 1 result back
if (switches.isEmpty())
switches.add(new ISwitch() {
@Override
public String getPositionName(int position) {
return "Default";
}
@Override
public int getPositionCount() {
return 1;
}
@Override
public boolean arePointsConnected(int index1, int index2, int position) {
return false;
}
});
switches.add(new TrivialSwitch());
// construct all possible combinations
int[] positions = new int[switches.size()];
@@ -418,7 +402,9 @@ public class NetlistBuilder {
// handle direct connections or connections through a switch
// when not using "includeSwitches" flag
if (c instanceof IContinuity &&
(!(c instanceof ISwitch) || switchPositions.isEmpty())) {
// ugly way to check if running in "exclude switches" mode.
(!(c instanceof ISwitch) || (switchPositions.size() == 1 &&
switchPositions.keySet().stream().findFirst().get() instanceof TrivialSwitch))) {
for (int i = 0; i < c.getControlPointCount() - 1; i++)
for (int j = i + 1; j < c.getControlPointCount(); j++)
if (((IContinuity) c).arePointsConnected(i, j))
@@ -493,4 +479,23 @@ public class NetlistBuilder {
return area;
}
}
private static class TrivialSwitch implements ISwitch {
@Override
public String getPositionName(int position) {
return "Default";
}
@Override
public int getPositionCount() {
return 1;
}
@Override
public boolean arePointsConnected(int index1, int index2, int position) {
return false;
}
}
}
+117 -1
View File
@@ -290,7 +290,7 @@
<minor>5</minor>
<build>0</build>
</versionNumber>
<releaseDate>2025-05-13 00:00:00.000 CET</releaseDate>
<releaseDate>2025-05-26 00:00:00.000 CET</releaseDate>
<name></name>
<changes>
<org.diylc.appframework.update.Change>
@@ -313,6 +313,10 @@
<changeType>BUG_FIX</changeType>
<description>X or Q keys sometimes do not register in secondary dialogs</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>BUG_FIX</changeType>
<description>'Auto-Edit Mode' doesn't do what it's supposed to</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>BUG_FIX</changeType>
<description>If the internet connection is present, but unstable, the application starts but nothing can be clicked until the connection restores</description>
@@ -320,5 +324,117 @@
</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>6</minor>
<build>0</build>
</versionNumber>
<releaseDate>2025-08-11 00:00:00.000 CET</releaseDate>
<name></name>
<changes>
<org.diylc.appframework.update.Change>
<changeType>NEW_FEATURE</changeType>
<description>Added 'Drill Hole' component that can be used for board mounting holes, chassis holes, faceplace holes, etc</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>Simplify zoom in/out keyboard shortcuts to only plus/minus keys and zero numerical key for zoom reset to 100</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>Bundle Java JRE with Linux AppImage to avoid having to install it</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>Updated naming conventions Mac build packages to replace 'arm' with 'silicon' and 'x86' to 'intel' for clarity</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>Allow Windows 64 bit installer to run on ARM-based Windows machine (through emulation)</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>More intuitive user account creation dialog for DIYLC Cloud - showing exact requirements for password and marking optional entries</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>Improved project search algorithm in DIYLC cloud</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>When a switch position is selected (i.e. set to a value other than None), DIYLC will treat the terminals that are internally connected in that switching logic as connected in analysis tools</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>Increase maximum size of the backup folder from 64MB to 1GB for deeper history</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>BUG_FIX</changeType>
<description>Alignment issues of some visual elements in the resulting Gerber files, e.g. DIL IC pin designators</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>BUG_FIX</changeType>
<description>'Connect to Patreon' option says that it connected account with username 'a b c' but doesn't do anything</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>7</minor>
<build>0</build>
</versionNumber>
<releaseDate>2025-12-21 00:00:00.000 CET</releaseDate>
<name></name>
<changes>
<org.diylc.appframework.update.Change>
<changeType>NEW_FEATURE</changeType>
<description>Added '6P5T (5-Position 6 Pole AxLabs)' lever switch type (thank you mm-rnd)</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>NEW_FEATURE</changeType>
<description>Added '4P3T (3-Position 4 Pole AxLabs)' lever switch type (thank you mm-rnd)</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>NEW_FEATURE</changeType>
<description>Added 'DP3T (Import 5-Position 2502N)' lever switch type (thank you mm-rnd)</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>Corrected 'S1 Switch' up/down position designators (thank you mm-rnd)</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>Corrected 'Mini Toggle Switch' position names for center-OFF variants to be in the correct order (thank you mm-rnd)</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>'Drill Hole' changes color to red when selected</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>8</minor>
<build>0</build>
</versionNumber>
<releaseDate>2025-12-21 00:00:00.000 CET</releaseDate>
<name></name>
<changes>
<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>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>
</changes>
<url>https://github.com/bancika/diy-layout-creator/releases</url>
</org.diylc.appframework.update.Version>
</a>
</java.util.Arrays_-ArrayList>
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>org.diylc</groupId>
<artifactId>diylc-parent</artifactId>
<version>5.4.0</version>
<version>5.7.0</version>
</parent>
<artifactId>diylc-library</artifactId>
@@ -41,7 +41,7 @@ import org.diylc.core.annotations.EditableProperty;
@ComponentDescriptor(name = "Blank Board", category = "Boards", author = "Branislav Stojkovic",
zOrder = IDIYComponent.BOARD, instanceNamePrefix = "Board", description = "Blank circuit board",
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, autoEdit = false, transformer = SimpleComponentTransformer.class)
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, transformer = SimpleComponentTransformer.class)
public class BlankBoard extends AbstractBoard {
private static final long serialVersionUID = 1L;
@@ -50,7 +50,7 @@ import org.diylc.core.measures.SizeUnit;
@ComponentDescriptor(name = "Breadboard", category = "Boards", author = "Branislav Stojkovic",
description = "Prototyping solderless breadboard", instanceNamePrefix = "BB",
zOrder = IDIYComponent.BOARD, bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, autoEdit = false,
zOrder = IDIYComponent.BOARD, bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME,
keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME, enableCache = true, transformer = ProtoBoardTransformer.class)
public class Breadboard extends AbstractProtoBoard {
@@ -52,7 +52,7 @@ import org.diylc.core.measures.SizeUnit;
author = "Branislav Stojkovic",
description = "David Haillant's eurorack format stripboard for prototyping and building simple modules",
instanceNamePrefix = "BB", zOrder = IDIYComponent.BOARD,
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, autoEdit = false,
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME,
keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME, enableCache = true,
transformer = ProtoBoardTransformer.class)
public class EurorackStripboard extends AbstractProtoBoard {
@@ -42,7 +42,7 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "Eyelet Board", category = "Boards", author = "Branislav Stojkovic",
zOrder = IDIYComponent.BOARD, instanceNamePrefix = "Board", description = "Perforated board with eyelets",
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, autoEdit = false, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
transformer = AbstractBoardTransformer.class, enableCache = true)
public class EyeletBoard extends AbstractBoard {
@@ -44,7 +44,7 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "Marshall Style Perf Board", category = "Boards", author = "Branislav Stojkovic",
zOrder = IDIYComponent.BOARD, instanceNamePrefix = "Board",
description = "Perforated board as found on some Marshall and Trainwreck amps",
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, autoEdit = false, keywordPolicy = KeywordPolicy.SHOW_TAG,
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TAG,
keywordTag = "Perf Board", transformer = AbstractBoardTransformer.class)
public class MarshallPerfBoard extends AbstractBoard {
@@ -44,7 +44,7 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "Perf Board w/ Pads", category = "Boards", author = "Branislav Stojkovic",
zOrder = IDIYComponent.BOARD, instanceNamePrefix = "Board", description = "Perforated board with solder pads",
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, autoEdit = false, keywordPolicy = KeywordPolicy.SHOW_TAG,
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TAG,
keywordTag = "Perf Board", transformer = AbstractBoardTransformer.class, enableCache = true)
public class PerfBoard extends AbstractBoard implements ILayeredComponent {
@@ -47,7 +47,7 @@ import org.diylc.core.measures.SizeUnit;
@ComponentDescriptor(name = "P-0+o (Proto) Board", category = "Boards", author = "Branislav Stojkovic",
description = "A prototyping board for modular synths, central bus with V+ GND and V-", instanceNamePrefix = "BB",
zOrder = IDIYComponent.BOARD, bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, autoEdit = false,
zOrder = IDIYComponent.BOARD, bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME,
keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME, enableCache = true, transformer = ProtoBoardTransformer.class)
public class ProtoBoard extends AbstractProtoBoard {
@@ -46,7 +46,7 @@ import org.diylc.core.measures.SizeUnit;
author = "Branislav Stojkovic",
description = "Prototyping board similar to Radio Shack 276-168B or MPJA 33304",
instanceNamePrefix = "BB", zOrder = IDIYComponent.BOARD,
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, autoEdit = false,
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME,
keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME, enableCache = true,
transformer = ProtoBoardTransformer.class)
public class ProtoBoard780 extends AbstractProtoBoard {
@@ -42,7 +42,7 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "Vero Board", category = "Boards", author = "Branislav Stojkovic",
zOrder = IDIYComponent.BOARD, instanceNamePrefix = "Board",
description = "Perforated FR4 board with copper strips connecting all holes in a row",
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, autoEdit = false, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME,
transformer = VeroBoardTransformer.class, enableCache = true)
public class VeroBoard extends AbstractVeroBoard implements ILayeredComponent {
@@ -47,7 +47,7 @@ import org.diylc.core.measures.SizeUnit;
@ComponentDescriptor(name = "Copper Trace", author = "Branislav Stojkovic", category = "Connectivity",
creationMethod = CreationMethod.POINT_BY_POINT, instanceNamePrefix = "Trace",
description = "Straight copper trace", zOrder = IDIYComponent.TRACE, bomPolicy = BomPolicy.NEVER_SHOW,
autoEdit = false, keywordPolicy = KeywordPolicy.SHOW_TAG, keywordTag = "PCB",
keywordPolicy = KeywordPolicy.SHOW_TAG, keywordTag = "PCB",
transformer = SimpleComponentTransformer.class)
public class CopperTrace extends AbstractLeadedComponent<Void> implements ILayeredComponent, IGerberComponentSimple {
@@ -46,7 +46,7 @@ import org.diylc.core.measures.SizeUnit;
@ComponentDescriptor(name = "Curved Trace", author = "Branislav Stojkovic", category = "Connectivity",
instanceNamePrefix = "Trace", description = "Curved copper trace with two control points",
zOrder = IDIYComponent.TRACE, bomPolicy = BomPolicy.NEVER_SHOW, autoEdit = false,
zOrder = IDIYComponent.TRACE, bomPolicy = BomPolicy.NEVER_SHOW,
keywordPolicy = KeywordPolicy.SHOW_TAG, keywordTag = "PCB", transformer = SimpleComponentTransformer.class,
enableCache = true)
public class CurvedTrace extends AbstractCurvedComponent<Void> implements ILayeredComponent, IGerberComponentSimple {
@@ -40,7 +40,7 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "Hookup Wire", author = "Branislav Stojkovic", category = "Connectivity",
instanceNamePrefix = "W", description = "Flexible wire with two control points", zOrder = IDIYComponent.WIRING,
flexibleZOrder = true, bomPolicy = BomPolicy.NEVER_SHOW, autoEdit = false,
flexibleZOrder = true, bomPolicy = BomPolicy.NEVER_SHOW,
transformer = SimpleComponentTransformer.class, enableCache = false)
public class HookupWire extends AbstractCurvedComponent<AWG> implements IContinuity {
@@ -28,21 +28,20 @@ import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import org.diylc.appframework.miscutils.ConfigurationManager;
import org.diylc.common.IPlugInPort;
import org.diylc.common.ObjectCache;
import org.diylc.common.Orientation45;
import org.diylc.components.AbstractTransparentComponent;
import org.diylc.components.transform.SimpleComponentTransformer;
import org.diylc.core.ComponentState;
import org.diylc.core.IDIYComponent;
import org.diylc.core.IDrawingObserver;
import org.diylc.core.Project;
import org.diylc.core.VisibilityPolicy;
import org.diylc.core.*;
import org.diylc.core.annotations.BomPolicy;
import org.diylc.core.annotations.ComponentDescriptor;
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;
@ComponentDescriptor(name = "Multimeter Probe", category = "Connectivity",
author = "Branislav Stojkovic", description = "Multimeter Probe", instanceNamePrefix = "Probe",
@@ -59,6 +58,7 @@ public class MultimeterProbe extends AbstractTransparentComponent<Color> {
public static Size HANDLE_DIAMETER = new Size(0.2d, SizeUnit.in);
public static Size HANDLE_LENGTH = new Size(0.4d, SizeUnit.in);
public static Color HANDLE_COLOR = Color.red;
public static Size SENSITIVITY = new Size(0.05d, SizeUnit.in);
private Point2D.Double point = new Point2D.Double(0, 0);
private Color handleColor = HANDLE_COLOR;
@@ -77,27 +77,49 @@ public class MultimeterProbe extends AbstractTransparentComponent<Color> {
Shape[] shapes = getBody();
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1f));
if (!outlineMode) {
Composite oldComposite = g2d.getComposite();
if (alpha < MAX_ALPHA) {
g2d.setComposite(
AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f * alpha / MAX_ALPHA));
}
g2d.setColor(PROBE_COLOR);
g2d.fill(shapes[0]);
g2d.setColor(handleColor);
g2d.fill(shapes[1]);
g2d.setComposite(oldComposite);
// draw an invisible conductive circle around the point to increase sensitivity
g2d.setColor(Constants.TRANSPARENT_COLOR);
drawingObserver.startTrackingContinuityArea(true);
double sensitivity = SENSITIVITY.convertToPixels();
g2d.fillOval((int) (point.getX() - sensitivity), (int) (point.getY() - sensitivity),
(int) (sensitivity * 2), (int) (sensitivity * 2));
drawingObserver.stopTrackingContinuityArea();
Composite oldComposite = g2d.getComposite();
if (alpha < MAX_ALPHA) {
g2d.setComposite(
AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f * alpha / MAX_ALPHA));
}
g2d.setColor(
componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING
? SELECTION_COLOR
: handleColor.darker());
g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : PROBE_COLOR);
g2d.fill(shapes[0]);
g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : handleColor);
g2d.fill(shapes[1]);
g2d.setComposite(oldComposite);
drawingObserver.stopTracking();
Theme theme = (Theme) ConfigurationManager.getInstance().readObject(IPlugInPort.THEME_KEY,
Constants.DEFAULT_THEME);
Color handleBorderColor;
if (outlineMode) {
handleBorderColor = theme.getOutlineColor();
} else {
handleBorderColor = componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING
? SELECTION_COLOR
: handleColor.darker();
}
g2d.setColor(handleBorderColor);
g2d.draw(shapes[1]);
g2d.setColor(
componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING
? SELECTION_COLOR
: PROBE_COLOR.darker());
Color probeBorderColor;
if (outlineMode) {
probeBorderColor = theme.getOutlineColor();
} else {
probeBorderColor = componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING
? SELECTION_COLOR
: PROBE_COLOR.darker();
}
g2d.setColor(probeBorderColor);
g2d.draw(shapes[0]);
}
@@ -50,7 +50,7 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "PCB Terminal Block", category = "Connectivity",
author = "Branislav Stojkovic", description = "Horizontal PCB terminal block with 5mm pitch",
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "TB", autoEdit = false,
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "TB",
enableCache = true, transformer = PCBTerminalBlockTransformer.class)
public class PCBTerminalBlock extends AbstractTransparentComponent<PCBTerminalBlockCount> implements IGerberComponentSimple {
@@ -52,7 +52,7 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "Solder Pad", category = "Connectivity", author = "Branislav Stojkovic",
description = "Copper solder pad, round or square", instanceNamePrefix = "Pad",
zOrder = IDIYComponent.TRACE + 0.1, bomPolicy = BomPolicy.NEVER_SHOW, autoEdit = false,
zOrder = IDIYComponent.TRACE + 0.1, bomPolicy = BomPolicy.NEVER_SHOW,
keywordPolicy = KeywordPolicy.SHOW_TAG, keywordTag = "PCB",
transformer = SimpleComponentTransformer.class, enableCache = true)
public class SolderPad extends AbstractTransparentComponent<Void>
@@ -49,7 +49,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,
flexibleZOrder = true, bomPolicy = BomPolicy.NEVER_SHOW, autoEdit = false,
flexibleZOrder = true, bomPolicy = BomPolicy.NEVER_SHOW,
transformer = SimpleComponentTransformer.class, enableCache = true)
public class TwistedWire extends AbstractCurvedComponent<Void> implements IContinuity {
@@ -53,7 +53,7 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "9V Battery Snap", category = "Electro-Mechanical", author = "Branislav Stojkovic",
description = "", zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "BTR",
autoEdit = false, transformer = BatterySnapTransformer.class)
transformer = BatterySnapTransformer.class)
public class BatterySnap9V extends AbstractTransparentComponent<String> {
private static final long serialVersionUID = 1L;
@@ -58,7 +58,7 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "Cliff 1/4\" Jack", category = "Electro-Mechanical", author = "Branislav Stojkovic",
description = "Cliff-style closed panel mount 1/4\" phono jack",
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "J", autoEdit = false, transformer = CliffJackTransformer.class,
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "J", transformer = CliffJackTransformer.class,
enableCache = true)
public class CliffJack1_4 extends AbstractMultiPartComponent<JackType> implements IGerberComponentSimple {
@@ -54,7 +54,7 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "Cliff 1/8\" Jack", category = "Electro-Mechanical",
author = "Branislav Stojkovic", description = "Cliff-style closed 1/8\" phono jack",
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "J", autoEdit = false,
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "J",
transformer = CliffJackTransformer.class, enableCache = true)
public class CliffJack1_8 extends AbstractMultiPartComponent<JackType> implements IGerberComponentSimple {
@@ -59,7 +59,7 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "Closed 1/4\" Jack", category = "Electro-Mechanical", author = "Branislav Stojkovic",
description = "Enclosed panel mount 1/4\" phono jack", zOrder = IDIYComponent.COMPONENT,
instanceNamePrefix = "J", autoEdit = false, transformer = ClosedJackTransformer.class, enableCache = true)
instanceNamePrefix = "J", transformer = ClosedJackTransformer.class, enableCache = true)
public class ClosedJack1_4 extends AbstractMultiPartComponent<JackType> implements IGerberComponentSimple {
private static final long serialVersionUID = 1L;
@@ -0,0 +1,229 @@
/*
*
* 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 java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import com.bancika.gerberwriter.GerberFunctions;
import org.diylc.components.AbstractTransparentComponent;
import org.diylc.components.transform.SimpleComponentTransformer;
import org.diylc.core.ComponentState;
import org.diylc.core.IDIYComponent;
import org.diylc.core.IDrawingObserver;
import org.diylc.core.Project;
import org.diylc.core.VisibilityPolicy;
import org.diylc.core.annotations.BomPolicy;
import org.diylc.core.annotations.ComponentDescriptor;
import org.diylc.core.annotations.EditableProperty;
import org.diylc.core.annotations.KeywordPolicy;
import org.diylc.core.annotations.PositiveNonZeroMeasureValidator;
import org.diylc.core.gerber.GerberLayer;
import org.diylc.core.gerber.IGerberComponentCustom;
import org.diylc.core.gerber.IGerberDrawingObserver;
import org.diylc.core.measures.Size;
import org.diylc.core.measures.SizeUnit;
@ComponentDescriptor(name = "Drill Hole", category = "Electro-Mechanical", author = "Branislav Stojkovic",
description = "Drill hole marker for PCB, chassis or faceplate", instanceNamePrefix = "Hole",
zOrder = IDIYComponent.BOARD + 0.1, bomPolicy = BomPolicy.NEVER_SHOW,
keywordPolicy = KeywordPolicy.SHOW_TAG, keywordTag = "PCB",
transformer = SimpleComponentTransformer.class, enableCache = true)
public class DrillHole extends AbstractTransparentComponent<Void>
implements IGerberComponentCustom {
private static final long serialVersionUID = 1L;
public static Size DIAMETER = new Size(3.0d, SizeUnit.mm);
public static Color COLOR = Color.black;
public static Size STROKE_WIDTH = new Size(1.0d, SizeUnit.px);
public static Size CROSSHAIR_WIDTH = new Size(1.0d, SizeUnit.px);
private Size diameter = DIAMETER;
private Color color = COLOR;
private Point2D.Double point = new Point2D.Double(0, 0);
@Override
public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode,
Project project, IDrawingObserver drawingObserver,
IGerberDrawingObserver gerberDrawingObserver) {
if (checkPointsClipped(g2d.getClip())) {
return;
}
double holeDiameter = getDiameter().convertToPixels();
double borderWidth = STROKE_WIDTH.convertToPixels(); // Use constant stroke width
// Export to Gerber drill layer
if (gerberDrawingObserver != null) {
GerberLayer drillLayer = GerberLayer.DrillNonPlated;
gerberDrawingObserver.startGerberOutput(drillLayer, GerberFunctions.COMPONENT_DRILL, false);
}
Composite oldComposite = g2d.getComposite();
if (getAlpha() < MAX_ALPHA) {
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f * getAlpha() / MAX_ALPHA));
}
// Draw white circle (hole)
g2d.setColor(Color.white);
g2d.fill(new Ellipse2D.Double(point.getX() - holeDiameter / 2, point.getY() - holeDiameter / 2,
holeDiameter, holeDiameter));
// Draw colored border
g2d.setColor(
componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING
? SELECTION_COLOR
: color);
g2d.setStroke(new BasicStroke((float) borderWidth));
g2d.draw(new Ellipse2D.Double(point.getX() - holeDiameter / 2, point.getY() - holeDiameter / 2,
holeDiameter, holeDiameter));
// Draw crosshairs
double crosshairLength = holeDiameter * 0.4; // 40% of diameter for crosshair length (increased from 30%)
double crosshairWidth = CROSSHAIR_WIDTH.convertToPixels(); // Use constant crosshair width
g2d.setStroke(new BasicStroke((float) crosshairWidth));
// Horizontal line
g2d.draw(new Line2D.Double(
point.getX() - crosshairLength / 2, point.getY(),
point.getX() + crosshairLength / 2, point.getY()));
// Vertical line
g2d.draw(new Line2D.Double(
point.getX(), point.getY() - crosshairLength / 2,
point.getX(), point.getY() + crosshairLength / 2));
drawingObserver.stopTracking();
if (gerberDrawingObserver != null) {
gerberDrawingObserver.stopGerberOutput();
}
g2d.setComposite(oldComposite);
}
@Override
public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode,
Project project, IDrawingObserver drawingObserver) {
this.draw(g2d, componentState, outlineMode, project, drawingObserver, null);
}
@Override
public void drawIcon(Graphics2D g2d, int width, int height) {
int diameter = getClosestOdd(width * 3 / 4); // Increased from width/2 to 3/4 of width
int borderWidth = Math.max(1, (int) STROKE_WIDTH.convertToPixels()); // Use constant stroke width
int crosshairLength = diameter * 2 / 5; // 40% of diameter (increased from 1/3)
int crosshairWidth = Math.max(1, (int) CROSSHAIR_WIDTH.convertToPixels()); // Use constant crosshair width
// Draw white circle
g2d.setColor(Color.white);
g2d.fillOval((width - diameter) / 2, (height - diameter) / 2, diameter, diameter);
// Draw colored border
g2d.setColor(COLOR);
g2d.setStroke(new BasicStroke(borderWidth));
g2d.drawOval((width - diameter) / 2, (height - diameter) / 2, diameter, diameter);
// Draw crosshairs
g2d.setStroke(new BasicStroke(crosshairWidth));
// Horizontal line
g2d.drawLine(
(width - crosshairLength) / 2, height / 2,
(width + crosshairLength) / 2, height / 2);
// Vertical line
g2d.drawLine(
width / 2, (height - crosshairLength) / 2,
width / 2, (height + crosshairLength) / 2);
}
@EditableProperty(validatorClass = PositiveNonZeroMeasureValidator.class)
public Size getDiameter() {
return diameter;
}
public void setDiameter(Size diameter) {
this.diameter = diameter;
}
@EditableProperty(name = "Color")
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
@Override
public String getName() {
return super.getName();
}
@Override
public int getControlPointCount() {
return 1;
}
@Override
public boolean isControlPointSticky(int index) {
return true;
}
@Override
public VisibilityPolicy getControlPointVisibilityPolicy(int index) {
return VisibilityPolicy.NEVER;
}
@Override
public Point2D getControlPoint(int index) {
return point;
}
@Override
public void setControlPoint(Point2D point, int index) {
this.point.setLocation(point);
}
@Override
public Void getValue() {
return null;
}
@Override
public String getControlPointNodeName(int index) {
return null;
}
@Override
public void setValue(Void value) {}
@Override
public Rectangle2D getCachingBounds() {
double diameter = getDiameter().convertToPixels();
return new Rectangle2D.Double(point.getX() - diameter, point.getY() - diameter,
diameter * 2, diameter * 2);
}
}
@@ -53,7 +53,7 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "Fuse Holder (Panel)", category = "Electro-Mechanical", author = "Branislav Stojkovic",
description = "Panel mounted fuse holder", zOrder = IDIYComponent.COMPONENT,
instanceNamePrefix = "FH", autoEdit = false, enableCache = true)
instanceNamePrefix = "FH", enableCache = true)
public class FuseHolderPanel extends AbstractMultiPartComponent<String> {
private static final long serialVersionUID = 1L;
@@ -53,7 +53,7 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "IEC Socket", category = "Electro-Mechanical", author = "Branislav Stojkovic",
description = "Panel mounted IEC power socket", zOrder = IDIYComponent.COMPONENT,
instanceNamePrefix = "IEC", autoEdit = false, enableCache = true)
instanceNamePrefix = "IEC", enableCache = true)
public class IECSocket extends AbstractMultiPartComponent<String> {
private static final long serialVersionUID = 1L;
@@ -59,7 +59,7 @@ import org.diylc.utils.SwitchUtils;
@ComponentDescriptor(name = "Mini Toggle Switch", category = "Electro-Mechanical", author = "Branislav Stojkovic",
description = "Panel mounted mini toggle switch", zOrder = IDIYComponent.COMPONENT,
instanceNamePrefix = "SW", autoEdit = false, enableCache = true, transformer = MiniToggleSwitchTransformer.class)
instanceNamePrefix = "SW", enableCache = true, transformer = MiniToggleSwitchTransformer.class)
public class MiniToggleSwitch extends AbstractTransparentComponent<ToggleSwitchType> implements ISwitch, ILayeredComponent, IGerberComponentSimple {
private static final long serialVersionUID = 1L;
@@ -502,10 +502,17 @@ public class MiniToggleSwitch extends AbstractTransparentComponent<ToggleSwitchT
}
@Override
public String getPositionName(int position) {
if (switchType.name().endsWith("_off") && position == 2)
return "OFF";
return "ON" + Integer.toString(position + 1);
public String getPositionName(int position) {
int pos = position;
if (switchType.name().endsWith("_off")) {
int midPosition = (getPositionCount() - 1) / 2;
if (position == midPosition) {
return "OFF";
} else if (position > midPosition) {
--pos;
}
}
return "ON" + Integer.toString(pos + 1);
}
@DynamicEditableProperty(source = ToggleSwitchPositionPropertyValueSource.class)
@@ -56,7 +56,7 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "Neutrik 1/4\" Jack", category = "Electro-Mechanical", author = "Branislav Stojkovic",
description = "1/4\" mono/stereo phono jack based on Neutrik NMJx series, PCB or Panel-mount",
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "J", autoEdit = false, transformer = NeutrikJack1_4Transformer.class,
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "J", transformer = NeutrikJack1_4Transformer.class,
enableCache = true)
public class NeutrikJack1_4 extends AbstractMultiPartComponent<JackType> {
@@ -59,7 +59,7 @@ import org.diylc.utils.SwitchUtils;
@ComponentDescriptor(name = "Slide Switch", category = "Electro-Mechanical",
author = "Branislav Stojkovic", description = "Panel mounted slide switch",
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "SW", autoEdit = false,
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "SW",
enableCache = true, transformer = MiniToggleSwitchTransformer.class)
public class SlideSwitch extends AbstractTransparentComponent<SlideSwitchType> implements ISwitch, IGerberComponentSimple {
@@ -51,7 +51,7 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "Humbucker Pickup", category = "Guitar", author = "Branislav Stojkovic",
description = "Double-coil humbucker guitar pickup (PAF, Mini Humbuckers, Filtertrons)",
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "PKP", autoEdit = false,
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "PKP",
keywordPolicy = KeywordPolicy.SHOW_TAG, keywordTag = "Guitar Wiring Diagram", enableCache = true,
transformer = GuitarPickupTransformer.class)
public class HumbuckerPickup extends AbstractGuitarPickup {
@@ -52,7 +52,7 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "Jazz Bass Pickup", category = "Guitar", author = "Branislav Stojkovic",
description = "Single coil pickup for Jazz Bass and similar guitars",
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "PKP", autoEdit = false,
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "PKP",
keywordPolicy = KeywordPolicy.SHOW_TAG, keywordTag = "Guitar Wiring Diagram", enableCache = true,
transformer = GuitarPickupTransformer.class)
public class JazzBassPickup extends AbstractSingleOrHumbuckerPickup {
@@ -151,8 +151,13 @@ public class LeverSwitch extends AbstractAngledComponent<LeverSwitch.LeverSwitch
g2d.draw(body[2]);
if (getShowMarkers()) {
String[] markers =
SwitchUtils.getSwitchingMarkers(this, getControlPointCount(), false);
String[] markers;
if(type == LeverSwitchType.DP3T_5pos_2502N) {
markers = new String[]{"1", "2", "3", "4", "5", "6", "7", "8"};
} else {
markers = SwitchUtils.getSwitchingMarkers(this, getControlPointCount(), false);
}
double theta = getAngle().getValueRad();
g2d.setColor(MARKER_COLOR);
@@ -203,11 +208,13 @@ public class LeverSwitch extends AbstractAngledComponent<LeverSwitch.LeverSwitch
|| type == LeverSwitchType.DP3T_5pos) {
x += terminalLength;
yOffset = 7;
} else if (type == LeverSwitchType.DP3T_5pos_Import) {
} else if (type == LeverSwitchType.DP3T_5pos_Import || type == LeverSwitchType.DP3T_5pos_2502N) {
yOffset = 10;
} else if (type == LeverSwitchType._6_WAY_OG) {
x += terminalLength;
yOffset = 8;
} else if (type == LeverSwitchType._4P3T) {
yOffset = 8;
} else {
yOffset = 12;
}
@@ -226,11 +233,21 @@ public class LeverSwitch extends AbstractAngledComponent<LeverSwitch.LeverSwitch
- (waferLength - terminalSpacing * yOffset) / 2, waferThickness,
waferLength));
if (type == LeverSwitchType._4P5T) {
if (type == LeverSwitchType._4P5T || type == LeverSwitchType._6P5T) {
waferArea.add(new Area(new Rectangle2D.Double(x - terminalLength / 2 - waferThickness / 2 + waferSpacing, y
- (waferLength - terminalSpacing * 12) / 2, waferThickness,
waferLength)));
}
if (type == LeverSwitchType._6P5T) {
waferArea.add(new Area(new Rectangle2D.Double(x - terminalLength / 2 - waferThickness / 2 + waferSpacing * 2, y
- (waferLength - terminalSpacing * 12) / 2, waferThickness,
waferLength)));
}
if (type == LeverSwitchType._4P3T) {
waferArea.add(new Area(new Rectangle2D.Double(x - terminalLength / 2 - waferThickness / 2 + waferSpacing, y
- (waferLength - terminalSpacing * 8) / 2, waferThickness,
waferLength)));
}
double theta = getAngle().getValueRad();
@@ -294,6 +311,7 @@ public class LeverSwitch extends AbstractAngledComponent<LeverSwitch.LeverSwitch
}
break;
case DP3T_5pos_Import:
case DP3T_5pos_2502N:
controlPoints = new Point2D[8];
terminalSpacing *= 1.5;
for (int i = 0; i < controlPoints.length; i++) {
@@ -319,6 +337,21 @@ public class LeverSwitch extends AbstractAngledComponent<LeverSwitch.LeverSwitch
controlPoints[i + 12] = new Point2D.Double(x + waferSpacing, y + i * terminalSpacing + (i >= 6 ? terminalSpacing : 0));
}
break;
case _6P5T:
controlPoints = new Point2D[36];
for (int i = 0; i < controlPoints.length / 3; i++) {
controlPoints[i] = new Point2D.Double(x, y + i * terminalSpacing + (i >= 6 ? terminalSpacing : 0));
controlPoints[i + 12] = new Point2D.Double(x + waferSpacing, y + i * terminalSpacing + (i >= 6 ? terminalSpacing : 0));
controlPoints[i + 24] = new Point2D.Double(x + waferSpacing * 2, y + i * terminalSpacing + (i >= 6 ? terminalSpacing : 0));
}
break;
case _4P3T:
controlPoints = new Point2D[16];
for (int i = 0; i < controlPoints.length / 2; i++) {
controlPoints[i] = new Point2D.Double(x, y + i * terminalSpacing + (i >= 4 ? terminalSpacing : 0));
controlPoints[i + 8] = new Point2D.Double(x + waferSpacing, y + i * terminalSpacing + (i >= 4 ? terminalSpacing : 0));
}
break;
}
// Rotate if needed
@@ -447,7 +480,16 @@ public class LeverSwitch extends AbstractAngledComponent<LeverSwitch.LeverSwitch
|| (index2 == 11 && index2 - index1 == 5 - position);
case _4P5T:
return ((index1 == 0 || index1 == 12) && index2 - index1 == position + 1)
|| ((index2 == 11 || index2 == 23) && index2 - index1 == 5 - position);
|| ((index2 == 11 || index2 == 23) && index2 - index1 == 5 - position);
case _6P5T:
return ((index1 == 0 || index1 == 12 || index1 == 24) && index2 - index1 == position + 1)
|| ((index2 == 11 || index2 == 23 || index2 == 35) && index2 - index1 == 5 - position);
case _4P3T:
return ((index1 == 0 || index1 == 8) && index2 - index1 == position + 1)
|| ((index2 == 7 || index2 == 15) && index2 - index1 == 3 - position);
case DP3T_5pos_2502N:
positionConnections = DP3T_5pos_2502N_CONNECTIONS.get(position);
break;
}
if (positionConnections != null) {
@@ -526,6 +568,32 @@ public class LeverSwitch extends AbstractAngledComponent<LeverSwitch.LeverSwitch
) // position 6
);
private static final List<List<int[]>> DP3T_5pos_2502N_CONNECTIONS = Arrays.asList(
Arrays.asList(
new int[] { 0, 3 }
), // position 1
Arrays.asList(
new int[] { 0, 3 },
new int[] { 1, 3 },
new int[] { 4, 7 },
new int[] { 5, 7 }
), // position 2
Arrays.asList(
new int[] { 0, 3 },
new int[] { 2, 3 },
new int[] { 6, 7 }
), // position 3
Arrays.asList(
new int[] { 1, 3 },
new int[] { 2, 3 },
new int[] { 5, 7 }
), // position 4
Arrays.asList(
new int[] { 2, 3 },
new int[] { 6, 7 }
) // position 5
);
@DynamicEditableProperty(source = LeverSwitchPositionPropertyValueSource.class)
@EditableProperty(name = "Selected Position")
@Override
@@ -559,7 +627,10 @@ public class LeverSwitch extends AbstractAngledComponent<LeverSwitch.LeverSwitch
_4P5T("4P5T (Super/Mega)", 5),
DP4T("DP4T (4-Position Tele)", 4),
_6_WAY_OG("DP4T (6-Position Oak Grigsby)", 6),
DP5T("DP5T", 5);
DP5T("DP5T", 5),
_6P5T("6P5T (5-Position 6 Pole AxLabs)", 5),
_4P3T("4P3T (3-Position 4 Pole AxLabs)", 3),
DP3T_5pos_2502N("DP3T (Import 5-Position 2502N)", 5);
private final int positionCount;
private String title;
@@ -53,7 +53,7 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "P-90 Single Coil Pickup", category = "Guitar", author = "Branislav Stojkovic",
description = "Single coil P-90 guitar pickup, both \"dog ear\" and \"soap bar\"",
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "PKP", autoEdit = false,
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "PKP",
keywordPolicy = KeywordPolicy.SHOW_TAG, keywordTag = "Guitar Wiring Diagram", enableCache = true)
public class P90Pickup extends AbstractSingleOrHumbuckerPickup {
@@ -52,7 +52,7 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "P- Bass Pickup", category = "Guitar", author = "Branislav Stojkovic",
description = "Split-coil pickup for P-Bass and similar guitars",
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "PKP", autoEdit = false,
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "PKP",
keywordPolicy = KeywordPolicy.SHOW_TAG, keywordTag = "Guitar Wiring Diagram", enableCache = true,
transformer = GuitarPickupTransformer.class)
public class PBassPickup extends AbstractSingleOrHumbuckerPickup {
@@ -53,7 +53,7 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "S1 Switch", category = "Guitar", author = "Branislav Stojkovic",
description = "Fender S1 4 pole pushbutton switch", zOrder = IDIYComponent.COMPONENT,
instanceNamePrefix = "SW", autoEdit = false, enableCache = true)
instanceNamePrefix = "SW", enableCache = true)
public class S1Switch extends AbstractTransparentComponent<Void> implements ISwitch {
private static final long serialVersionUID = 1L;
@@ -208,9 +208,9 @@ public class S1Switch extends AbstractTransparentComponent<Void> implements ISwi
double dy = 0;
if (i % 3 == 1) {
dy = -1;
} else if (i % 3 == 0) {
dy = 1;
} else if (i % 3 == 0) {
dy = -1;
}
Stroke stroke = g2d.getStroke();
@@ -280,9 +280,9 @@ public class S1Switch extends AbstractTransparentComponent<Void> implements ISwi
@Override
public String getPositionName(int position) {
if (position == 0) {
return "Up";
return "Down";
}
return "Down";
return "Up";
}
@EditableProperty(name = "Markers")
@@ -54,7 +54,7 @@ import org.diylc.utils.Constants;
@ComponentDescriptor(name = "Single Coil Pickup", category = "Guitar", author = "Branislav Stojkovic",
description = "Single coil guitar pickup, both Strat and Tele style",
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "PKP", autoEdit = false,
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "PKP",
keywordPolicy = KeywordPolicy.SHOW_TAG, keywordTag = "Guitar Wiring Diagram", enableCache = true,
transformer = GuitarPickupTransformer.class)
public class SingleCoilPickup extends AbstractSingleOrHumbuckerPickup {
@@ -40,7 +40,7 @@ import java.awt.geom.*;
@ComponentDescriptor(name = "Stingray Bass Pickup", category = "Guitar", author = "JD",
description = "Humbucker Bass Pickup for Musicman Stingray and similar guitars",
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "PKP", autoEdit = false,
zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "PKP",
keywordPolicy = KeywordPolicy.SHOW_TAG, keywordTag = "Guitar Wiring Diagram",
enableCache = true, transformer = GuitarPickupTransformer.class)
@@ -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, autoEdit = false, transformer = SimpleComponentTransformer.class)
bomPolicy = BomPolicy.SHOW_ALL_NAMES, 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, autoEdit = false,
flexibleZOrder = true, bomPolicy = BomPolicy.SHOW_ALL_NAMES,
transformer = SimpleComponentTransformer.class)
public class Polygon extends AbstractShape {
@@ -39,7 +39,7 @@ 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, autoEdit = false,
zOrder = IDIYComponent.COMPONENT, flexibleZOrder = true, bomPolicy = BomPolicy.SHOW_ALL_NAMES,
transformer = SimpleComponentTransformer.class)
public class Rectangle extends AbstractShapeWithDimensions {
@@ -53,7 +53,7 @@ import org.diylc.core.measures.SizeUnit;
@ComponentDescriptor(name = "Tape Measure", author = "Branislav Stojkovic", category = "Misc",
creationMethod = CreationMethod.POINT_BY_POINT, instanceNamePrefix = "TM", description = "Measures distance between the two points",
zOrder = IDIYComponent.COMPONENT, bomPolicy = BomPolicy.NEVER_SHOW, autoEdit = false,
zOrder = IDIYComponent.COMPONENT, bomPolicy = BomPolicy.NEVER_SHOW,
transformer = SimpleComponentTransformer.class)
public class TapeMeasure extends AbstractLeadedComponent<String> {
@@ -27,6 +27,7 @@ import java.awt.geom.Point2D;
import org.diylc.common.IComponentTransformer;
import org.diylc.common.OrientationHV;
import org.diylc.components.electromechanical.MiniToggleSwitch;
import org.diylc.components.electromechanical.ToggleSwitchType;
import org.diylc.core.IDIYComponent;
public class MiniToggleSwitchTransformer implements IComponentTransformer {
@@ -107,7 +108,8 @@ public class MiniToggleSwitchTransformer implements IComponentTransformer {
MiniToggleSwitch sw = (MiniToggleSwitch) component;
if (direction == IComponentTransformer.HORIZONTAL) {
double dx = 2 * (center.getX() - sw.getControlPoint(1).getX());
double dx = 2 * (center.getX() -
(sw.getControlPoint(0).getX() + sw.getControlPoint(sw.getControlPointCount() - 1).getX()) / 2);
double dy = 0;
for (int i = 0; i < sw.getControlPointCount(); i++) {
@@ -116,7 +118,8 @@ public class MiniToggleSwitchTransformer implements IComponentTransformer {
}
} else {
double dx = 0;
double dy = 2 * (center.getY() - sw.getControlPoint(1).getY());
double dy = 2 * (center.getY() -
(sw.getControlPoint(0).getY() + sw.getControlPoint(sw.getControlPointCount() - 1).getY()) / 2);
for (int i = 0; i < sw.getControlPointCount(); i++) {
Point2D p = sw.getControlPoint(i);
@@ -28,6 +28,34 @@ public class LeverSwitchTests extends AbstractSwitchTests {
leverSwitch.setValue(LeverSwitchType._4P5T);
testSwitch(leverSwitch, validCombinations);
}
@Test
public void testLeverSwitch6P5T() {
LeverSwitch leverSwitch = new LeverSwitch();
String[] validCombinations = new String[] {
"0,0,1", "0,6,11", "0,12,13", "0,18,23", "0,24,25", "0,30,35",
"1,0,2", "1,7,11", "1,12,14", "1,19,23", "1,24,26", "1,31,35",
"2,0,3", "2,8,11", "2,12,15", "2,20,23", "2,24,27", "2,32,35",
"3,0,4", "3,9,11", "3,12,16", "3,21,23", "3,24,28", "3,33,35",
"4,0,5", "4,10,11", "4,12,17", "4,22,23", "4,24,29", "4,34,35"
};
leverSwitch.setValue(LeverSwitchType._6P5T);
testSwitch(leverSwitch, validCombinations);
}
@Test
public void testLeverSwitch4P3T() {
LeverSwitch leverSwitch = new LeverSwitch();
String[] validCombinations = new String[] {
"0,0,1", "0,4,7", "0,8,9", "0,12,15",
"1,0,2", "1,5,7", "1,8,10", "1,13,15",
"2,0,3", "2,6,7", "2,8,11", "2,14,15",
};
leverSwitch.setValue(LeverSwitchType._4P3T);
testSwitch(leverSwitch, validCombinations);
}
@Test
public void testLeverSwitchImport() {
@@ -50,4 +78,20 @@ public class LeverSwitchTests extends AbstractSwitchTests {
leverSwitch.setValue(LeverSwitchType._6_WAY_OG);
testSwitch(leverSwitch, validCombinations);
}
@Test
public void testLeverSwitchImport2502N() {
LeverSwitch leverSwitch = new LeverSwitch();
String[] validCombinations = new String[] {
"0,0,3",
"1,0,3", "1,1,3", "1,4,7", "1,5,7",
"2,0,3", "2,2,3", "2,6,7",
"3,1,3", "3,2,3", "3,5,7",
"4,2,3", "4,6,7",
};
leverSwitch.setValue(LeverSwitchType.DP3T_5pos_2502N);
testSwitch(leverSwitch, validCombinations);
}
}
@@ -1,24 +1,58 @@
#!/bin/bash
#set -x # Enable debug mode
set -x # Enable debug mode
# Get the AppImage directory
if [ -n "$APPDIR" ]; then
HERE="$APPDIR/usr/bin"
JRE_PATH="$APPDIR/usr/lib/jre/bin/java"
JRE_DIR="$APPDIR/usr/lib/jre"
else
HERE="$(cd "$(dirname "$0")" && pwd)"
JRE_PATH="$(dirname "$HERE")/lib/jre/bin/java"
JRE_DIR="$(dirname "$HERE")/lib/jre"
fi
echo "diylc.sh: Running from directory: $HERE"
echo "diylc.sh: Looking for JRE at: $JRE_PATH"
# Use system Java
JAVA="$(which java)"
if [ -z "$JAVA" ] || [ ! -x "$JAVA" ]; then
echo "diylc.sh: ERROR - System Java not found or not executable!"
echo "diylc.sh: Please install Java 17 or later"
# List directory contents for debugging
echo "diylc.sh: Contents of $(dirname "$JRE_PATH"):"
ls -la "$(dirname "$JRE_PATH")"
# Use bundled JRE
if [ ! -f "$JRE_PATH" ]; then
echo "diylc.sh: ERROR - Bundled JRE not found at: $JRE_PATH"
exit 1
fi
echo "diylc.sh: Using system Java: $JAVA"
# Check if we're running from a mounted AppImage
if [[ "$HERE" == /tmp/.mount_* ]]; then
echo "diylc.sh: Running from mounted AppImage"
# Create a temporary directory for the JRE
TEMP_DIR="/tmp/diylc-jre-$$"
mkdir -p "$TEMP_DIR"
# Copy the entire JRE directory to the temporary location
echo "diylc.sh: Copying JRE to temporary location: $TEMP_DIR"
cp -r "$JRE_DIR"/* "$TEMP_DIR/"
chmod -R +x "$TEMP_DIR/bin"
JRE_PATH="$TEMP_DIR/bin/java"
# Clean up the temporary directory on exit
trap 'rm -rf "$TEMP_DIR"' EXIT
fi
# Verify permissions
echo "diylc.sh: Verifying JRE permissions..."
ls -la "$JRE_PATH"
if [ ! -x "$JRE_PATH" ]; then
echo "diylc.sh: ERROR - JRE is not executable at: $JRE_PATH"
echo "diylc.sh: Current permissions: $(ls -l "$JRE_PATH")"
exit 1
fi
echo "diylc.sh: Using JRE: $JRE_PATH"
cd "$HERE"
echo "diylc.sh: Current working directory: $(pwd)"
@@ -26,15 +60,17 @@ echo "diylc.sh: Current working directory: $(pwd)"
# Debug: Print command before execution
echo "diylc.sh: Executing DIYLCStarter with Java"
exec "$JAVA" \
-Xms1024m \
-Xmx4096m \
-cp "$APPDIR/usr/lib/diylc.jar" \
--add-opens java.base/java.util=ALL-UNNAMED \
--add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/java.text=ALL-UNNAMED \
--add-opens java.desktop/java.awt=ALL-UNNAMED \
--add-opens java.desktop/java.awt.font=ALL-UNNAMED \
--add-opens java.desktop/java.awt.geom=ALL-UNNAMED \
-Dorg.diylc.scriptRun=true \
org.diylc.DIYLCStarter "$@"
# Execute Java directly with the command array
exec "$JRE_PATH" \
-Xms1024m \
-Xmx4096m \
-cp "$(dirname "$HERE")/lib/diylc.jar" \
--add-opens java.base/java.util=ALL-UNNAMED \
--add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/java.text=ALL-UNNAMED \
--add-opens java.desktop/java.awt=ALL-UNNAMED \
--add-opens java.desktop/java.awt.font=ALL-UNNAMED \
--add-opens java.desktop/java.awt.geom=ALL-UNNAMED \
-Dorg.diylc.scriptRun=true \
org.diylc.DIYLCStarter "$@"
+3 -3
View File
@@ -12,11 +12,11 @@ ChangesAssociations=yes
AlwaysShowDirOnReadyPage=yes
; Only allow the installer to run on x64-compatible systems,
; and enable 64-bit install mode.
ArchitecturesAllowed={#arch}
ArchitecturesInstallIn64BitMode={#arch}
ArchitecturesAllowed=x64compatible
ArchitecturesInstallIn64BitMode=x64compatible
[Files]
Source: "diylc-{#arch}.exe"; DestDir: "{app}"; DestName: "diylc.exe"
Source: "diylc-x64.exe"; DestDir: "{app}"; DestName: "diylc.exe"
Source: "..\..\src\main\resources\icons\diylc_file.ico"; DestDir: "{app}"
Source: "..\..\target\diylc.jar"; DestDir: "{app}"
Source: "..\..\target\jre_win\*"; DestDir: "{app}\jre17"; Flags: ignoreversion recursesubdirs
+1
View File
@@ -182,6 +182,7 @@ DP3T (Standard 5-Position Strat)
DP4T (4-Position Tele)
DP4T (6-Position Oak Grigsby)
DP5T
6P5T
DPDT
DPDT (On/Off/On)
DPDT (On/On)
+64 -12
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>org.diylc</groupId>
<artifactId>diylc-parent</artifactId>
<version>5.4.0</version>
<version>5.7.0</version>
</parent>
<artifactId>diylc-swing</artifactId>
@@ -317,12 +317,49 @@
</properties>
<build>
<plugins>
<!-- Create AppDir structure and copy files -->
<!-- Download and extract JRE -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>download-extract-jre</id>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<!-- Clean old jre folder -->
<delete dir="${jre.dest.linux}" />
<!-- Create folder to hold extracted JRE -->
<mkdir dir="${jre.dest.linux}" />
<!-- Download JRE for Linux -->
<mkdir dir="${project.build.directory}/downloads" />
<get src="${jdk.url.linux}"
dest="${project.build.directory}/downloads/${jdk.archive.linux}"
usetimestamp="true" />
<!-- Extract JRE -->
<untar src="${project.build.directory}/downloads/${jdk.archive.linux}"
dest="${jre.dest.linux}"
compression="gzip" />
<!-- Move files up one level -->
<move todir="${jre.dest.linux}">
<fileset dir="${jre.dest.linux}/jdk-${jdk.version}-jre" />
</move>
<!-- Remove empty top-level folder -->
<delete dir="${jre.dest.linux}/jdk-${jdk.version}-jre" />
</target>
</configuration>
</execution>
<!-- Create AppDir structure and copy files -->
<execution>
<id>create-appdir</id>
<phase>package</phase>
@@ -339,7 +376,22 @@
<!-- Copy app files -->
<copy file="${project.build.directory}/diylc.jar" todir="${appdir.dir}/usr/lib" />
<!-- Copy JRE -->
<copy todir="${appdir.dir}/usr/lib/jre">
<fileset dir="${jre.dest.linux}" />
</copy>
<!-- Make JRE files executable -->
<chmod perm="755">
<fileset dir="${appdir.dir}/usr/lib/jre/bin" includes="**/*" />
</chmod>
<!-- Ensure JRE binaries are executable in the source directory too -->
<chmod perm="755">
<fileset dir="${jre.dest.linux}/bin" includes="**/*" />
</chmod>
<!-- Copy AppImage specific files -->
<copy file="${project.basedir}/deploy/linux/appimage/diylc.sh" tofile="${appdir.dir}/usr/bin/diylc.sh" />
<copy file="${project.basedir}/deploy/linux/appimage/AppRun" tofile="${appdir.dir}/AppRun" />
@@ -581,7 +633,7 @@
<argument>${project.basedir}:/workspace</argument>
<argument>amake/innosetup</argument>
<argument>/Ddiylcver=${project.version}</argument>
<argument>/Darch=x64</argument>
<!--<argument>/Darch=win64 arm64</argument>-->
<argument>/workspace/deploy/win/install.iss</argument>
</arguments>
</configuration>
@@ -839,11 +891,11 @@
<echo message="Creating unsigned zip for distribution..."/>
<!-- Create unsigned zip for distribution -->
<zip destfile="${project.build.directory}/diylc-${project.version}-osx-x86-unsigned.zip">
<zip destfile="${project.build.directory}/diylc-${project.version}-osx-intel-unsigned.zip">
<zipfileset filemode="755" dir="${project.build.directory}/DIYLC.app" prefix="DIYLC.app" />
<zipfileset dir="${project.basedir}/deploy/mac" includes="README.txt" />
</zip>
<echo message="Unsigned zip created at: ${project.build.directory}/diylc-${project.version}-osx-x86-unsigned.zip"/>
<echo message="Unsigned zip created at: ${project.build.directory}/diylc-${project.version}-osx-intel-unsigned.zip"/>
<!-- First remove existing signatures -->
<echo message="Removing any existing signatures..."/>
@@ -984,10 +1036,10 @@
<echo message="Creating final signed and notarized zip package..."/>
<!-- Create final signed and notarized zip package -->
<zip destfile="${project.build.directory}/diylc-${project.version}-osx-x86.zip">
<zip destfile="${project.build.directory}/diylc-${project.version}-osx-intel.zip">
<zipfileset filemode="755" dir="${project.build.directory}/DIYLC.app" prefix="DIYLC.app" />
</zip>
<echo message="Final package created at: ${project.build.directory}/diylc-${project.version}-osx-x86.zip"/>
<echo message="Final package created at: ${project.build.directory}/diylc-${project.version}-osx-intel.zip"/>
<echo message="Mac build process completed successfully!"/>
</target>
</configuration>
@@ -1153,11 +1205,11 @@
<echo message="Creating unsigned zip for distribution..."/>
<!-- Create unsigned zip for distribution -->
<zip destfile="${project.build.directory}/diylc-${project.version}-osx-arm-unsigned.zip">
<zip destfile="${project.build.directory}/diylc-${project.version}-osx-silicon-unsigned.zip">
<zipfileset filemode="755" dir="${project.build.directory}/DIYLC.app" prefix="DIYLC.app" />
<zipfileset dir="${project.basedir}/deploy/mac" includes="README.txt" />
</zip>
<echo message="Unsigned zip created at: ${project.build.directory}/diylc-${project.version}-osx-arm-unsigned.zip"/>
<echo message="Unsigned zip created at: ${project.build.directory}/diylc-${project.version}-osx-silicon-unsigned.zip"/>
<!-- First remove existing signatures -->
<echo message="Removing any existing signatures..."/>
@@ -1298,10 +1350,10 @@
<echo message="Creating final signed and notarized zip package..."/>
<!-- Create final signed and notarized zip package -->
<zip destfile="${project.build.directory}/diylc-${project.version}-osx-arm.zip">
<zip destfile="${project.build.directory}/diylc-${project.version}-osx-silicon.zip">
<zipfileset filemode="755" dir="${project.build.directory}/DIYLC.app" prefix="DIYLC.app" />
</zip>
<echo message="Final package created at: ${project.build.directory}/diylc-${project.version}-osx-arm.zip"/>
<echo message="Final package created at: ${project.build.directory}/diylc-${project.version}-osx-silicon.zip"/>
<echo message="Mac build process completed successfully!"/>
</target>
</configuration>
@@ -33,6 +33,7 @@ import org.diylc.appframework.miscutils.PropertyInjector;
import org.diylc.appframework.miscutils.Utils;
import org.diylc.swingframework.export.DrawingExporter;
import org.diylc.swingframework.fonts.FontOptimizer;
import org.diylc.utils.ThemeUtils;
import com.thoughtworks.xstream.XStream;
import org.diylc.common.IPlugInPort;
import org.diylc.core.IView;
@@ -71,6 +72,9 @@ public class DIYLCStarter {
}
public static void runDIYLC(String[] args) {
// Apply OS dark theme settings before creating any UI components
ThemeUtils.applyOSDarkTheme();
// Initialize splash screen
DIYLCSplash splash = null;
Exception splashException = null;
@@ -137,6 +141,8 @@ public class DIYLCStarter {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// Apply dark theme colors if dark mode is detected
ThemeUtils.applyDarkThemeColors();
} catch (Exception e) {
LOG.error("Could not set Look&Feel", e);
}
@@ -324,7 +324,7 @@ public class MainFrame extends JFrame implements ISwingUI {
@Override
public void injectGUIComponent(JComponent component, int position, boolean collapsible, String visibilityConfigKey) throws BadPositionException {
LOG.info(String.format("injectGUIComponent(%s, %s, %s, %s)", component.getClass().getName(), position, collapsible, visibilityConfigKey));
// LOG.info(String.format("injectGUIComponent(%s, %s, %s, %s)", component.getClass().getName(), position, collapsible, visibilityConfigKey));
if (collapsible) {
CollapsiblePanel panel = new CollapsiblePanel(position, visibilityConfigKey);
panel.add(component);
@@ -354,8 +354,8 @@ public class MainFrame extends JFrame implements ISwingUI {
@Override
public void injectMenuAction(Action action, String menuName) {
LOG.info(String.format("injectMenuAction(%s, %s)", action == null ? "Separator" : action.getValue(Action.NAME),
menuName));
// LOG.info(String.format("injectMenuAction(%s, %s)", action == null ? "Separator" : action.getValue(Action.NAME),
// menuName));
if (menuName != null)
menuName = LangUtil.translate(menuName);
@@ -55,6 +55,21 @@ public class ActionBarPlugin implements IPlugIn {
private static final Logger LOG = Logger.getLogger(ActionBarPlugin.class);
private static final SupportOption[] SUPPORT_OPTIONS = new SupportOption[] {
new SupportOption(
"Enjoying DIYLC? Click here to buy me a coffee :)",
"Donate",
IconLoader.Donate,
HelpMenuPlugin.DONATE_URL
),
new SupportOption(
"Become a Patreon and enjoy perks",
"Join Patreon",
IconLoader.Patreon,
HelpMenuPlugin.PATREON_URL
)
};
private IPlugInPort plugInPort;
private ISwingUI swingUI;
@@ -62,13 +77,12 @@ public class ActionBarPlugin implements IPlugIn {
private ActionToolbar contextActionToolbar;
private ConfigToolbar configToolbar;
private JLabel donateLabel;
private static final String DONATE_HTML = LangUtil.translate("Enjoying DIYLC? Click here to buy me a coffee :)");
private static final String DONATE_HTML_SHORT = LangUtil.translate("Donate");
private SupportOption selectedOption;
private Consumer<Boolean> highlightConnectedAreasUpdateAction;
public ActionBarPlugin(ISwingUI swingUI) {
this.swingUI = swingUI;
this.selectedOption = SUPPORT_OPTIONS[(int)(Math.random() * SUPPORT_OPTIONS.length)];
}
public JPanel getActionPanel() {
@@ -110,12 +124,9 @@ public class ActionBarPlugin implements IPlugIn {
public JLabel getDonateLabel() {
if (donateLabel == null) {
donateLabel = new JLabel(DONATE_HTML);
// Map<TextAttribute, Integer> fontAttributes = new HashMap<TextAttribute, Integer>();
// fontAttributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
// donateLabel.setFont(donateLabel.getFont().deriveFont(fontAttributes));
donateLabel = new JLabel(selectedOption.longText);
donateLabel.setForeground(Color.blue);
donateLabel.setIcon(IconLoader.Donate.getIcon());
donateLabel.setIcon(selectedOption.icon.getIcon());
donateLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
this.swingUI.getOwnerFrame().addComponentListener(new ComponentAdapter() {
@@ -126,11 +137,15 @@ public class ActionBarPlugin implements IPlugIn {
@Override
public void componentResized(ComponentEvent e) {
if (rootPane.getWidth() < 940) {
if (!DONATE_HTML_SHORT.equals(donateLabel.getText()))
donateLabel.setText(DONATE_HTML_SHORT);
if (!selectedOption.shortText.equals(donateLabel.getText())) {
donateLabel.setText(selectedOption.shortText);
donateLabel.setIcon(selectedOption.icon.getIcon());
}
} else {
if (!DONATE_HTML.equals(donateLabel.getText()))
donateLabel.setText(DONATE_HTML);
if (!selectedOption.longText.equals(donateLabel.getText())) {
donateLabel.setText(selectedOption.longText);
donateLabel.setIcon(selectedOption.icon.getIcon());
}
}
}
});
@@ -140,9 +155,11 @@ public class ActionBarPlugin implements IPlugIn {
@Override
public void mouseClicked(MouseEvent e) {
try {
Utils.openURL(HelpMenuPlugin.DONATE_URL);
Utils.openURL(selectedOption.url);
} catch (Exception e1) {
swingUI.showMessage("Web browser launching failed. Please visit " + HelpMenuPlugin.DONATE_URL + " to donate. Thank you!", "Error", ISwingUI.INFORMATION_MESSAGE);
swingUI.showMessage("Web browser launching failed. Please visit " +
selectedOption.url +
" to support DIYLC. Thank you!", "Error", ISwingUI.INFORMATION_MESSAGE);
}
}
});
@@ -225,4 +242,16 @@ public class ActionBarPlugin implements IPlugIn {
OperationMode.HIGHLIGHT_CONNECTED_AREAS);
}
}
private record SupportOption(
String longText,
String shortText,
IconLoader icon,
String url
) {
public SupportOption {
longText = LangUtil.translate(longText);
shortText = LangUtil.translate(shortText);
}
}
}
@@ -45,7 +45,7 @@ public class AutoSavePlugin implements IPlugIn {
private static final Logger LOG = Logger.getLogger(AutoSavePlugin.class);
public static long BACKUP_FREQ_MS = 60 * 1000;
public static int MAX_TOTAL_SIZE_MB = 64;
public static int MAX_TOTAL_SIZE_MB = 1024;
private ExecutorService executor;
@@ -146,42 +146,35 @@ public class CanvasPanel extends JComponent implements Autoscroll {
}
private void initializeActions() {
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
getInputMap(JComponent.WHEN_FOCUSED)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_QUOTE, 0), "repeatLast");
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
getInputMap(JComponent.WHEN_FOCUSED)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "clearSlot");
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, KeyEvent.CTRL_DOWN_MASK), "zoomIn");
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, KeyEvent.CTRL_DOWN_MASK), "zoomIn");
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, KeyEvent.META_DOWN_MASK), "zoomIn");
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, KeyEvent.CTRL_DOWN_MASK), "zoomIn");
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, KeyEvent.META_DOWN_MASK), "zoomIn");
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, KeyEvent.META_DOWN_MASK), "zoomIn");
getInputMap(JComponent.WHEN_FOCUSED)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0), "zoomIn");
getInputMap(JComponent.WHEN_FOCUSED)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, 0), "zoomIn");
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, KeyEvent.CTRL_DOWN_MASK), "zoomOut");
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, KeyEvent.CTRL_DOWN_MASK), "zoomOut");
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, KeyEvent.META_DOWN_MASK), "zoomOut");
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, KeyEvent.META_DOWN_MASK), "zoomOut");
getInputMap(JComponent.WHEN_FOCUSED)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, 0), "zoomIn");
getInputMap(JComponent.WHEN_FOCUSED)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, 0), "zoomIn");
getInputMap(JComponent.WHEN_FOCUSED)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0), "zoomOut");
getInputMap(JComponent.WHEN_FOCUSED)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, 0), "zoomOut");
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_0, KeyEvent.CTRL_DOWN_MASK), "resetZoom");
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD0, KeyEvent.CTRL_DOWN_MASK), "resetZoom");
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_0, KeyEvent.META_DOWN_MASK), "resetZoom");
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD0, KeyEvent.META_DOWN_MASK), "resetZoom");
getInputMap(JComponent.WHEN_FOCUSED)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_0, 0), "resetZoom");
getInputMap(JComponent.WHEN_FOCUSED)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD0, 0), "resetZoom");
getInputMap(JComponent.WHEN_FOCUSED)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_0, 0), "resetZoom");
getInputMap(JComponent.WHEN_FOCUSED)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD0, 0), "resetZoom");
for (int i = 1; i <= 12; i++) {
final int x = i;
@@ -198,36 +191,6 @@ public class CanvasPanel extends JComponent implements Autoscroll {
});
}
// getActionMap().put("zoomIn", new AbstractAction() {
//
// private static final long serialVersionUID = 1L;
//
// @Override
// public void actionPerformed(ActionEvent e) {
// LOG.debug("Keyboard zoom-in triggered");
// double oldZoom = CanvasPanel.this.plugInPort.getZoomLevel();
// Double[] availableZoomLevels = CanvasPanel.this.plugInPort.getAvailableZoomLevels();
// int index = Arrays.binarySearch(availableZoomLevels, oldZoom);
// if (index < availableZoomLevels.length - 1)
// CanvasPanel.this.plugInPort.setZoomLevel(availableZoomLevels[index + 1]);
// }
// });
//
// getActionMap().put("zoomOut", new AbstractAction() {
//
// private static final long serialVersionUID = 1L;
//
// @Override
// public void actionPerformed(ActionEvent e) {
// LOG.debug("Keyboard zoom-out triggered");
// double oldZoom = CanvasPanel.this.plugInPort.getZoomLevel();
// Double[] availableZoomLevels = CanvasPanel.this.plugInPort.getAvailableZoomLevels();
// int index = Arrays.binarySearch(availableZoomLevels, oldZoom);
// if (index > 0)
// CanvasPanel.this.plugInPort.setZoomLevel(availableZoomLevels[index - 1]);
// }
// });
getActionMap().put("resetZoom", new AbstractAction() {
private static final long serialVersionUID = 1L;
@@ -104,6 +104,7 @@ public class CloudPlugIn implements IPlugIn {
getManageAccountAction().setEnabled(false);
getChangePasswordAction().setEnabled(false);
getLogOutAction().setEnabled(false);
getConnectPatreonAction().setEnabled(false);
}
public CloudBrowserFrame getCloudBrowser() {
@@ -179,6 +180,7 @@ public class CloudPlugIn implements IPlugIn {
getUploadAction().setEnabled(true);
getManageProjectsAction().setEnabled(true);
getChangePasswordAction().setEnabled(true);
getConnectPatreonAction().setEnabled(true);
}
public void loggedOut() {
@@ -190,6 +192,7 @@ public class CloudPlugIn implements IPlugIn {
getUploadAction().setEnabled(false);
getManageProjectsAction().setEnabled(false);
getChangePasswordAction().setEnabled(false);
getConnectPatreonAction().setEnabled(false);
}
// Getters for action classes
@@ -60,7 +60,6 @@ public class ConnectPatreonAction extends AbstractAction {
return;
}
String username = plugInPort.getCloudService().getCurrentUsername();
username ="a b c";
String url = "http://diy-fever.com/diylc/api/v1/ai/patreon_login.php?username=" + URLEncoder.encode(username, StandardCharsets.UTF_8);
try {
java.awt.Desktop.getDesktop().browse(new java.net.URI(url));
@@ -37,6 +37,7 @@ import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.UIManager;
import org.diylc.swingframework.ButtonDialog;
@@ -47,6 +48,14 @@ public class UserEditDialog extends ButtonDialog {
private static final long serialVersionUID = 1L;
private static final String BULLET = "&nbsp;&nbsp;&nbsp;&#8226;&nbsp;";
private static final String PASSWORD_RULES_HTML = "<html>" + BULLET
+ "Password must be at least 8 characters long<br>" + BULLET
+ "Password must contain at least one letter<br>" + BULLET
+ "Password must contain at least one number<br>" + BULLET
+ "Passwords must match exactly<br>"
+ "</html>";
private JPanel mainPanel;
private JTextField userNameField;
@@ -65,6 +74,8 @@ public class UserEditDialog extends ButtonDialog {
private UserEntity existingEntity;
private JLabel passwordRulesLabel;
public UserEditDialog(JFrame owner, UserEntity existingEntity) {
super(owner, existingEntity == null ? "New Account" : "Manage Account", new String[] {OK, CANCEL});
@@ -117,16 +128,33 @@ public class UserEditDialog extends ButtonDialog {
gbc.gridy = 2;
mainPanel.add(new JLabel("Confirm Password:"), gbc);
// Add password rules hint pane
gbc.gridy = 3;
gbc.gridwidth = 2;
gbc.gridx = 0;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(4, 2, 4, 2); // Add more vertical padding
mainPanel.add(getPasswordRulesLabel(), gbc);
// Reset gridwidth and gridx for remaining fields
gbc.gridwidth = 1;
gbc.gridx = 1;
gbc.insets = new Insets(2, 2, 2, 2); // Reset insets
gbc.gridy = 4;
}
gbc.gridy = 3;
gbc.gridy = existingEntity == null ? 4 : 3;
gbc.gridx = 0;
mainPanel.add(new JLabel("eMail:"), gbc);
gbc.gridy = 4;
mainPanel.add(new JLabel("Website:"), gbc);
gbc.gridy = existingEntity == null ? 5 : 4;
gbc.gridx = 0;
mainPanel.add(new JLabel("Website (optional):"), gbc);
gbc.gridy = 5;
mainPanel.add(new JLabel("Short Bio:"), gbc);
gbc.gridy = existingEntity == null ? 6 : 5;
gbc.gridx = 0;
mainPanel.add(new JLabel("Short Bio (optional):"), gbc);
gbc.gridx = 1;
gbc.fill = GridBagConstraints.HORIZONTAL;
@@ -144,13 +172,13 @@ public class UserEditDialog extends ButtonDialog {
mainPanel.add(getConfirmPasswordField(), gbc);
}
gbc.gridy = 3;
gbc.gridy = existingEntity == null ? 4 : 3;
mainPanel.add(getEmailField(), gbc);
gbc.gridy = 4;
gbc.gridy = existingEntity == null ? 5 : 4;
mainPanel.add(getWebsiteField(), gbc);
gbc.gridy = 5;
gbc.gridy = existingEntity == null ? 6 : 5;
gbc.fill = GridBagConstraints.BOTH;
mainPanel.add(getBioPane(), gbc);
}
@@ -161,7 +189,16 @@ public class UserEditDialog extends ButtonDialog {
this.userName = getUserNameField().getText();
String password = new String(getPasswordField().getPassword());
String confirmPassword = new String(getConfirmPasswordField().getPassword());
if (password.equals(confirmPassword)) {
// Password validation
boolean hasValidPassword = true;
if (existingEntity == null) {
hasValidPassword = password.length() >= 8 &&
password.matches(".*[a-zA-Z].*") && // at least one letter
password.matches(".*\\d.*"); // at least one number
}
if (password.equals(confirmPassword) && hasValidPassword) {
this.password = password;
} else {
this.password = null;
@@ -342,4 +379,16 @@ public class UserEditDialog extends ButtonDialog {
}
return bioArea;
}
private JLabel getPasswordRulesLabel() {
if (passwordRulesLabel == null) {
passwordRulesLabel = new JLabel();
passwordRulesLabel.setOpaque(true);
passwordRulesLabel.setBackground(UIManager.getColor("ToolTip.background"));
passwordRulesLabel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEtchedBorder(),
BorderFactory.createEmptyBorder(2, 2, 2, 2)));
passwordRulesLabel.setText(PASSWORD_RULES_HTML);
}
return passwordRulesLabel;
}
}
@@ -81,7 +81,7 @@ public class CloudBrowserFrame extends JFrame implements ISimpleView {
super(TITLE);
this.swingUI = swingUI;
this.setIconImage(IconLoader.Cloud.getImage());
this.setPreferredSize(new Dimension(700, 640));
this.setPreferredSize(new Dimension(800, 640));
this.plugInPort = plugInPort;
this.searchSession = new SearchSession(plugInPort);
@@ -63,10 +63,11 @@ public class HelpMenuPlugin implements IPlugIn {
public static String PLUGIN_URL = "https://github.com/bancika/diy-layout-creator/blob/wiki/PluginAPI.md";
public static String BUG_URL = "https://github.com/bancika/diy-layout-creator/issues";
public static String DONATE_URL = "http://diy-fever.com/donate";
public static String PATREON_URL = "http://diy-fever.com/patreon";
private IPlugInPort plugInPort;
private AboutDialog aboutDialog;
private ISwingUI swingUI;
private final ISwingUI swingUI;
public HelpMenuPlugin(ISwingUI swingUI) {
this.swingUI = swingUI;
@@ -0,0 +1,381 @@
/*
*
* 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.io.BufferedReader;
import java.io.InputStreamReader;
import javax.swing.UIManager;
import org.apache.log4j.Logger;
import org.diylc.appframework.miscutils.Utils;
/**
* Utility class for detecting and applying OS dark theme settings.
*
* Note: Modern Java (11+) with system Look and Feel should automatically
* respect OS dark theme settings, but this utility provides explicit detection
* and ensures compatibility with older Java versions.
*
* @author Branislav Stojkovic
*/
public class ThemeUtils {
private static final Logger LOG = Logger.getLogger(ThemeUtils.class);
/**
* Detects and applies OS dark theme settings.
* This must be called before creating any AWT/Swing components.
*/
public static void applyOSDarkTheme() {
try {
if (Utils.isMac()) {
applyMacOSDarkTheme();
} else if (Utils.isWindows()) {
detectWindowsDarkTheme();
} else {
detectLinuxDarkTheme();
}
} catch (Exception e) {
LOG.warn("Error detecting OS dark theme: " + e.getMessage(), e);
}
}
/**
* Applies dark theme colors to UIManager after the Look and Feel is set.
* This should be called after UIManager.setLookAndFeel().
*
* @return true if dark mode was detected and colors were applied, false otherwise
*/
public static boolean applyDarkThemeColors() {
try {
if (Utils.isMac()) {
return applyMacOSDarkThemeColors();
} else if (Utils.isWindows()) {
return applyWindowsDarkThemeColors();
} else {
return applyLinuxDarkThemeColors();
}
} catch (Exception e) {
LOG.warn("Error applying dark theme colors: " + e.getMessage(), e);
return false;
}
}
/**
* Detects and applies macOS dark theme settings.
*/
private static void applyMacOSDarkTheme() {
// macOS: Check if dark mode is enabled
// Modern Java (11+) should automatically detect this, but we'll check explicitly
String appearance = System.getProperty("apple.awt.application.appearance");
if (appearance == null) {
// Try to detect dark mode via system command (for older Java versions)
try {
Process process = Runtime.getRuntime().exec(
new String[]{"defaults", "read", "-g", "AppleInterfaceStyle"});
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String result = reader.readLine();
if ("Dark".equals(result)) {
// Note: Setting this property after JVM start may not always work,
// but it's worth trying for older Java versions
System.setProperty("apple.awt.application.appearance", "NSAppearanceNameDarkAqua");
LOG.info("Detected macOS dark mode - attempting to apply dark theme");
} else {
LOG.debug("macOS light mode detected");
}
} catch (Exception e) {
// If detection fails, that's okay - modern Java will handle it automatically
LOG.debug("Could not detect macOS theme via defaults command: " + e.getMessage());
}
} else if ("NSAppearanceNameDarkAqua".equals(appearance)) {
LOG.info("macOS dark mode already detected by JVM");
} else {
LOG.debug("macOS appearance: " + appearance);
}
}
/**
* Detects Windows dark theme settings.
*/
private static void detectWindowsDarkTheme() {
// Windows: Try to detect dark theme via registry
try {
Process process = Runtime.getRuntime().exec(
new String[]{"reg", "query",
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
"/v", "AppsUseLightTheme"});
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
if (line.contains("AppsUseLightTheme")) {
// If value is 0, dark theme is enabled
if (line.contains("0x0") || line.endsWith("0")) {
// For Windows, the system Look and Feel should automatically respect dark mode
// in Java 11+, but we can log it for debugging
LOG.info("Detected Windows dark mode");
}
break;
}
}
} catch (Exception e) {
LOG.debug("Could not detect Windows theme via registry: " + e.getMessage());
}
}
/**
* Detects Linux dark theme settings.
*/
private static void detectLinuxDarkTheme() {
// Linux: Check GTK theme or environment variables
try {
String gtkTheme = System.getenv("GTK_THEME");
if (gtkTheme != null && (gtkTheme.toLowerCase().contains("dark") ||
gtkTheme.toLowerCase().contains("Dark"))) {
LOG.info("Detected Linux dark theme: " + gtkTheme);
}
// Also check COLORFGBG environment variable (used by some terminals/desktops)
String colorFgBg = System.getenv("COLORFGBG");
if (colorFgBg != null) {
// Format is typically "15;0" where second number < 8 indicates dark background
String[] parts = colorFgBg.split(";");
if (parts.length > 1) {
try {
int bgColor = Integer.parseInt(parts[parts.length - 1]);
if (bgColor < 8) {
LOG.info("Detected Linux dark background via COLORFGBG");
}
} catch (NumberFormatException e) {
// Ignore
}
}
}
} catch (Exception e) {
LOG.debug("Could not detect Linux theme: " + e.getMessage());
}
}
/**
* Applies macOS dark theme colors to UIManager.
* @return true if dark mode is active
*/
private static boolean applyMacOSDarkThemeColors() {
boolean isDarkMode = false;
// Check if dark mode is active
String appearance = System.getProperty("apple.awt.application.appearance");
if (appearance == null) {
try {
Process process = Runtime.getRuntime().exec(
new String[]{"defaults", "read", "-g", "AppleInterfaceStyle"});
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String result = reader.readLine();
isDarkMode = "Dark".equals(result);
} catch (Exception e) {
LOG.debug("Could not detect macOS theme: " + e.getMessage());
// Try to infer from current UI colors
Color menuBarBg = UIManager.getColor("MenuBar.background");
if (menuBarBg != null && isDarkColor(menuBarBg)) {
isDarkMode = true;
}
}
} else {
isDarkMode = "NSAppearanceNameDarkAqua".equals(appearance);
}
if (isDarkMode) {
LOG.info("Applying macOS dark theme colors");
applyDarkColors();
return true;
}
return false;
}
/**
* Applies Windows dark theme colors to UIManager.
* @return true if dark mode is active
*/
private static boolean applyWindowsDarkThemeColors() {
boolean isDarkMode = false;
try {
Process process = Runtime.getRuntime().exec(
new String[]{"reg", "query",
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
"/v", "AppsUseLightTheme"});
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
if (line.contains("AppsUseLightTheme")) {
// If value is 0, dark theme is enabled
if (line.contains("0x0") || line.endsWith("0")) {
isDarkMode = true;
}
break;
}
}
} catch (Exception e) {
LOG.debug("Could not detect Windows theme: " + e.getMessage());
// Try to infer from current UI colors
Color menuBarBg = UIManager.getColor("MenuBar.background");
if (menuBarBg != null && isDarkColor(menuBarBg)) {
isDarkMode = true;
}
}
if (isDarkMode) {
LOG.info("Applying Windows dark theme colors");
applyDarkColors();
return true;
}
return false;
}
/**
* Applies Linux dark theme colors to UIManager.
* @return true if dark mode is active
*/
private static boolean applyLinuxDarkThemeColors() {
boolean isDarkMode = false;
try {
String gtkTheme = System.getenv("GTK_THEME");
if (gtkTheme != null && (gtkTheme.toLowerCase().contains("dark") ||
gtkTheme.toLowerCase().contains("Dark"))) {
isDarkMode = true;
}
// Also check COLORFGBG
if (!isDarkMode) {
String colorFgBg = System.getenv("COLORFGBG");
if (colorFgBg != null) {
String[] parts = colorFgBg.split(";");
if (parts.length > 1) {
try {
int bgColor = Integer.parseInt(parts[parts.length - 1]);
if (bgColor < 8) {
isDarkMode = true;
}
} catch (NumberFormatException e) {
// Ignore
}
}
}
}
// Try to infer from current UI colors
if (!isDarkMode) {
Color menuBarBg = UIManager.getColor("MenuBar.background");
if (menuBarBg != null && isDarkColor(menuBarBg)) {
isDarkMode = true;
}
}
} catch (Exception e) {
LOG.debug("Could not detect Linux theme: " + e.getMessage());
}
if (isDarkMode) {
LOG.info("Applying Linux dark theme colors");
applyDarkColors();
return true;
}
return false;
}
/**
* Applies dark theme colors to common UI components.
* Only overrides colors that would cause visibility issues (e.g., dark text on dark background).
*/
private static void applyDarkColors() {
// Menu bar and menu colors - fix text colors if background is dark
Color menuBarBg = UIManager.getColor("MenuBar.background");
Color menuBarFg = UIManager.getColor("MenuBar.foreground");
if (menuBarBg != null && isDarkColor(menuBarBg)) {
// Background is dark, ensure text is light
if (menuBarFg == null || isDarkColor(menuBarFg)) {
UIManager.put("MenuBar.foreground", Color.WHITE);
}
// Fix menu colors
Color menuBg = UIManager.getColor("Menu.background");
Color menuFg = UIManager.getColor("Menu.foreground");
if (menuBg == null || isDarkColor(menuBg)) {
UIManager.put("Menu.background", new Color(50, 50, 50));
}
if (menuFg == null || (menuBg != null && isDarkColor(menuBg) && isDarkColor(menuFg))) {
UIManager.put("Menu.foreground", Color.WHITE);
}
UIManager.put("Menu.selectionBackground", new Color(60, 60, 60));
UIManager.put("Menu.selectionForeground", Color.WHITE);
// Fix menu item colors
Color menuItemBg = UIManager.getColor("MenuItem.background");
Color menuItemFg = UIManager.getColor("MenuItem.foreground");
if (menuItemBg == null || isDarkColor(menuItemBg)) {
UIManager.put("MenuItem.background", new Color(50, 50, 50));
}
if (menuItemFg == null || (menuItemBg != null && isDarkColor(menuItemBg) && isDarkColor(menuItemFg))) {
UIManager.put("MenuItem.foreground", Color.WHITE);
}
UIManager.put("MenuItem.selectionBackground", new Color(60, 60, 60));
UIManager.put("MenuItem.selectionForeground", Color.WHITE);
}
// Popup menu colors
Color popupBg = UIManager.getColor("PopupMenu.background");
if (popupBg == null || isDarkColor(popupBg)) {
UIManager.put("PopupMenu.background", new Color(50, 50, 50));
UIManager.put("PopupMenu.foreground", Color.WHITE);
UIManager.put("PopupMenu.borderColor", new Color(70, 70, 70));
}
// Checkbox and radio button menu items
Color checkBoxMenuItemBg = UIManager.getColor("CheckBoxMenuItem.background");
if (checkBoxMenuItemBg == null || isDarkColor(checkBoxMenuItemBg)) {
UIManager.put("CheckBoxMenuItem.background", new Color(50, 50, 50));
UIManager.put("CheckBoxMenuItem.foreground", Color.WHITE);
UIManager.put("CheckBoxMenuItem.selectionBackground", new Color(60, 60, 60));
UIManager.put("CheckBoxMenuItem.selectionForeground", Color.WHITE);
}
Color radioMenuItemBg = UIManager.getColor("RadioButtonMenuItem.background");
if (radioMenuItemBg == null || isDarkColor(radioMenuItemBg)) {
UIManager.put("RadioButtonMenuItem.background", new Color(50, 50, 50));
UIManager.put("RadioButtonMenuItem.foreground", Color.WHITE);
UIManager.put("RadioButtonMenuItem.selectionBackground", new Color(60, 60, 60));
UIManager.put("RadioButtonMenuItem.selectionForeground", Color.WHITE);
}
}
/**
* Checks if a color is dark (suitable for dark theme).
*/
private static boolean isDarkColor(Color color) {
if (color == null) {
return false;
}
// Calculate luminance
double luminance = (0.299 * color.getRed() + 0.587 * color.getGreen() + 0.114 * color.getBlue()) / 255;
return luminance < 0.5;
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>org.diylc</groupId>
<artifactId>diylc-parent</artifactId>
<version>5.4.0</version>
<version>5.7.0</version>
<packaging>pom</packaging>
<name>DIY Layout Creator</name>
+1 -1
View File
@@ -129,7 +129,7 @@ INSERT INTO diylc_knowledge_base (category, section, content) ('Component Types'
"type" : "java.lang.Boolean"
}, {
"name" : "Type",
"type" : "org.diylc.components.guitar.LeverSwitch$LeverSwitchType(DP3T (Standard 3-Position Strat),DP3T (Standard 5-Position Strat),DP3T (Import 5-Position Strat),4P5T (Super/Mega),DP4T (4-Position Tele),DP4T (6-Position Oak Grigsby),DP5T)"
"type" : "org.diylc.components.guitar.LeverSwitch$LeverSwitchType(DP3T (Standard 3-Position Strat),DP3T (Standard 5-Position Strat),DP3T (Import 5-Position Strat),4P5T (Super/Mega),DP4T (4-Position Tele),DP4T (6-Position Oak Grigsby),DP5T,6P5T (5-Position 6 Pole AxLabs)),4P3T (3-Position 3 Pole AxLabs),DP3T (Import 5-Position 2502N)"
}, {
"name" : "Angle",
"type" : "org.diylc.core.Angle"