* 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>
11 KiB
Component Category Visibility
A Config submenu that lets the user hide whole component categories from the
component tree and the tabbed component bar, with the filtering applied in the
Presenter and announced to the views through a new event.
1. Goal
The library ships sixteen categories (Boards, Connectivity, Controllers,
Displays & Outputs, Electro-Mechanical, Guitar, Misc,
Modules & Breakouts, Passive, Robotics, SMD, Schematic Symbols,
Semiconductors, Sensors, Shapes, Tubes). A user who only ever draws
guitar wiring has to scroll past tabs and tree nodes they will never open. The
feature is a per-user toggle list: every category visible by default, any subset
hidden, the change visible immediately in whichever browser is active.
New in ConfigPlugin:
Config
...
Auto-Edit Mode
Component Categories >
Reset All
---------------
[x] Boards
[x] Connectivity
...
[x] Tubes
Continuous Creation
...
Disabling a category changes only what the two browsers list. Nothing is
hidden on the canvas, no existing project is altered, and no .diy file is
affected, so the serialized format and the regression references are untouched.
2. Four decisions taken up front
The filtered view is a second accessor, not a change to getComponentTypes().
That method has six callers and only two of them are browsers:
| Caller | Uses the map for | Needs every category |
|---|---|---|
CustomTreeModel |
tree nodes | no - filter here |
ComponentTabbedPane |
category tabs | no - filter here |
CanvasPanel |
type cache, keyboard-shortcut lookup | yes |
ExplorerPane |
list cell renderer | yes |
NetlistImportDialog |
netlist type mapping | yes |
ExportVariantsAction |
variant export tree | yes |
Had the existing method started filtering, a hidden category would silently
break the explorer's rendering of components already on the canvas and the
netlist importer's type lookup. So getComponentTypes() keeps its contract and
a new getVisibleComponentTypes() is added for the browsers.
Favorites, Recently Used and keyboard shortcuts stay reachable. Only the
per-category listings are filtered. A part the user explicitly pinned does not
vanish because its category was collapsed, and CustomTreeModel's favorites
branch - which dereferences typesByClass.get(fav.getName()) without a null
check - keeps working because that map is still built from the complete type
list.
One config key holds the disabled categories, as a List<String> written
through writeValue / read through readObject, the same shape as
FAVORITES_KEY and RECENT_COMPONENTS_KEY:
disabledComponentCategories = ["Robotics", "Tubes"]
Storing the disabled set rather than the enabled one means an empty value is
the default state, and a category introduced in a future release shows up
enabled for every existing user without a migration. "Reset All" is then simply
writing an empty list. The alternative - one boolean key per category - would
have let ConfigAction be reused verbatim, at the cost of config keys derived
from display names and one listener per category inside the Presenter.
The submenu sits directly under Config, a sibling of Snap To, Theme
and Toolbox, rather than nested inside Toolbox.
3. Core: the event
diylc-core/src/main/java/org/diylc/common/EventType.java gains a constant at
the end of the enum, documented like its neighbours:
/**
* Called when the set of enabled component categories changes. The first parameter is a
* <code>Set<String></code> of disabled category names. All categories not included are
* enabled.
*/
COMPONENT_CATEGORIES_CHANGED
The payload mirrors LAYER_VISIBILITY_CHANGED, which passes the set of hidden
layers. Views are free to ignore it and re-read getVisibleComponentTypes(),
which is what both of them will do.
4. Core: IPlugInPort
Next to FAVORITES_KEY:
public static final String DISABLED_CATEGORIES_KEY = "disabledComponentCategories";
and four methods, the accessor documented immediately after
getComponentTypes() so the difference between the two is impossible to miss:
/**
* Returns the {@link ComponentType}s that should be offered to the user, i.e. the result of
* {@link #getComponentTypes()} without the categories the user disabled. Intended for component
* browsers; anything that needs to resolve an arbitrary component class must use
* {@link #getComponentTypes()} instead.
*/
Map<String, List<ComponentType>> getVisibleComponentTypes();
boolean isCategoryEnabled(String category);
void setCategoryEnabled(String category, boolean enabled);
void resetCategoryVisibility();
Keeping the set arithmetic behind these three mutators means the Swing action never parses the stored list and never touches the config key, so the meaning of "disabled" lives in exactly one place.
5. Core: Presenter
The disabled set is read from configManager on demand rather than cached in a
field. None of these calls is on a hot path - they run when a browser rebuilds
or a menu item is toggled - and a stateless read cannot go stale:
@SuppressWarnings("unchecked")
private Set<String> getDisabledCategories() {
return new HashSet<String>(
(List<String>) configManager.readObject(DISABLED_CATEGORIES_KEY, new ArrayList<String>()));
}
getVisibleComponentTypes() copies the entries it keeps into a new map;
ComponentProcessor hands back its own cached instance, which must not be
mutated.
setCategoryEnabled adds or removes one name and writes a sorted
ArrayList<String> back, so the config file stays stable across sessions.
resetCategoryVisibility() writes an empty list.
The event is fired from a config listener, registered in the constructor next to
the existing SHOW_NODE_NAME_TOOLTIPS_KEY one, rather than from the setters:
configManager.addConfigListener(IPlugInPort.DISABLED_CATEGORIES_KEY, new IConfigListener() {
@Override
public void valueChanged(String key, Object value) {
messageDispatcher.dispatchMessage(EventType.COMPONENT_CATEGORIES_CHANGED,
getDisabledCategories());
}
});
Any writer then refreshes the views, not just the menu. The dispatcher is
constructed with new MessageDispatcher<EventType>(true) - synchronous - so the
views are rebuilt on the same EDT call that toggled the checkbox, and no
invokeLater is needed.
6. Swing: the menu
Two new action classes beside ConfigAction and ComponentBrowserAction in
org.diylc.swing.actions:
ComponentCategoryAction- one per category. Modelled onConfigAction:NAMEis the category,IView.CHECK_BOX_MENU_ITEMistrue,SELECTED_KEYis seeded fromplugInPort.isCategoryEnabled(category), and a listener onDISABLED_CATEGORIES_KEYre-reads that flag so "Reset All" re-checks every item in the submenu.actionPerformedcallssetCategoryEnabled.ResetCategoriesAction- a plain item callingresetCategoryVisibility().
ActionFactory gets createComponentCategoryAction(plugInPort, category) and
createResetCategoriesAction(plugInPort) next to
createComponentBrowserAction.
ConfigPlugin gains CATEGORIES_MENU = "Component Categories" and, placed
alphabetically between "Auto-Edit Mode" and "Continuous Creation" to match the
ordering of the rest of the 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));
The items are built from the complete keyset - a disabled category still
needs a checkbox to turn it back on. injectMenuAction(null, ...) is the
established way to add a separator, and MainFrame.injectMenuAction already
pushes action names through LangUtil.translate, so category names are
translated like every other menu label.
7. Swing: the component tree reacts
CustomTreeModel takes its categories from the filtered map while keeping the
class lookup complete:
this.componentTypes = plugInPort.getVisibleComponentTypes();
...
for (Map.Entry<String, List<ComponentType>> e : plugInPort.getComponentTypes().entrySet())
for (ComponentType c : e.getValue())
typesByClass.put(c.getInstanceClass().getCanonicalName(), c);
and gains a refresh() that re-reads the visible map, rebuilds categories
with (Favorites), (Recently Used) and (Building Blocks) back on front, and
calls the existing updateVisibleCategories(), which already clears the leaf
cache and fires treeStructureChanged. The constructor's category setup moves
into that method so there is one copy of it. TreePanel exposes it through its
existing getTreeModel().
ComponentTree currently returns null from getSubscribedEventTypes() with a
TODO and does nothing in processMessage. Both are filled in:
@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) {
getTreePanel().getTreeModel().refresh();
}
}
Search text survives the refresh because updateVisibleCategories() re-applies
it; tree expansion state does not, which is acceptable for an explicit menu
action.
8. Swing: the tabbed toolbar reacts
ComponentTabbedPane builds its category tabs in the constructor, after the
three fixed tabs. That loop moves into refreshCategoryTabs(), which removes
every tab from index 3 onwards and re-adds one per visible category. Each
category's panel is cached in a Map<String, Component>, because
createComponentPanel instantiates a ComponentButtonFactory button - with its
icon and variant popup - for every type in the category; toggling a category
must not pay that cost again for the fifteen categories that did not change.
The selected tab is remembered by title and restored if it is still present. A
refreshing guard keeps the existing ChangeListener from persisting
intermediate lastSelectedTab indices while tabs are being removed.
ToolBox fills in the same null/TODO pair as ComponentTree, delegating to
getComponentTabbedPane().refreshCategoryTabs().
9. What is deliberately not touched
CanvasPanel, ExplorerPane, NetlistImportDialog and ExportVariantsAction
keep calling getComponentTypes() and keep seeing every category, so keyboard
shortcuts, the project explorer's renderer, netlist import and variant export
behave identically whatever is hidden.
10. Verification
mvn -q -pl diylc-swing -am compile # core + library + swing
mvn -q test # reactor, since core changed
Then a manual pass in the app: toggle a category with the tree active and with the tabbed toolbar active, confirm both update without reopening the menu, confirm "Reset All" re-checks every item, confirm a favorited part from a hidden category is still clickable, and confirm the setting survives a restart.
The change touches neither rendering, serialization nor netlist output, so a regression-suite run is not called for.