diff --git a/diylc/diylc-core/pom.xml b/diylc/diylc-core/pom.xml index a96995f3..2ee50f0d 100644 --- a/diylc/diylc-core/pom.xml +++ b/diylc/diylc-core/pom.xml @@ -62,6 +62,13 @@ junit test + + + org.mockito + mockito-core + 5.11.0 + test + diff --git a/diylc/diylc-core/src/main/java/org/diylc/common/IPlugInPort.java b/diylc/diylc-core/src/main/java/org/diylc/common/IPlugInPort.java index f5c69b3f..9e76e511 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/common/IPlugInPort.java +++ b/diylc/diylc-core/src/main/java/org/diylc/common/IPlugInPort.java @@ -61,6 +61,7 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP public static final String ANTI_ALIASING_KEY = "antiAliasing"; public static final String HI_QUALITY_RENDER_KEY = "hiQualityRender"; + public static final String HIGH_DPI_RENDERING = "hiDpiRendering"; public static final String EXPORT_GRID_KEY = "exportGrid"; public static final String STICKY_POINTS_KEY = "stickyPoints"; public static final String METRIC_KEY = "metric"; diff --git a/diylc/diylc-core/src/main/java/org/diylc/presenter/DatasheetService.java b/diylc/diylc-core/src/main/java/org/diylc/presenter/DatasheetService.java index 24bc5ad0..abff8506 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/presenter/DatasheetService.java +++ b/diylc/diylc-core/src/main/java/org/diylc/presenter/DatasheetService.java @@ -120,4 +120,10 @@ public class DatasheetService { return null; } + + // Add method to clear cache for testing + public void clearCache() { + datasheetCache.clear(); + lookupCache.clear(); + } } diff --git a/diylc/diylc-core/src/main/java/org/diylc/presenter/DrawingCache.java b/diylc/diylc-core/src/main/java/org/diylc/presenter/DrawingCache.java index e0aacfe2..88e9d789 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/presenter/DrawingCache.java +++ b/diylc/diylc-core/src/main/java/org/diylc/presenter/DrawingCache.java @@ -181,6 +181,11 @@ public class DrawingCache { private CacheValue renderAndCache(IDIYComponent component, G2DWrapper g2d, ComponentState componentState, boolean outlineMode, Project project, double zoom, double scaleFactor, int zOrder) { + + if (!g2d.getClipBounds().intersects(component.getCachingBounds())) { + return null; + } + // if we need to apply caching Point2D firstPoint = component.getControlPoint(0); @@ -311,6 +316,7 @@ public class DrawingCache { this.zoom = zoom; this.dx = dx; this.dy = dy; + this.scaleFactor = scaleFactor; } public IDIYComponent getComponent() { diff --git a/diylc/diylc-core/src/main/resources/update.xml b/diylc/diylc-core/src/main/resources/update.xml index 4d285908..3ed8e068 100644 --- a/diylc/diylc-core/src/main/resources/update.xml +++ b/diylc/diylc-core/src/main/resources/update.xml @@ -249,6 +249,14 @@ 2025-03-09 00:00:00.000 CET + + NEW_FEATURE + Configurable 'High DPI Rendering' feature which makes the diagram smoother on high-resolution displays but can affect performance + + + IMPROVEMENT + Significantly improved rendering performance + IMPROVEMENT Upgraded source code structure to modern standards and introduced Maven diff --git a/diylc/diylc-core/src/test/java/org/diylc/presenter/CalcUtilsTest.java b/diylc/diylc-core/src/test/java/org/diylc/presenter/CalcUtilsTest.java index 611162aa..00d1c46f 100644 --- a/diylc/diylc-core/src/test/java/org/diylc/presenter/CalcUtilsTest.java +++ b/diylc/diylc-core/src/test/java/org/diylc/presenter/CalcUtilsTest.java @@ -1,27 +1,145 @@ package org.diylc.presenter; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; -import org.junit.Test; +import java.awt.geom.Point2D; +import java.util.Arrays; +import java.util.List; +import org.diylc.core.IDIYComponent; import org.diylc.core.measures.Size; import org.diylc.core.measures.SizeUnit; -import org.diylc.presenter.CalcUtils; +import org.junit.Test; public class CalcUtilsTest { - @Test - public void TestRoundToGrid() { - Size spacing = new Size(1d, SizeUnit.mm); - int value = 5; - double s = spacing.convertToPixels(); - double targetPrev = (value - 1) * s; - double target = value * s; - double targetNext = (value + 1) * s; - for (int i = -100; i <= 100; i++) { - double rounded = CalcUtils.roundToGrid(target + i / 100.0 * s, spacing); - double expected = i < -50 ? targetPrev : (i >= 50 ? targetNext : target); - assertTrue("Failed at " + i + " rounded = " + rounded + "; expected = " + expected, Math.abs(rounded - expected) < 1e-10); + @Test + public void testRoundToGrid() { + Size gridSpacing = new Size(10.0, SizeUnit.px); + + // Test exact grid points + assertEquals(10.0, CalcUtils.roundToGrid(10.0, gridSpacing), 0.001); + assertEquals(20.0, CalcUtils.roundToGrid(20.0, gridSpacing), 0.001); + + // Test rounding up + assertEquals(10.0, CalcUtils.roundToGrid(8.0, gridSpacing), 0.001); + assertEquals(20.0, CalcUtils.roundToGrid(16.0, gridSpacing), 0.001); + + // Test rounding down + assertEquals(10.0, CalcUtils.roundToGrid(12.0, gridSpacing), 0.001); + assertEquals(20.0, CalcUtils.roundToGrid(22.0, gridSpacing), 0.001); + + // Test with different grid spacing + Size finerGrid = new Size(5.0, SizeUnit.px); + assertEquals(5.0, CalcUtils.roundToGrid(6.0, finerGrid), 0.001); + assertEquals(15.0, CalcUtils.roundToGrid(13.0, finerGrid), 0.001); + } + + @Test + public void testSnapPointToGrid() { + Size gridSpacing = new Size(10.0, SizeUnit.px); + Point2D point = new Point2D.Double(12.0, 18.0); + + CalcUtils.snapPointToGrid(point, gridSpacing); + + assertEquals(10.0, point.getX(), 0.001); + assertEquals(20.0, point.getY(), 0.001); + + // Test with point exactly on grid + point.setLocation(20.0, 30.0); + CalcUtils.snapPointToGrid(point, gridSpacing); + assertEquals(20.0, point.getX(), 0.001); + assertEquals(30.0, point.getY(), 0.001); + + // Test with different grid spacing + Size finerGrid = new Size(5.0, SizeUnit.px); + point.setLocation(13.0, 28.0); + CalcUtils.snapPointToGrid(point, finerGrid); + assertEquals(15.0, point.getX(), 0.001); + assertEquals(30.0, point.getY(), 0.001); + } + + @Test + public void testFindClosestMultiplierOf() { + Size factor = new Size(1.0, SizeUnit.mm); + Size target = new Size(3.5, SizeUnit.mm); + int step = 1; + + Size result = CalcUtils.findClosestMultiplierOf(factor, target, step); + assertEquals(4.0, result.getValue(), 0.001); + assertEquals(SizeUnit.mm, result.getUnit()); + + // Test with different step + result = CalcUtils.findClosestMultiplierOf(factor, target, 2); + assertEquals(4.0, result.getValue(), 0.001); + + // Test with different factor + factor = new Size(0.5, SizeUnit.mm); + result = CalcUtils.findClosestMultiplierOf(factor, target, step); + assertEquals(3.5, result.getValue(), 0.001); + } + + @Test + public void testSnapPointToObjects() { + Size gridSpacing = new Size(10.0, SizeUnit.px); + Point2D point = new Point2D.Double(12.0, 18.0); + + // Create mock components + IDIYComponent component1 = mock(IDIYComponent.class); + IDIYComponent component2 = mock(IDIYComponent.class); + + // Setup component1 with a sticky point near our test point + when(component1.getControlPointCount()).thenReturn(1); + when(component1.isControlPointSticky(0)).thenReturn(true); + when(component1.getControlPoint(0)).thenReturn(new Point2D.Double(10.0, 20.0)); + + // Setup component2 with a non-sticky point + when(component2.getControlPointCount()).thenReturn(1); + when(component2.isControlPointSticky(0)).thenReturn(false); + when(component2.getControlPoint(0)).thenReturn(new Point2D.Double(11.0, 19.0)); + + List> components = Arrays.asList(component1, component2); + + // Test snapping to sticky point + CalcUtils.snapPointToObjects(point, gridSpacing, null, components); + assertEquals(10.0, point.getX(), 0.001); + assertEquals(20.0, point.getY(), 0.001); + + // Test not snapping when point is too far + point.setLocation(25.0, 25.0); + CalcUtils.snapPointToObjects(point, gridSpacing, null, components); + assertEquals(25.0, point.getX(), 0.001); + assertEquals(25.0, point.getY(), 0.001); + + // Test not snapping to non-sticky points + point.setLocation(15.0, 23.0); // Changed coordinates to be further from sticky point + CalcUtils.snapPointToObjects(point, gridSpacing, null, components); + assertEquals(15.0, point.getX(), 0.001); + assertEquals(23.0, point.getY(), 0.001); + } + + @Test + public void testPointsMatch() { + Point2D point1 = new Point2D.Double(10.0, 20.0); + Point2D point2 = new Point2D.Double(10.0, 20.0); + + // Test exact match + assertTrue(CalcUtils.pointsMatch(point1, point2, 0.001)); + + // Test within delta + point2.setLocation(10.1, 20.1); + assertTrue(CalcUtils.pointsMatch(point1, point2, 0.2)); + + // Test outside delta + assertFalse(CalcUtils.pointsMatch(point1, point2, 0.05)); + + // Test x coordinate outside delta + point2.setLocation(11.0, 20.0); + assertFalse(CalcUtils.pointsMatch(point1, point2, 0.5)); + + // Test y coordinate outside delta + point2.setLocation(10.0, 21.0); + assertFalse(CalcUtils.pointsMatch(point1, point2, 0.5)); } - } } diff --git a/diylc/diylc-core/src/test/java/org/diylc/presenter/ComponentProcessorTest.java b/diylc/diylc-core/src/test/java/org/diylc/presenter/ComponentProcessorTest.java new file mode 100644 index 00000000..27c2b2c4 --- /dev/null +++ b/diylc/diylc-core/src/test/java/org/diylc/presenter/ComponentProcessorTest.java @@ -0,0 +1,219 @@ +package org.diylc.presenter; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.awt.Point; +import java.awt.geom.Point2D; +import java.util.Arrays; +import java.util.List; + +import org.diylc.common.ComponentType; +import org.diylc.common.IComponentTransformer; +import org.diylc.common.PropertyWrapper; +import org.diylc.components.AbstractComponent; +import org.diylc.core.CreationMethod; +import org.diylc.core.IDIYComponent; +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.junit.Before; +import org.junit.Test; + +public class ComponentProcessorTest { + + private ComponentProcessor processor; + + @Before + public void setUp() { + processor = ComponentProcessor.getInstance(); + } + + @ComponentDescriptor( + name = "Test Component", + category = "Test", + author = "Test Author", + description = "Test Description", + instanceNamePrefix = "TEST", + creationMethod = CreationMethod.POINT_BY_POINT, + zOrder = IDIYComponent.COMPONENT, + flexibleZOrder = false, + bomPolicy = BomPolicy.NEVER_SHOW, + autoEdit = false, + enableCache = true, + keywordPolicy = KeywordPolicy.SHOW_VALUE, + keywordTag = "", + enableDatasheet = false, + datasheetCreationStepCount = 0 + ) + public static class TestComponent extends AbstractComponent { + private String testProperty = "test"; + private int numericProperty = 42; + + @EditableProperty + public String getTestProperty() { + return testProperty; + } + + public void setTestProperty(String value) { + this.testProperty = value; + } + + @EditableProperty(name = "Number") + public int getNumericProperty() { + return numericProperty; + } + + public void setNumericProperty(int value) { + this.numericProperty = value; + } + + // Implement required methods + @Override + public void drawIcon(java.awt.Graphics2D g2d, int width, int height) {} + + @Override + public void draw(java.awt.Graphics2D g2d, org.diylc.core.ComponentState componentState, + boolean outlineMode, org.diylc.core.Project project, + org.diylc.core.IDrawingObserver drawingObserver) {} + + @Override + public Point2D getControlPoint(int index) { + return new Point(0, 0); + } + + @Override + public boolean isControlPointSticky(int index) { + return true; + } + + @Override + public int getControlPointCount() { + return 1; + } + + @Override + public void setControlPoint(Point2D point, int index) {} + + @Override + public VisibilityPolicy getControlPointVisibilityPolicy(int index) { + return VisibilityPolicy.WHEN_SELECTED; + } + + @Override + public Object getValue() { + return null; + } + + @Override + public void setValue(Object value) {} + } + + @Test + public void testExtractComponentType() { + ComponentType type = processor.extractComponentTypeFrom(TestComponent.class); + + assertNotNull("Component type should not be null", type); + assertEquals("Test Component", type.getName()); + assertEquals("Test", type.getCategory()); + assertEquals("Test Author", type.getAuthor()); + assertEquals("Test Description", type.getDescription()); + assertEquals("TEST", type.getNamePrefix()); + assertEquals(TestComponent.class, type.getInstanceClass()); + } + + @Test + public void testExtractProperties() { + List properties = processor.extractProperties(TestComponent.class); + + assertNotNull("Properties list should not be null", properties); + assertEquals("Should have 3 properties", 3, properties.size()); + + // Find and verify testProperty + PropertyWrapper testProp = findPropertyByName(properties, "TestProperty"); + assertNotNull("TestProperty should exist", testProp); + assertEquals(String.class, testProp.getType()); + + // Find and verify numericProperty + PropertyWrapper numProp = findPropertyByName(properties, "Number"); + assertNotNull("Number property should exist", numProp); + assertEquals(int.class, numProp.getType()); + + // Find and verify name property from AbstractComponent + PropertyWrapper nameProp = findPropertyByName(properties, "Name"); + assertNotNull("Name property should exist", nameProp); + assertEquals(String.class, nameProp.getType()); + } + + @Test + public void testGetMutualSelectionProperties() throws Exception { + TestComponent comp1 = new TestComponent(); + TestComponent comp2 = new TestComponent(); + + // Set same values + comp1.setTestProperty("same"); + comp2.setTestProperty("same"); + comp1.setNumericProperty(42); + comp2.setNumericProperty(42); + comp1.setName("same name"); + comp2.setName("same name"); + + List mutualProps = processor.getMutualSelectionProperties( + Arrays.asList(comp1, comp2)); + + assertNotNull("Mutual properties should not be null", mutualProps); + assertEquals("Should have 3 mutual properties", 3, mutualProps.size()); + assertTrue("All properties should be unique", + mutualProps.stream().allMatch(PropertyWrapper::isUnique)); + } + + @Test + public void testGetMutualSelectionPropertiesWithDifferentValues() throws Exception { + TestComponent comp1 = new TestComponent(); + TestComponent comp2 = new TestComponent(); + + // Set different values + comp1.setTestProperty("value1"); + comp2.setTestProperty("value2"); + comp1.setNumericProperty(42); + comp2.setNumericProperty(43); + comp1.setName("name1"); + comp2.setName("name2"); + + List mutualProps = processor.getMutualSelectionProperties( + Arrays.asList(comp1, comp2)); + + assertNotNull("Mutual properties should not be null", mutualProps); + assertEquals("Should have 3 mutual properties", 3, mutualProps.size()); + + // Each property should not be unique since they have different values + for (PropertyWrapper prop : mutualProps) { + assertFalse("Property '" + prop.getName() + "' should not be unique", + prop.isUnique()); + } + } + + @Test + public void testComponentPointsTouch() { + TestComponent comp1 = new TestComponent(); + TestComponent comp2 = new TestComponent(); + + assertTrue("Components with same point should touch", + ComponentProcessor.componentPointsTouch(comp1, comp2)); + } + + @Test + public void testHasStickyPoint() { + TestComponent component = new TestComponent(); + assertTrue("Should have sticky point", ComponentProcessor.hasStickyPoint(component)); + } + + private PropertyWrapper findPropertyByName(List properties, String name) { + return properties.stream() + .filter(p -> p.getName().equals(name)) + .findFirst() + .orElse(null); + } +} diff --git a/diylc/diylc-core/src/test/java/org/diylc/presenter/DatasheetServiceTest.java b/diylc/diylc-core/src/test/java/org/diylc/presenter/DatasheetServiceTest.java new file mode 100644 index 00000000..08fdb8ee --- /dev/null +++ b/diylc/diylc-core/src/test/java/org/diylc/presenter/DatasheetServiceTest.java @@ -0,0 +1,266 @@ +package org.diylc.presenter; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class DatasheetServiceTest { + + private DatasheetService service; + + @Mock + private ClassLoader mockClassLoader; + + @Before + public void setUp() { + service = DatasheetService.getInstance(); + service.clearCache(); // Clear cache before each test + Thread.currentThread().setContextClassLoader(mockClassLoader); + } + + @Test + public void testLoadDatasheet() throws Exception { + // Create a mock datasheet content + String datasheetContent = + "1N4001,50V,1A,1.1V\n" + + "1N4002,100V,1A,1.1V\n" + + "1N4003,200V,1A,1.1V"; + + // Create a mock URL that returns our test content + URL mockUrl = mock(URL.class); + when(mockUrl.openStream()).thenReturn( + new ByteArrayInputStream(datasheetContent.getBytes())); + + // Setup the mock ClassLoader to return our mock URL + when(mockClassLoader.getResource("TestComponent.datasheet")) + .thenReturn(mockUrl); + + // Test loading the datasheet + List result = service.loadDatasheet(TestComponent.class); + + // Verify results + assertNotNull("Datasheet should not be null", result); + assertEquals("Should have 3 entries", 3, result.size()); + + // Verify first entry + String[] firstEntry = result.get(0); + assertEquals("1N4001", firstEntry[0]); + assertEquals("50V", firstEntry[1]); + assertEquals("1A", firstEntry[2]); + assertEquals("1.1V", firstEntry[3]); + + // Verify second entry + String[] secondEntry = result.get(1); + assertEquals("1N4002", secondEntry[0]); + assertEquals("100V", secondEntry[1]); + + // Verify third entry + String[] thirdEntry = result.get(2); + assertEquals("1N4003", thirdEntry[0]); + assertEquals("200V", thirdEntry[1]); + } + + @Test + public void testLoadDatasheetFileNotFound() { + // Setup the mock ClassLoader to return null (file not found) + when(mockClassLoader.getResource("TestComponent.datasheet")) + .thenReturn(null); + + List result = service.loadDatasheet(TestComponent.class); + + assertNull("Result should be null when file is not found", result); + } + + @Test + public void testLookupExactMatch() throws Exception { + // Create a mock datasheet content + String datasheetContent = + "TEST,KEY,10.0\n" + + "TEST,KEY,20.0\n" + + "TEST,OTHER,15.0\n" + + "OTHER,KEY,10.0"; + + URL mockUrl = mock(URL.class); + when(mockUrl.openStream()).thenReturn( + new ByteArrayInputStream(datasheetContent.getBytes())); + when(mockClassLoader.getResource("DatasheetServiceTest.datasheet")) + .thenReturn(mockUrl); + + // Create test data + String[] result = service.lookup( + DatasheetServiceTest.class, // Using test class itself as component class + 0.1, // 0.1% tolerance + "TEST|KEY", // Key to look up + 10.0 // Value to match + ); + + assertNotNull("Should find matching entry", result); + assertEquals("TEST", result[0]); + assertEquals("KEY", result[1]); + assertEquals("10.0", result[2]); + } + + @Test + public void testLookupWithinTolerance() throws Exception { + // Create a mock datasheet content + String datasheetContent = + "TEST,KEY,10.0\n" + + "TEST,KEY,20.0\n" + + "TEST,OTHER,15.0\n" + + "OTHER,KEY,10.0"; + + URL mockUrl = mock(URL.class); + when(mockUrl.openStream()).thenReturn( + new ByteArrayInputStream(datasheetContent.getBytes())); + when(mockClassLoader.getResource("DatasheetServiceTest.datasheet")) + .thenReturn(mockUrl); + + // Should match 10.0 with 5% tolerance when looking for 10.2 + String[] result = service.lookup( + DatasheetServiceTest.class, + 5.0, // 5% tolerance + "TEST|KEY", + 10.2 // Should match 10.0 within 5% tolerance + ); + + assertNotNull("Should find matching entry within tolerance", result); + assertEquals("10.0", result[2]); + } + + @Test + public void testLookupNoMatch() throws Exception { + // Create a mock datasheet content + String datasheetContent = + "TEST,KEY,10.0\n" + + "TEST,KEY,20.0\n" + + "TEST,OTHER,15.0\n" + + "OTHER,KEY,10.0"; + + URL mockUrl = mock(URL.class); + when(mockUrl.openStream()).thenReturn( + new ByteArrayInputStream(datasheetContent.getBytes())); + when(mockClassLoader.getResource("DatasheetServiceTest.datasheet")) + .thenReturn(mockUrl); + + // Should not match when value is outside tolerance + String[] result = service.lookup( + DatasheetServiceTest.class, + 1.0, // 1% tolerance + "TEST|KEY", + 15.0 // No value close enough to match + ); + + assertNull("Should not find matching entry", result); + } + + @Test + public void testLookupNonexistentKey() throws Exception { + // Create a mock datasheet content + String datasheetContent = + "TEST,KEY,10.0\n" + + "TEST,KEY,20.0\n" + + "TEST,OTHER,15.0\n" + + "OTHER,KEY,10.0"; + + URL mockUrl = mock(URL.class); + when(mockUrl.openStream()).thenReturn( + new ByteArrayInputStream(datasheetContent.getBytes())); + when(mockClassLoader.getResource("DatasheetServiceTest.datasheet")) + .thenReturn(mockUrl); + + String[] result = service.lookup( + DatasheetServiceTest.class, + 0.1, + "NONEXISTENT|KEY", // Key that doesn't exist in datasheet + 10.0 + ); + + assertNull("Should not find matching entry for nonexistent key", result); + } + + @Test + public void testLookupWithTolerance() throws Exception { + // Create a mock datasheet content with resistor values + String datasheetContent = + "0805,1%,100,0.125W\n" + + "0805,1%,220,0.125W\n" + + "0805,1%,470,0.125W"; + + URL mockUrl = mock(URL.class); + when(mockUrl.openStream()).thenReturn( + new ByteArrayInputStream(datasheetContent.getBytes())); + when(mockClassLoader.getResource("TestComponent.datasheet")) + .thenReturn(mockUrl); + + // Test lookup with 5% tolerance (should match 220 when looking for 225) + String[] result = service.lookup(TestComponent.class, 5.0, "0805|1%", 225.0); + + assertNotNull("Should find matching entry within tolerance", result); + assertEquals("0805", result[0]); + assertEquals("1%", result[1]); + assertEquals("220", result[2]); + } + + @Test + public void testLookupOutsideTolerance() throws Exception { + // Create a mock datasheet content + String datasheetContent = + "0805,1%,100,0.125W\n" + + "0805,1%,220,0.125W\n" + + "0805,1%,470,0.125W"; + + URL mockUrl = mock(URL.class); + when(mockUrl.openStream()).thenReturn( + new ByteArrayInputStream(datasheetContent.getBytes())); + when(mockClassLoader.getResource("TestComponent.datasheet")) + .thenReturn(mockUrl); + + // Test lookup with value outside tolerance + String[] result = service.lookup(TestComponent.class, 1.0, "0805|1%", 250.0); + + assertNull("Should not find matching entry outside tolerance", result); + } + + @Test + public void testLookupMultipleValues() throws Exception { + // Create a mock datasheet content with multiple value matches + String datasheetContent = + "TO220,NPN,50,5,0.7\n" + // Vce, Ic, Vbe + "TO220,NPN,60,8,0.7\n" + + "TO220,NPN,100,10,0.7"; + + URL mockUrl = mock(URL.class); + when(mockUrl.openStream()).thenReturn( + new ByteArrayInputStream(datasheetContent.getBytes())); + when(mockClassLoader.getResource("TestComponent.datasheet")) + .thenReturn(mockUrl); + + // Test lookup matching multiple values within tolerance + String[] result = service.lookup(TestComponent.class, 5.0, "TO220|NPN", + 98.0, 9.8, 0.71); + + assertNotNull("Should find matching entry for multiple values", result); + assertEquals("TO220", result[0]); + assertEquals("NPN", result[1]); + assertEquals("100", result[2]); + assertEquals("10", result[3]); + assertEquals("0.7", result[4]); + } + + // Test component class + private static class TestComponent { + // Empty class just for testing + } +} \ No newline at end of file diff --git a/diylc/diylc-core/src/test/resources/org/diylc/presenter/DatasheetServiceTest.datasheet b/diylc/diylc-core/src/test/resources/org/diylc/presenter/DatasheetServiceTest.datasheet new file mode 100644 index 00000000..5249c552 --- /dev/null +++ b/diylc/diylc-core/src/test/resources/org/diylc/presenter/DatasheetServiceTest.datasheet @@ -0,0 +1,4 @@ +TEST,KEY,10.0 mm +TEST,KEY,20.0 mm +TEST,OTHER,15.0 mm +OTHER,KEY,10.0 mm \ No newline at end of file diff --git a/diylc/diylc-library/src/main/java/org/diylc/components/misc/Image.java b/diylc/diylc-library/src/main/java/org/diylc/components/misc/Image.java index 94c9fdc4..1e67dd13 100644 --- a/diylc/diylc-library/src/main/java/org/diylc/components/misc/Image.java +++ b/diylc/diylc-library/src/main/java/org/diylc/components/misc/Image.java @@ -40,6 +40,7 @@ import org.diylc.awt.ImageUtils; import org.diylc.common.ObjectCache; import org.diylc.common.Orientation; import org.diylc.components.AbstractTransparentComponent; +import org.diylc.components.boards.AbstractBoard; import org.diylc.components.transform.ImageTransformer; import org.diylc.core.ComponentState; import org.diylc.core.IDIYComponent; @@ -94,6 +95,8 @@ public class Image extends AbstractTransparentComponent { private byte newScale = DEFAULT_SCALE; private ImageSizingMode sizingMode = ImageSizingMode.Scale; + private transient ImageIcon imageIcon; + public Image() { try { data = IOUtils.toByteArray(Image.class.getResourceAsStream("/image.png")); @@ -105,6 +108,11 @@ public class Image extends AbstractTransparentComponent { @Override public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project, IDrawingObserver drawingObserver) { + + if (!getImageRectangle().intersects(g2d.getClipBounds())) { + return; + } + double scaleX; double scaleY; ImageIcon imageIcon = getImage(); @@ -199,21 +207,24 @@ public class Image extends AbstractTransparentComponent { } public ImageIcon getImage() { - if (image != null) { - // when loading old files, convert the stored image to byte array and then then discard it, we won't be needing it anymore - BufferedImage bi = ImageUtils.ToBufferedImage(image.getImage()); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - ImageIO.write(bi, "png", baos); - // make it official - data = baos.toByteArray(); - } catch (IOException e) { - } - // don't save back to the file - image = null; - } + if (imageIcon == null) { + if (image != null) { + // when loading old files, convert the stored image to byte array and then then discard it, we won't be needing it anymore + BufferedImage bi = ImageUtils.ToBufferedImage(image.getImage()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + ImageIO.write(bi, "png", baos); + // make it official + data = baos.toByteArray(); + } catch (IOException e) { + } + // don't save back to the file + image = null; + } - return new ImageIcon(data); + imageIcon = new ImageIcon(data); + } + return imageIcon; } @ByteArrayProperty(binaryType = BinaryType.IMAGE) @@ -279,7 +290,28 @@ public class Image extends AbstractTransparentComponent { public void setOrientation(Orientation orientation) { this.orientation = orientation; } - + + protected Point2D getFinalSecondPoint() { + Point2D finalSecondPoint; + if (getSizingMode() == ImageSizingMode.TwoPoints) { + finalSecondPoint = secondPoint; + } else { + double scale = 1d * getScale() / DEFAULT_SCALE; + ImageIcon iconImage = getImage(); + finalSecondPoint = new Point2D.Double(point.getX() + iconImage.getIconWidth() * scale, + point.getY() + iconImage.getIconHeight() * scale); + } + return finalSecondPoint; + } + + protected Rectangle2D getImageRectangle() { + Point2D finalSecondPoint = getFinalSecondPoint(); + return new Rectangle2D.Double(Math.min(point.getX(), finalSecondPoint.getX()), + Math.min(point.getY(), finalSecondPoint.getY()), + Math.abs(point.getX() - finalSecondPoint.getX()), + Math.abs(point.getY() - finalSecondPoint.getY())); + } + public static enum ImageSizingMode { TwoPoints("Opposing Points"), Scale("Scale"); diff --git a/diylc/diylc-swing/dict.txt b/diylc/diylc-swing/dict.txt index 4c1b704b..920d8064 100644 --- a/diylc/diylc-swing/dict.txt +++ b/diylc/diylc-swing/dict.txt @@ -278,6 +278,7 @@ Heaters Height Help Hi-Quality Rendering +High DPI Rendering Highlight Connected Areas Hole Hole Size diff --git a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/canvas/CanvasPanel.java b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/canvas/CanvasPanel.java index 2c3914e4..011eaa9a 100644 --- a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/canvas/CanvasPanel.java +++ b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/canvas/CanvasPanel.java @@ -42,6 +42,7 @@ import javax.swing.AbstractAction; import javax.swing.JComponent; import javax.swing.KeyStroke; import org.apache.log4j.Logger; +import org.diylc.appframework.miscutils.IConfigListener; import org.diylc.appframework.miscutils.IConfigurationManager; import org.diylc.common.ComponentType; @@ -98,6 +99,15 @@ public class CanvasPanel extends JComponent implements Autoscroll { // GraphicsDevice[] devices = graphicsEnvironment.getScreenDevices(); // screenGraphicsConfiguration = devices[0].getDefaultConfiguration(); + configManager.addConfigListener(IPlugInPort.HIGH_DPI_RENDERING, + new IConfigListener() { + + @Override + public void valueChanged(String key, Object value) { + bufferImage = null; + } + }); + initializeActions(); } @@ -311,8 +321,12 @@ public class CanvasPanel extends JComponent implements Autoscroll { } final Graphics2D g2d = (Graphics2D)g; - double scaleFactor = Math.sqrt(g2d.getTransform().getDeterminant()); -// scaleFactor = 1d; + double scaleFactor; + if (configManager.readBoolean(IPlugInPort.HIGH_DPI_RENDERING, false)) { + scaleFactor = Math.sqrt(g2d.getTransform().getDeterminant()); + } else { + scaleFactor = 1d; + } Rectangle visibleRect = getVisibleRect(); @@ -324,28 +338,9 @@ public class CanvasPanel extends JComponent implements Autoscroll { double x = visibleRect.x;// / scaleFactor; double y = visibleRect.y;// / scaleFactor; bufferG2d.translate(-x, -y); + bufferG2d.setClip(visibleRect); - Set drawOptions = - EnumSet.of(DrawOption.SELECTION, DrawOption.ZOOM, DrawOption.CONTROL_POINTS); - - if (configManager.readBoolean(IPlugInPort.ANTI_ALIASING_KEY, true)) { - drawOptions.add(DrawOption.ANTIALIASING); - } - if (configManager.readBoolean(IPlugInPort.OUTLINE_KEY, false)) { - drawOptions.add(DrawOption.OUTLINE_MODE); - } - if (configManager.readBoolean(IPlugInPort.SHOW_GRID_KEY, true)) { - drawOptions.add(DrawOption.GRID); - } - if (configManager.readBoolean(IPlugInPort.EXTRA_SPACE_KEY, true)) { - drawOptions.add(DrawOption.EXTRA_SPACE); - } - if (configManager.readBoolean(IPlugInPort.CACHING_ENABLED_KEY, true)) { - drawOptions.add(DrawOption.ENABLE_CACHING); - } - if (configManager.readBoolean(IPlugInPort.LOCKED_ALPHA, true)) { - drawOptions.add(DrawOption.LOCKED_ALPHA); - } + Set drawOptions = getDrawOptions(); plugInPort.draw(bufferG2d, drawOptions, null, null, scaleFactor, visibleRect); @@ -375,6 +370,31 @@ public class CanvasPanel extends JComponent implements Autoscroll { bufferG2d.dispose(); } + private Set getDrawOptions() { + Set drawOptions = + EnumSet.of(DrawOption.SELECTION, DrawOption.ZOOM, DrawOption.CONTROL_POINTS); + + if (configManager.readBoolean(IPlugInPort.ANTI_ALIASING_KEY, true)) { + drawOptions.add(DrawOption.ANTIALIASING); + } + if (configManager.readBoolean(IPlugInPort.OUTLINE_KEY, false)) { + drawOptions.add(DrawOption.OUTLINE_MODE); + } + if (configManager.readBoolean(IPlugInPort.SHOW_GRID_KEY, true)) { + drawOptions.add(DrawOption.GRID); + } + if (configManager.readBoolean(IPlugInPort.EXTRA_SPACE_KEY, true)) { + drawOptions.add(DrawOption.EXTRA_SPACE); + } + if (configManager.readBoolean(IPlugInPort.CACHING_ENABLED_KEY, true)) { + drawOptions.add(DrawOption.ENABLE_CACHING); + } + if (configManager.readBoolean(IPlugInPort.LOCKED_ALPHA, true)) { + drawOptions.add(DrawOption.LOCKED_ALPHA); + } + return drawOptions; + } + @Override public void update(Graphics g) { paint(g); diff --git a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/config/ConfigPlugin.java b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/config/ConfigPlugin.java index 2d67a217..204af055 100644 --- a/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/config/ConfigPlugin.java +++ b/diylc/diylc-swing/src/main/java/org/diylc/swing/plugins/config/ConfigPlugin.java @@ -100,6 +100,9 @@ public class ConfigPlugin implements IPlugIn { // swingUI.injectMenuAction( // ActionFactory.getInstance().createConfigAction(plugInPort, "Hardware Acceleration", IPlugInPort.HARDWARE_ACCELERATION, false), // CONFIG_MENU); + swingUI.injectMenuAction( + ActionFactory.getInstance().createConfigAction(plugInPort, "High DPI Rendering", + IPlugInPort.HIGH_DPI_RENDERING, false), CONFIG_MENU); swingUI.injectMenuAction( ActionFactory.getInstance().createConfigAction(plugInPort, "Hi-Quality Rendering", IPlugInPort.HI_QUALITY_RENDER_KEY, false), CONFIG_MENU);