From 9d8555c4b753452c511c86ff408e9165d87ca68c Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Sat, 15 Mar 2025 23:29:47 +0100 Subject: [PATCH] switch position display --- .../org/diylc/common/PropertyWrapper.java | 11 +- .../diylc/core/IDynamicPropertySource.java | 34 ++++ .../src/main/java/org/diylc/core/ISwitch.java | 20 ++ .../SwitchPositionPropertyValueSource.java | 38 ++++ .../annotations/DynamicEditableProperty.java | 42 ++++ .../core/annotations/EditableProperty.java | 1 + .../diylc/presenter/ComponentProcessor.java | 25 ++- .../java/org/diylc/utils/ResourceUtils.java | 21 ++ .../org/diylc/utils/ScaledBufferedImage.java | 21 ++ .../java/org/diylc/utils/SwitchUtils.java | 89 +++++++++ .../java/org/diylc/utils/SwitchUtilsTest.java | 136 +++++++++++++ .../diylc/components/guitar/LeverSwitch.java | 109 +++++----- .../swing/actions/CheckProximityAction.java | 4 +- .../swing/gui/editor/DynamicValueEditor.java | 187 ++++++++++++++++++ .../swing/gui/editor/FieldEditorFactory.java | 4 + 15 files changed, 686 insertions(+), 56 deletions(-) create mode 100644 diylc/diylc-core/src/main/java/org/diylc/core/IDynamicPropertySource.java create mode 100644 diylc/diylc-core/src/main/java/org/diylc/core/SwitchPositionPropertyValueSource.java create mode 100644 diylc/diylc-core/src/main/java/org/diylc/core/annotations/DynamicEditableProperty.java create mode 100644 diylc/diylc-core/src/main/java/org/diylc/utils/SwitchUtils.java create mode 100644 diylc/diylc-core/src/test/java/org/diylc/utils/SwitchUtilsTest.java create mode 100644 diylc/diylc-swing/src/main/java/org/diylc/swing/gui/editor/DynamicValueEditor.java diff --git a/diylc/diylc-core/src/main/java/org/diylc/common/PropertyWrapper.java b/diylc/diylc-core/src/main/java/org/diylc/common/PropertyWrapper.java index 3232d432..b6184677 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/common/PropertyWrapper.java +++ b/diylc/diylc-core/src/main/java/org/diylc/common/PropertyWrapper.java @@ -24,6 +24,7 @@ package org.diylc.common; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import org.diylc.core.IDynamicPropertySource; import org.diylc.core.IPropertyValidator; /** @@ -45,9 +46,10 @@ public class PropertyWrapper implements Cloneable { private boolean changed = false; private int sortOrder; private Object ownerObject; + private IDynamicPropertySource dynamicPropertySource; public PropertyWrapper(String name, Class type, String getter, String setter, boolean defaultable, - IPropertyValidator validator, int sortOrder) { + IPropertyValidator validator, int sortOrder, IDynamicPropertySource dynamicPropertySource) { super(); this.name = name; this.type = type; @@ -56,6 +58,7 @@ public class PropertyWrapper implements Cloneable { this.defaultable = defaultable; this.validator = validator; this.sortOrder = sortOrder; + this.dynamicPropertySource = dynamicPropertySource; this.ownerObject = null; } @@ -138,6 +141,10 @@ public class PropertyWrapper implements Cloneable { return sortOrder; } + public IDynamicPropertySource getDynamicPropertySource() { + return dynamicPropertySource; + } + // @Override // public Object clone() throws CloneNotSupportedException { // // Try to invoke clone method on value if possible. @@ -152,7 +159,7 @@ public class PropertyWrapper implements Cloneable { public Object clone() throws CloneNotSupportedException { PropertyWrapper clone = new PropertyWrapper(this.name, this.type, this.getter, this.setter, this.defaultable, this.validator, - this.sortOrder); + this.sortOrder, this.dynamicPropertySource); clone.value = this.value; clone.changed = this.changed; clone.unique = this.unique; diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/IDynamicPropertySource.java b/diylc/diylc-core/src/main/java/org/diylc/core/IDynamicPropertySource.java new file mode 100644 index 00000000..1f48d97c --- /dev/null +++ b/diylc/diylc-core/src/main/java/org/diylc/core/IDynamicPropertySource.java @@ -0,0 +1,34 @@ +/* + + DIY Layout Creator (DIYLC). + Copyright (c) 2009-2018 held jointly by the individual authors. + + This file is part of DIYLC. + + DIYLC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DIYLC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DIYLC. If not, see . + +*/ +package org.diylc.core; + +import java.util.List; +import java.util.Map; + +public interface IDynamicPropertySource { + + void setComponent(IDIYComponent component); + + List getAvailableValues(); + + String getDisplayValue(Object value); +} diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/ISwitch.java b/diylc/diylc-core/src/main/java/org/diylc/core/ISwitch.java index 7c826c70..994621e0 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/core/ISwitch.java +++ b/diylc/diylc-core/src/main/java/org/diylc/core/ISwitch.java @@ -21,6 +21,8 @@ */ package org.diylc.core; +import java.awt.*; + /** * Interface for all component that can act as switches. They must have at least one position. * @@ -28,6 +30,13 @@ package org.diylc.core; */ public interface ISwitch { + Color[] POLE_COLORS = new Color[] { Color.decode("#FFD1DC"), + Color.decode("#FFE5B4"), + Color.decode("#FFFACD"), + Color.decode("#B8F2E6"), + Color.decode("#AEC6CF"), + Color.decode("#E6E6FA")}; + /** * @return a positive number of positions. */ @@ -48,4 +57,15 @@ public interface ISwitch { * @return true if the two points are connected in the given switch position, false otherwise. */ boolean arePointsConnected(int index1, int index2, int position); + + /** + * @return currently selected switch position, or null if none + */ + default Integer getSelectedPosition() { + return null; + } + + default Boolean getRenderConnectedTerminals() { + return false; + } } diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/SwitchPositionPropertyValueSource.java b/diylc/diylc-core/src/main/java/org/diylc/core/SwitchPositionPropertyValueSource.java new file mode 100644 index 00000000..ff94f52f --- /dev/null +++ b/diylc/diylc-core/src/main/java/org/diylc/core/SwitchPositionPropertyValueSource.java @@ -0,0 +1,38 @@ +package org.diylc.core; + +import java.util.*; + +public class SwitchPositionPropertyValueSource implements IDynamicPropertySource { + + private IDIYComponent component; + + @Override + public void setComponent(IDIYComponent component) { + this.component = component; + } + + @Override + public List getAvailableValues() { + if (component == null) { + throw new RuntimeException("Component not set"); + } + if (!(component instanceof ISwitch sw)) { + return List.of(); + } + int positionCount = sw.getPositionCount(); + List res = new ArrayList<>(); + res.add(null); + for (int i = 0; i < positionCount; i++) { + res.add(i); + } + return res; + } + + @Override + public String getDisplayValue(Object value) { + if (value == null) { + return ""; + } + return "#" + (((Integer)value) + 1); + } +} diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/annotations/DynamicEditableProperty.java b/diylc/diylc-core/src/main/java/org/diylc/core/annotations/DynamicEditableProperty.java new file mode 100644 index 00000000..44f326c8 --- /dev/null +++ b/diylc/diylc-core/src/main/java/org/diylc/core/annotations/DynamicEditableProperty.java @@ -0,0 +1,42 @@ +/* + + DIY Layout Creator (DIYLC). + Copyright (c) 2009-2018 held jointly by the individual authors. + + This file is part of DIYLC. + + DIYLC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DIYLC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DIYLC. If not, see . + +*/ +package org.diylc.core.annotations; + +import org.diylc.core.IDynamicPropertySource; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/*** + * Used for properties having dynamic set of possible values. Only to be used in addition to + * EditableProperty annotation. + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface DynamicEditableProperty { + + /** + * Optional parameter for dynamic values + * + * @return + */ + Class source() ; +} diff --git a/diylc/diylc-core/src/main/java/org/diylc/core/annotations/EditableProperty.java b/diylc/diylc-core/src/main/java/org/diylc/core/annotations/EditableProperty.java index ba31063a..575b2a77 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/core/annotations/EditableProperty.java +++ b/diylc/diylc-core/src/main/java/org/diylc/core/annotations/EditableProperty.java @@ -25,6 +25,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.diylc.core.AllowAllValidator; +import org.diylc.core.IDynamicPropertySource; import org.diylc.core.IPropertyValidator; /** diff --git a/diylc/diylc-core/src/main/java/org/diylc/presenter/ComponentProcessor.java b/diylc/diylc-core/src/main/java/org/diylc/presenter/ComponentProcessor.java index 3b839c92..7a865ddf 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/presenter/ComponentProcessor.java +++ b/diylc/diylc-core/src/main/java/org/diylc/presenter/ComponentProcessor.java @@ -41,11 +41,9 @@ import org.diylc.common.IComponentTransformer; import org.diylc.common.PropertyWrapper; import org.diylc.core.CreationMethod; import org.diylc.core.IDIYComponent; +import org.diylc.core.IDynamicPropertySource; import org.diylc.core.IPropertyValidator; -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.*; import org.diylc.lang.LangUtil; /** @@ -189,10 +187,16 @@ public class ComponentProcessor { LangUtil.translate(o.toString()); } } + + IDynamicPropertySource dynamicPropertySource = null; + if (getter.isAnnotationPresent(DynamicEditableProperty.class)) { + DynamicEditableProperty dynamicAnnotation = getter.getAnnotation(DynamicEditableProperty.class); + dynamicPropertySource = getDynamicPropertySource(dynamicAnnotation.source()); + } PropertyWrapper property = new PropertyWrapper(name, getter.getReturnType(), getter.getName(), setterName, - annotation.defaultable(), validator, annotation.sortOrder()); + annotation.defaultable(), validator, annotation.sortOrder(), dynamicPropertySource); properties.add(property); } } @@ -278,6 +282,17 @@ public class ComponentProcessor { return validator; } + private IDynamicPropertySource getDynamicPropertySource(Class clazz) { + IDynamicPropertySource source; + try { + source = clazz.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + LOG.error("Could not instantiate source for " + clazz.getName(), e); + return null; + } + return source; + } + private IComponentTransformer getComponentTransformer(Class clazz) { if (clazz == null) return null; diff --git a/diylc/diylc-core/src/main/java/org/diylc/utils/ResourceUtils.java b/diylc/diylc-core/src/main/java/org/diylc/utils/ResourceUtils.java index 9a638584..6a1cbb25 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/utils/ResourceUtils.java +++ b/diylc/diylc-core/src/main/java/org/diylc/utils/ResourceUtils.java @@ -1,3 +1,24 @@ +/* + + DIY Layout Creator (DIYLC). + Copyright (c) 2009-2018 held jointly by the individual authors. + + This file is part of DIYLC. + + DIYLC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DIYLC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DIYLC. If not, see . + +*/ package org.diylc.utils; import org.apache.log4j.Logger; diff --git a/diylc/diylc-core/src/main/java/org/diylc/utils/ScaledBufferedImage.java b/diylc/diylc-core/src/main/java/org/diylc/utils/ScaledBufferedImage.java index 78b9db3b..d7b102e5 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/utils/ScaledBufferedImage.java +++ b/diylc/diylc-core/src/main/java/org/diylc/utils/ScaledBufferedImage.java @@ -1,3 +1,24 @@ +/* + + DIY Layout Creator (DIYLC). + Copyright (c) 2009-2018 held jointly by the individual authors. + + This file is part of DIYLC. + + DIYLC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DIYLC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DIYLC. If not, see . + +*/ package org.diylc.utils; import java.awt.Graphics2D; diff --git a/diylc/diylc-core/src/main/java/org/diylc/utils/SwitchUtils.java b/diylc/diylc-core/src/main/java/org/diylc/utils/SwitchUtils.java new file mode 100644 index 00000000..40bac993 --- /dev/null +++ b/diylc/diylc-core/src/main/java/org/diylc/utils/SwitchUtils.java @@ -0,0 +1,89 @@ +/* + + DIY Layout Creator (DIYLC). + Copyright (c) 2009-2018 held jointly by the individual authors. + + This file is part of DIYLC. + + DIYLC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DIYLC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DIYLC. If not, see . + +*/ +package org.diylc.utils; + +import org.diylc.core.ISwitch; + +import java.util.*; + +public class SwitchUtils { + + public static List> getConnectedTerminals(ISwitch switchComponent, int numPoints) { + List> connectedGroups = new ArrayList<>(); + + // Only process if we have a selected position and should render connections + if (switchComponent.getSelectedPosition() == null || !Boolean.TRUE.equals(switchComponent.getRenderConnectedTerminals())) { + return connectedGroups; + } + + // First, collect all direct connections + List connections = new ArrayList<>(); + for (int i = 0; i < numPoints; i++) { + for (int j = i + 1; j < numPoints; j++) { + if (switchComponent.arePointsConnected(i, j, switchComponent.getSelectedPosition())) { + connections.add(new int[]{i, j}); + } + } + } + + // Create a map to track which points are in which group + Map> pointToGroup = new HashMap<>(); + + // Process each connection + for (int[] connection : connections) { + int p1 = connection[0]; + int p2 = connection[1]; + + Set group1 = pointToGroup.get(p1); + Set group2 = pointToGroup.get(p2); + + if (group1 == null && group2 == null) { + // Create new group + Set newGroup = new HashSet<>(); + newGroup.add(p1); + newGroup.add(p2); + pointToGroup.put(p1, newGroup); + pointToGroup.put(p2, newGroup); + } else if (group1 == null) { + // Add p1 to p2's group + group2.add(p1); + pointToGroup.put(p1, group2); + } else if (group2 == null) { + // Add p2 to p1's group + group1.add(p2); + pointToGroup.put(p2, group1); + } else if (group1 != group2) { + // Merge groups + group1.addAll(group2); + for (Integer p : group2) { + pointToGroup.put(p, group1); + } + } + } + + // Collect unique groups + Set> uniqueGroups = new HashSet<>(pointToGroup.values()); + connectedGroups.addAll(uniqueGroups); + + return connectedGroups; + } +} diff --git a/diylc/diylc-core/src/test/java/org/diylc/utils/SwitchUtilsTest.java b/diylc/diylc-core/src/test/java/org/diylc/utils/SwitchUtilsTest.java new file mode 100644 index 00000000..878289e0 --- /dev/null +++ b/diylc/diylc-core/src/test/java/org/diylc/utils/SwitchUtilsTest.java @@ -0,0 +1,136 @@ +package org.diylc.utils; + +import org.diylc.core.ISwitch; +import org.junit.Test; +import static org.junit.Assert.*; + +import java.util.*; + +public class SwitchUtilsTest { + + // Mock switch implementation for testing + private static class MockSwitch implements ISwitch { + private final List connections; + private final Integer selectedPosition; + private final boolean renderConnected; + + public MockSwitch(List connections, Integer selectedPosition, boolean renderConnected) { + this.connections = connections; + this.selectedPosition = selectedPosition; + this.renderConnected = renderConnected; + } + + @Override + public boolean arePointsConnected(int index1, int index2, int position) { + if (position != selectedPosition) return false; + return connections.stream() + .anyMatch(conn -> (conn[0] == index1 && conn[1] == index2) || + (conn[0] == index2 && conn[1] == index1)); + } + + @Override + public Integer getSelectedPosition() { + return selectedPosition; + } + + @Override + public Boolean getRenderConnectedTerminals() { + return renderConnected; + } + + // Other interface methods not needed for testing + @Override public int getPositionCount() { return 0; } + @Override public String getPositionName(int position) { return null; } + } + + @Test + public void testNoConnections() { + MockSwitch mockSwitch = new MockSwitch(Collections.emptyList(), 0, true); + List> groups = SwitchUtils.getConnectedTerminals(mockSwitch, 5); + assertTrue("Should return empty list when no connections", groups.isEmpty()); + } + + @Test + public void testSingleConnection() { + List connections = Arrays.asList(new int[]{0, 1}); + MockSwitch mockSwitch = new MockSwitch(connections, 0, true); + List> groups = SwitchUtils.getConnectedTerminals(mockSwitch, 5); + + assertEquals("Should have one group", 1, groups.size()); + assertTrue("Group should contain points 0 and 1", + groups.get(0).containsAll(Arrays.asList(0, 1))); + } + + @Test + public void testTransitiveConnections() { + // Test case where 1-2-3 are connected through 2 + List connections = Arrays.asList( + new int[]{1, 2}, + new int[]{2, 3} + ); + MockSwitch mockSwitch = new MockSwitch(connections, 0, true); + List> groups = SwitchUtils.getConnectedTerminals(mockSwitch, 5); + + assertEquals("Should have one group", 1, groups.size()); + assertTrue("Group should contain points 1, 2, and 3", + groups.get(0).containsAll(Arrays.asList(1, 2, 3))); + } + + @Test + public void testMultipleGroups() { + // Test case with two separate groups: {1,2,3} and {4,6,7} + List connections = Arrays.asList( + new int[]{2, 3}, + new int[]{1, 3}, + new int[]{4, 7}, + new int[]{4, 6} + ); + MockSwitch mockSwitch = new MockSwitch(connections, 0, true); + List> groups = SwitchUtils.getConnectedTerminals(mockSwitch, 8); + + assertEquals("Should have two groups", 2, groups.size()); + assertTrue("Groups should contain {1,2,3} and {4,6,7}", + groups.stream().anyMatch(g -> g.containsAll(Arrays.asList(1, 2, 3))) && + groups.stream().anyMatch(g -> g.containsAll(Arrays.asList(4, 6, 7)))); + } + + @Test + public void testRenderConnectedFalse() { + List connections = Arrays.asList(new int[]{0, 1}); + MockSwitch mockSwitch = new MockSwitch(connections, 0, false); + List> groups = SwitchUtils.getConnectedTerminals(mockSwitch, 5); + assertTrue("Should return empty list when renderConnected is false", groups.isEmpty()); + } + + @Test + public void testNoSelectedPosition() { + List connections = Arrays.asList(new int[]{0, 1}); + MockSwitch mockSwitch = new MockSwitch(connections, null, true); + List> groups = SwitchUtils.getConnectedTerminals(mockSwitch, 5); + assertTrue("Should return empty list when no position selected", groups.isEmpty()); + } + + @Test + public void testComplexConnections() { + // Test case with multiple interconnected points + List connections = Arrays.asList( + new int[]{0, 1}, + new int[]{1, 2}, + new int[]{2, 3}, + new int[]{3, 0}, // Creates a circle + new int[]{4, 5}, + new int[]{5, 6}, + new int[]{7, 8} // Separate pair + ); + MockSwitch mockSwitch = new MockSwitch(connections, 0, true); + List> groups = SwitchUtils.getConnectedTerminals(mockSwitch, 9); + + assertEquals("Should have three groups", 3, groups.size()); + assertTrue("Should have group with points 0,1,2,3", + groups.stream().anyMatch(g -> g.containsAll(Arrays.asList(0, 1, 2, 3)))); + assertTrue("Should have group with points 4,5,6", + groups.stream().anyMatch(g -> g.containsAll(Arrays.asList(4, 5, 6)))); + assertTrue("Should have group with points 7,8", + groups.stream().anyMatch(g -> g.containsAll(Arrays.asList(7, 8)))); + } +} diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/guitar/LeverSwitch.java b/diylc/diylc-library/src/main/java/org/diylc/components/guitar/LeverSwitch.java index 5834732b..168abc81 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/guitar/LeverSwitch.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/guitar/LeverSwitch.java @@ -32,28 +32,26 @@ import java.awt.geom.Ellipse2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; -import java.util.Arrays; -import java.util.List; +import java.util.*; +import java.util.stream.IntStream; + import org.diylc.appframework.miscutils.ConfigurationManager; import org.diylc.common.IPlugInPort; import org.diylc.common.ObjectCache; import org.diylc.components.AbstractAngledComponent; import org.diylc.components.transform.AngledComponentTransformer; -import org.diylc.core.ComponentState; -import org.diylc.core.IDIYComponent; -import org.diylc.core.IDrawingObserver; -import org.diylc.core.ISwitch; -import org.diylc.core.Project; -import org.diylc.core.Theme; -import org.diylc.core.VisibilityPolicy; +import org.diylc.core.*; import org.diylc.core.annotations.ComponentDescriptor; +import org.diylc.core.annotations.DynamicEditableProperty; import org.diylc.core.annotations.EditableProperty; import org.diylc.core.annotations.KeywordPolicy; import org.diylc.core.measures.Size; import org.diylc.core.measures.SizeUnit; import org.diylc.utils.Constants; +import static org.diylc.utils.SwitchUtils.getConnectedTerminals; + @ComponentDescriptor(name = "Lever Switch", category = "Guitar", author = "Branislav Stojkovic", description = "Strat-style lever switch", zOrder = IDIYComponent.COMPONENT, instanceNamePrefix = "SW", keywordPolicy = KeywordPolicy.SHOW_TAG, @@ -65,7 +63,6 @@ public class LeverSwitch extends AbstractAngledComponent> connectedTerminals = getConnectedTerminals(this, controlPoints.length); + + body = new Shape[3 + connectedTerminals.size()]; + body[0] = baseArea; + body[1] = waferArea; + for (int i = 0; i <= connectedTerminals.size(); i++) { + body[2 + i] = new Area(); + } + for (int i = 0; i < controlPoints.length; i++) { Point2D point = controlPoints[i]; Area terminal = @@ -222,19 +229,14 @@ public class LeverSwitch extends AbstractAngledComponent connectedTerminals.get(j).contains(finalI)) + .findFirst().orElse(-1); + + ((Area)body[3 + groupIndex]).add(terminal); } - body[2] = terminalArea; - body[3] = commonTerminalArea; // Rotate if needed if (theta != 0) { @@ -250,6 +252,8 @@ public class LeverSwitch extends AbstractAngledComponent. + +*/ +package org.diylc.swing.gui.editor; + +import org.diylc.common.PropertyWrapper; +import org.diylc.core.IDIYComponent; +import org.diylc.core.IDynamicPropertySource; +import org.diylc.lang.LangUtil; +import org.diylc.utils.Constants; + +import javax.swing.*; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; +import java.awt.*; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseListener; +import java.util.Vector; + +public class DynamicValueEditor extends JComboBox { + + private static final long serialVersionUID = 1L; + + private Color oldBg = getBackground(); + + private final PropertyWrapper property; + + public DynamicValueEditor(final PropertyWrapper property) { + this.property = property; + IDynamicPropertySource dynamicPropertySource = property.getDynamicPropertySource(); + if (property.getOwnerObject() == null || !(property.getOwnerObject() instanceof IDIYComponent)) { + setModel(new DefaultComboBoxModel(new Object[0])); + setEnabled(false); + } else { + dynamicPropertySource.setComponent((IDIYComponent) property.getOwnerObject()); + setModel(new DynamicComboBoxModel(dynamicPropertySource)); + } + if (property.isReadOnly()) + setEnabled(false); + + setRenderer(new DefaultListCellRenderer() { + + private static final long serialVersionUID = 1L; + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, + boolean isSelected, boolean cellHasFocus) { + // TODO Auto-generated method stub + return super.getListCellRendererComponent(list, value == null ? null : LangUtil.translate(dynamicPropertySource.getDisplayValue(value)), + index, isSelected, cellHasFocus); + } + + }); + setSelectedItem(property.getValue()); + + addPopupMenuListener(new PopupMenuListener() { + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + // Schedule the update just after the popup becomes visible + SwingUtilities.invokeLater(() -> { + if (getModel() instanceof DynamicComboBoxModel model) { + model.refreshValues(); + } + }); + } + + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {} + + @Override + public void popupMenuCanceled(PopupMenuEvent e) {} + }); + + addItemListener(new ItemListener() { + + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + property.setChanged(true); + setBackground(oldBg); + DynamicValueEditor.this.property.setValue(e.getItem()); + } + } + }); + if (!property.isUnique()) { + setBackground(Constants.MULTI_VALUE_COLOR); + } + } + + class DynamicComboBoxModel implements ComboBoxModel { + private final Vector items; + private Object selectedItem; + private final Vector listeners; + private final IDynamicPropertySource dynamicPropertySource; + + public DynamicComboBoxModel(IDynamicPropertySource source) { + this.dynamicPropertySource = source; + this.items = new Vector<>(); + this.listeners = new Vector<>(); + refreshValues(); + } + + private void refreshValues() { + if (dynamicPropertySource != null) { + java.util.List newValues = dynamicPropertySource.getAvailableValues(); + if (!items.equals(newValues)) { + Object selected = selectedItem; + items.clear(); + items.addAll(newValues); + + // Single update event + ListDataEvent event = new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, items.size()); + for (ListDataListener listener : listeners) { + listener.contentsChanged(event); + } + + // Restore selection if possible + if (selected != null && items.contains(selected)) { + selectedItem = selected; + } else if (!items.isEmpty()) { + selectedItem = items.get(0); + } else { + selectedItem = null; + } + } + } + } + + @Override + public void setSelectedItem(Object anItem) { + if ((selectedItem != null && !selectedItem.equals(anItem)) || + selectedItem == null && anItem != null) { + selectedItem = anItem; + } + } + + @Override + public Object getSelectedItem() { + return selectedItem; + } + + @Override + public int getSize() { + return items.size(); + } + + @Override + public Object getElementAt(int index) { + return items.get(index); + } + + @Override + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } + } +} diff --git a/diylc/diylc-swing/src/main/java/org/diylc/swing/gui/editor/FieldEditorFactory.java b/diylc/diylc-swing/src/main/java/org/diylc/swing/gui/editor/FieldEditorFactory.java index 0bd7337b..a840540b 100644 --- a/diylc/diylc-swing/src/main/java/org/diylc/swing/gui/editor/FieldEditorFactory.java +++ b/diylc/diylc-swing/src/main/java/org/diylc/swing/gui/editor/FieldEditorFactory.java @@ -95,6 +95,10 @@ public class FieldEditorFactory { EnumEditor editor = new EnumEditor(property); return editor; } + if (property.getDynamicPropertySource() != null) { + DynamicValueEditor editor = new DynamicValueEditor(property); + return editor; + } if (Byte.class.isAssignableFrom(property.getType()) || byte.class.isAssignableFrom(property.getType())) { ByteEditor editor = new ByteEditor(property); return editor;