Controllers (#1080)

* initial

* more components

* fixes

* Update TreePanel.java

* fix boost converter

* Improve wrapping of component names

* Update ULN2003Driver.java

* Update NRF24L01Transceiver.java

* imp

* updates

* improvements

* more components

* arduino

* uno

* icon

* cleanup

* mega

* mega

* improveent

* nano

* esp boards

* esp

* esp32

* esp32

* Update ESP8266NodeMCU.java

* d1

* Update WemosD1Mini.java

* pi

* consolidate esp32

* pi pico updates

* pi zero

* Update RaspberryPiZero.java

* pi pico

* cleanup

* pi improvements

* consolidate pins

* antenna consolidation

* improve solder pads

* teensy

* headers

* teensy fix, test cleanup

* s3

* Update ESP32DevKit.java

* usb standardization

* pico w

* button standardization

* arduino improvements

* Update AbstractMakerBoard.java

* Update RaspberryPiZero.java

* Update RaspberryPi.java

* pi 3

* pi 5 improvements

* Update RaspberryPiZero.java

* fix tests

* esp pinout fix

* Update ESP32DevKit.java

* pin name improvements

* Create tier-1-modern-boards.md

* new versions

* pinouts

* nano improvements

* sanitize node names

* label improvements

* minima fixes

* arduino uno labels

* nano labels

* Update ArduinoNano.java

* mega

* mega

* comment out future stuff

* release prep

* Update RaspberryPiPico.java

* Refactor abstract class

* labeling improvement

* label offset fixes

* Update Teensy.java

* consolidate tests

* micro variants

* Update RaspberryPi.java

* displays tweaks

* display improvements

* neopix

* displays

* improvements

* led matrix

* strip

* led bar graph

* jewel

* tft

* tft

* Update maker-peripherals.md

* nokia

* led bar

* labels

* tft

* cleanup

* oled

* cleanup

* display enhancement

* displays

* neopixel

* displays

* displays

* new tft

* displays

* icon

* variants

* 2-digit 0.56 inch 7 segment

* Added ability to hide component categories in the component tree and toolbar from the Config menu

* led bar

* Update variants.xml

* color selection for bar graph

* Added 1.8" sd

* tft fixes

* Update WS2812BRing.java

* neopixel improvements

* netlist and node name improvements

* oled tweak

* cleanup

* mockito fix

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Bane Stojkovic
2026-10-09 09:38:09 +02:00
committed by GitHub
parent f62b3ddf04
commit a743de4024
87 changed files with 11343 additions and 1933 deletions
@@ -30,12 +30,14 @@ import org.diylc.core.ExpansionMode;
import org.diylc.core.Theme;
import org.diylc.swing.actions.CheckProximityAction;
import org.diylc.swing.actions.ComponentBrowserAction;
import org.diylc.swing.actions.ComponentCategoryAction;
import org.diylc.swing.actions.ConfigAction;
import org.diylc.swing.actions.ExportGerberAction;
import org.diylc.swing.actions.ExportPNGAction;
import org.diylc.swing.actions.FindAction;
import org.diylc.swing.actions.FlexibleLeadsAction;
import org.diylc.swing.actions.RenumberAction;
import org.diylc.swing.actions.ResetCategoriesAction;
import org.diylc.swing.actions.ThemeAction;
import org.diylc.swing.actions.ToggleAction;
import org.diylc.swing.actions.edit.BringToFrontAction;
@@ -264,6 +266,15 @@ public class ActionFactory {
return new ComponentBrowserAction(browserType);
}
public ComponentCategoryAction createComponentCategoryAction(IPlugInPort plugInPort,
String category) {
return new ComponentCategoryAction(plugInPort, category);
}
public ResetCategoriesAction createResetCategoriesAction(IPlugInPort plugInPort) {
return new ResetCategoriesAction(plugInPort);
}
public RenumberAction createRenumberAction(IPlugInPort plugInPort, boolean xAxisFirst) {
return new RenumberAction(plugInPort, xAxisFirst);
}
@@ -0,0 +1,63 @@
/*
DIY Layout Creator (DIYLC).
Copyright (c) 2009-2025 held jointly by the individual authors.
This file is part of DIYLC.
DIYLC is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
DIYLC is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diylc.swing.actions;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import org.diylc.appframework.miscutils.ConfigurationManager;
import org.diylc.appframework.miscutils.IConfigListener;
import org.diylc.common.IPlugInPort;
import org.diylc.core.IView;
public class ComponentCategoryAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private final IPlugInPort plugInPort;
private final String category;
public ComponentCategoryAction(IPlugInPort plugInPort, String category) {
super();
this.plugInPort = plugInPort;
this.category = category;
putValue(AbstractAction.NAME, category);
putValue(IView.CHECK_BOX_MENU_ITEM, true);
putValue(AbstractAction.SELECTED_KEY, plugInPort.isCategoryEnabled(category));
ConfigurationManager.getInstance().addConfigListener(
IPlugInPort.DISABLED_CATEGORIES_KEY, new IConfigListener() {
@Override
public void valueChanged(String key, Object value) {
putValue(AbstractAction.SELECTED_KEY, plugInPort.isCategoryEnabled(category));
}
});
}
@Override
public void actionPerformed(ActionEvent e) {
boolean selected = Boolean.TRUE.equals(getValue(AbstractAction.SELECTED_KEY));
plugInPort.setCategoryEnabled(category, selected);
}
}
@@ -0,0 +1,45 @@
/*
DIY Layout Creator (DIYLC).
Copyright (c) 2009-2025 held jointly by the individual authors.
This file is part of DIYLC.
DIYLC is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
DIYLC is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diylc.swing.actions;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import org.diylc.common.IPlugInPort;
public class ResetCategoriesAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private final IPlugInPort plugInPort;
public ResetCategoriesAction(IPlugInPort plugInPort) {
super();
this.plugInPort = plugInPort;
putValue(AbstractAction.NAME, "Reset All");
}
@Override
public void actionPerformed(ActionEvent e) {
plugInPort.resetCategoryVisibility();
}
}
@@ -58,6 +58,7 @@ public class ConfigPlugin implements IPlugIn {
private static final String THEME_MENU = "Theme";
private static final String RULER_MENU = "Ruler Inch Subdivision...";
private static final String SNAP_MENU = "Snap To";
private static final String CATEGORIES_MENU = "Component Categories";
private static final String COMPONENT_BROWSER_MENU = "Toolbox";
public static final String COMPONENT_BROWSER = "componentBrowser";
public static final String PROJECT_EXPLORER = "projectExplorer";
@@ -89,7 +90,15 @@ public class ConfigPlugin implements IPlugIn {
IPlugInPort.AUTO_UNIT_CONVERSION_KEY, true), CONFIG_MENU);
swingUI.injectMenuAction(
ActionFactory.getInstance().createConfigAction(plugInPort, "Auto-Edit Mode", IPlugInPort.AUTO_EDIT_KEY, true),
CONFIG_MENU);
CONFIG_MENU);
swingUI.injectSubmenu(CATEGORIES_MENU, IconLoader.FolderPreferences.getIcon(), CONFIG_MENU);
swingUI.injectMenuAction(
ActionFactory.getInstance().createResetCategoriesAction(plugInPort), CATEGORIES_MENU);
swingUI.injectMenuAction(null, CATEGORIES_MENU);
plugInPort.getComponentTypes().keySet().stream().sorted()
.forEach(category -> swingUI.injectMenuAction(
ActionFactory.getInstance().createComponentCategoryAction(plugInPort, category),
CATEGORIES_MENU));
swingUI.injectMenuAction(
ActionFactory.getInstance().createConfigAction(plugInPort, "Continuous Creation",
IPlugInPort.CONTINUOUS_CREATION_KEY, false), CONFIG_MENU);
@@ -89,6 +89,9 @@ class ComponentTabbedPane extends JTabbedPane {
private List<String> pendingRecentComponents = null;
private List<String> blocks;
private final Map<String, Component> categoryPanels = new HashMap<String, Component>();
private boolean refreshing = false;
public ComponentTabbedPane(IPlugInPort plugInPort, ISwingUI swingUI) {
super();
this.plugInPort = plugInPort;
@@ -100,23 +103,22 @@ class ComponentTabbedPane extends JTabbedPane {
addTab(LangUtil.translate("(Favorites)"), createFavoritesPanel());
addTab(LangUtil.translate("(Recently Used)"), createRecentComponentsPanel());
addTab(LangUtil.translate("(Building Blocks)"), createBuildingBlocksPanel());
Map<String, List<ComponentType>> componentTypes = plugInPort.getComponentTypes();
List<String> categories = new ArrayList<String>(componentTypes.keySet());
Collections.sort(categories);
for (String category : categories) {
JPanel panel = createTab((componentTypes.get(category)));
addTab(category, panel);
}
refreshCategoryTabs();
// restore last selected tab
int lastSelectedTab = ConfigurationManager.getInstance().readInt(LAST_SELECTED_TAB, -1);
if (lastSelectedTab >= 0)
if (lastSelectedTab >= 0 && lastSelectedTab < getTabCount()) {
setSelectedIndex(lastSelectedTab);
}
addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
if (refreshing) {
return;
}
ConfigurationManager.getInstance().writeValue(LAST_SELECTED_TAB, getSelectedIndex());
ComponentTabbedPane.this.plugInPort.setNewComponentTypeSlot(null, null, null, false);
// Refresh recent components if needed
@@ -131,6 +133,50 @@ class ComponentTabbedPane extends JTabbedPane {
setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
}
public void refreshCategoryTabs() {
refreshing = true;
int restoredIndex = -1;
try {
int previousIndex = getSelectedIndex();
String selectedTitle =
previousIndex >= 0 && previousIndex < getTabCount() ? getTitleAt(previousIndex) : null;
while (getTabCount() > 3) {
removeTabAt(3);
}
Map<String, List<ComponentType>> visibleComponentTypes =
plugInPort.getVisibleComponentTypes();
List<String> categories = new ArrayList<String>(visibleComponentTypes.keySet());
Collections.sort(categories);
for (String category : categories) {
Component panel = categoryPanels.get(category);
if (panel == null) {
panel = createTab(visibleComponentTypes.get(category));
categoryPanels.put(category, panel);
}
addTab(category, panel);
}
if (selectedTitle != null) {
for (int i = 0; i < getTabCount(); i++) {
if (selectedTitle.equals(getTitleAt(i))) {
restoredIndex = i;
break;
}
}
}
} finally {
refreshing = false;
}
if (restoredIndex >= 0) {
setSelectedIndex(restoredIndex);
} else if (getTabCount() > 0) {
setSelectedIndex(0);
}
}
@SuppressWarnings("unchecked")
private void initialize() {
Map<String, List<ComponentType>> componentTypes = plugInPort.getComponentTypes();
@@ -79,13 +79,13 @@ public class ToolBox implements IPlugIn {
@Override
public EnumSet<EventType> getSubscribedEventTypes() {
// TODO Auto-generated method stub
return null;
return EnumSet.of(EventType.COMPONENT_CATEGORIES_CHANGED);
}
@Override
public void processMessage(EventType eventType, Object... params) {
// TODO Auto-generated method stub
if (eventType == EventType.COMPONENT_CATEGORIES_CHANGED) {
getComponentTabbedPane().refreshCategoryTabs();
}
}
}
@@ -103,13 +103,13 @@ public class ComponentTree implements IPlugIn {
@Override
public EnumSet<EventType> getSubscribedEventTypes() {
// TODO Auto-generated method stub
return null;
return EnumSet.of(EventType.COMPONENT_CATEGORIES_CHANGED);
}
@Override
public void processMessage(EventType eventType, Object... params) {
// TODO Auto-generated method stub
if (eventType == EventType.COMPONENT_CATEGORIES_CHANGED) {
getTreePanel().getTreeModel().refresh();
}
}
}
@@ -76,20 +76,26 @@ public class CustomTreeModel implements TreeModel {
public CustomTreeModel(IPlugInPort plugInPort) {
super();
this.plugInPort = plugInPort;
this.componentTypes = plugInPort.getComponentTypes();
this.visibleLeaves = new HashMap<String, List<TreeNode>>();
this.typesByClass = new HashMap<String, ComponentType>();
for (Map.Entry<String, List<ComponentType>> e : plugInPort.getComponentTypes().entrySet()) {
for (ComponentType c : e.getValue()) {
typesByClass.put(c.getInstanceClass().getCanonicalName(), c);
}
}
this.initialize();
refresh();
}
public void refresh() {
this.componentTypes = plugInPort.getVisibleComponentTypes();
this.categories = new ArrayList<String>(this.componentTypes.keySet());
Collections.sort(this.categories);
this.categories.add(0, BUILDING_BLOCKS);
this.categories.add(0, RECENTLY_USED);
this.categories.add(0, FAVORITES);
this.visibleLeaves = new HashMap<String, List<TreeNode>>();
this.typesByClass = new HashMap<String, ComponentType>();
for (Map.Entry<String, List<ComponentType>> e : componentTypes.entrySet())
for (ComponentType c : e.getValue())
typesByClass.put(c.getInstanceClass().getCanonicalName(), c);
this.initialize();
updateVisibleCategories();
}
@@ -89,7 +89,7 @@ public enum IconLoader {
"trace_mask.png"), TraceProximity("trace_proximity.png"), Undo("undo.png"), Ungroup(
"ungroup.png"), Unlock("lock_open.png"), Upload("upload.png"), User("user1.png"), Warning(
"warning.png"), Web("web.png"), WindowColors("window_colors.png"), WindowGear(
"window_gear.png"), Wrench("wrench.png"), ZoomSmall("zoom_small.png");
"window_gear.png"), Wrench("wrench.png"), ZoomSmall("zoom_small.png"), FolderPreferences("folder_preferences.png");
protected String name;
Binary file not shown.

After

Width:  |  Height:  |  Size: 863 B

@@ -2,12 +2,27 @@ package org.diylc;
import java.awt.Point;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.Test;
import junit.framework.Assert;
import org.junit.Test;
import org.diylc.appframework.miscutils.ConfigurationManager;
import org.diylc.appframework.miscutils.InMemoryConfigurationManager;
import org.diylc.common.ComponentType;
import org.diylc.common.EventType;
import org.diylc.common.IPlugIn;
import org.diylc.common.IPlugInPort;
import org.diylc.core.Template;
import org.diylc.presenter.Presenter;
import org.diylc.swing.ActionFactory;
import org.diylc.swing.actions.ComponentCategoryAction;
import org.diylc.swing.actions.ResetCategoriesAction;
import org.diylc.swing.plugins.tree.CustomTreeModel;
public class PresenterTests extends TestBase {
@@ -98,6 +113,114 @@ public class PresenterTests extends TestBase {
presenter.mouseMoved(new Point(1000, 1000), false, false, false);
Assert.assertNull(receivedTooltip[0]);
}
@Test
public void testCategoryVisibilityDefaults() {
presenter.resetCategoryVisibility();
Map<String, List<ComponentType>> all = presenter.getComponentTypes();
Map<String, List<ComponentType>> visible = presenter.getVisibleComponentTypes();
Assert.assertEquals(all.keySet(), visible.keySet());
for (String category : all.keySet()) {
Assert.assertTrue(presenter.isCategoryEnabled(category));
}
}
@Test
@SuppressWarnings("unchecked")
public void testDisableAndEnableCategory() {
presenter.resetCategoryVisibility();
final Set<String>[] lastEventDisabled = new Set[1];
IPlugIn testPlugin = new IPlugIn() {
@Override
public void connect(IPlugInPort plugInPort) {}
@Override
public EnumSet<EventType> getSubscribedEventTypes() {
return EnumSet.of(EventType.COMPONENT_CATEGORIES_CHANGED);
}
@Override
public void processMessage(EventType eventType, Object... params) {
if (eventType == EventType.COMPONENT_CATEGORIES_CHANGED && params.length > 0) {
lastEventDisabled[0] = (Set<String>) params[0];
}
}
};
presenter.installPlugin(() -> testPlugin);
// Disable "Tubes"
presenter.setCategoryEnabled("Tubes", false);
Assert.assertFalse(presenter.isCategoryEnabled("Tubes"));
Assert.assertFalse(presenter.getVisibleComponentTypes().containsKey("Tubes"));
Assert.assertTrue(presenter.getComponentTypes().containsKey("Tubes"));
Assert.assertNotNull(lastEventDisabled[0]);
Assert.assertTrue(lastEventDisabled[0].contains("Tubes"));
// Also disable "Robotics"
presenter.setCategoryEnabled("Robotics", false);
Assert.assertFalse(presenter.isCategoryEnabled("Robotics"));
Assert.assertFalse(presenter.getVisibleComponentTypes().containsKey("Robotics"));
Assert.assertTrue(lastEventDisabled[0].contains("Robotics"));
Assert.assertTrue(lastEventDisabled[0].contains("Tubes"));
// Check config stores sorted list
List<String> stored = (List<String>) InMemoryConfigurationManager.getInstance()
.readObject(IPlugInPort.DISABLED_CATEGORIES_KEY, new ArrayList<String>());
Assert.assertEquals(2, stored.size());
Assert.assertEquals("Robotics", stored.get(0));
Assert.assertEquals("Tubes", stored.get(1));
// Re-enable "Tubes"
presenter.setCategoryEnabled("Tubes", true);
Assert.assertTrue(presenter.isCategoryEnabled("Tubes"));
Assert.assertTrue(presenter.getVisibleComponentTypes().containsKey("Tubes"));
Assert.assertFalse(lastEventDisabled[0].contains("Tubes"));
Assert.assertTrue(lastEventDisabled[0].contains("Robotics"));
// Reset all
presenter.resetCategoryVisibility();
Assert.assertTrue(presenter.isCategoryEnabled("Robotics"));
Assert.assertTrue(presenter.isCategoryEnabled("Tubes"));
Assert.assertTrue(lastEventDisabled[0].isEmpty());
}
@Test
public void testComponentCategoryActions() {
Presenter prodPresenter = new Presenter(view, ConfigurationManager.getInstance());
prodPresenter.resetCategoryVisibility();
ComponentCategoryAction catAction =
ActionFactory.getInstance().createComponentCategoryAction(prodPresenter, "Tubes");
Assert.assertEquals(true, catAction.getValue(javax.swing.AbstractAction.SELECTED_KEY));
// Toggle off
catAction.putValue(javax.swing.AbstractAction.SELECTED_KEY, false);
catAction.actionPerformed(null);
Assert.assertFalse(prodPresenter.isCategoryEnabled("Tubes"));
// Reset via ResetCategoriesAction
ResetCategoriesAction resetAction =
ActionFactory.getInstance().createResetCategoriesAction(prodPresenter);
resetAction.actionPerformed(null);
Assert.assertTrue(prodPresenter.isCategoryEnabled("Tubes"));
Assert.assertEquals(true, catAction.getValue(javax.swing.AbstractAction.SELECTED_KEY));
}
@Test
public void testCustomTreeModelRefresh() {
presenter.resetCategoryVisibility();
CustomTreeModel treeModel = new CustomTreeModel(presenter);
Assert.assertTrue(presenter.isCategoryEnabled("Tubes"));
presenter.setCategoryEnabled("Tubes", false);
treeModel.refresh();
Assert.assertFalse(presenter.getVisibleComponentTypes().containsKey("Tubes"));
presenter.resetCategoryVisibility();
treeModel.refresh();
Assert.assertTrue(presenter.getVisibleComponentTypes().containsKey("Tubes"));
}
// @Test
// public void testLoadVariants() {