mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-07 12:24:58 +10:00
added ability to save default model
This commit is contained in:
@@ -43,6 +43,10 @@ public interface IVariantProcessor {
|
||||
void deleteVariant(ComponentType type, String variantName);
|
||||
|
||||
void setDefaultVariant(ComponentType type, String variantName);
|
||||
|
||||
void setDefaultModel(ComponentType type, String[] model);
|
||||
|
||||
boolean isDefaultModel(ComponentType type, String[] model);
|
||||
|
||||
int importVariants(String fileName) throws IOException;
|
||||
|
||||
|
||||
@@ -2868,17 +2868,21 @@ public class Presenter implements IPlugInPort {
|
||||
controlPointMap.clear();
|
||||
}
|
||||
|
||||
// try to find a default template if none is provided
|
||||
if (componentType != null && template == null) {
|
||||
String defaultTemplate = getDefaultVariant(componentType);
|
||||
List<Template> templates = getVariantsFor(componentType);
|
||||
if (templates != null && defaultTemplate != null)
|
||||
for (Template t : templates) {
|
||||
if (t.getName().equals(defaultTemplate)) {
|
||||
template = t;
|
||||
break;
|
||||
// try to find a default model or template if none is provided
|
||||
if (componentType != null && template == null && model == null) {
|
||||
model = variantManager.getDefaultModel(componentType);
|
||||
if (model == null) {
|
||||
String defaultTemplate = getDefaultVariant(componentType);
|
||||
List<Template> templates = getVariantsFor(componentType);
|
||||
if (templates != null && defaultTemplate != null) {
|
||||
for (Template t : templates) {
|
||||
if (t.getName().equals(defaultTemplate)) {
|
||||
template = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -3064,28 +3068,19 @@ public class Presenter implements IPlugInPort {
|
||||
variantManager.deleteVariant(type, templateName);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void setDefaultVariant(ComponentType type, String templateName) {
|
||||
LOG.debug(String.format("setTemplateDefault(%s, %s)", type, templateName));
|
||||
Map<String, String> defaultTemplateMap =
|
||||
(Map<String, String>) configManager.readObject(DEFAULT_TEMPLATES_KEY, null);
|
||||
if (defaultTemplateMap == null)
|
||||
defaultTemplateMap = new HashMap<String, String>();
|
||||
variantManager.setDefaultVariant(type, templateName);
|
||||
}
|
||||
|
||||
// try by class name and then by old category.type format
|
||||
String key1 = type.getInstanceClass().getCanonicalName();
|
||||
String key2 = type.getCategory() + "." + type.getName();
|
||||
@Override
|
||||
public void setDefaultModel(ComponentType type, String[] model) {
|
||||
variantManager.setDefaultModel(type, model);
|
||||
}
|
||||
|
||||
if (templateName.equals(defaultTemplateMap.get(key1)) || templateName.equals(defaultTemplateMap.get(key2))) {
|
||||
defaultTemplateMap.remove(key1);
|
||||
defaultTemplateMap.remove(key2);
|
||||
} else {
|
||||
// get rid of legacy key
|
||||
defaultTemplateMap.remove(key2);
|
||||
defaultTemplateMap.put(key1, templateName);
|
||||
}
|
||||
configManager.writeValue(DEFAULT_TEMPLATES_KEY, defaultTemplateMap);
|
||||
@Override
|
||||
public boolean isDefaultModel(ComponentType type, String[] model) {
|
||||
return variantManager.isDefaultModel(type, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
@@ -46,6 +47,9 @@ import org.diylc.serialization.ProjectFileManager;
|
||||
public class VariantManager {
|
||||
|
||||
private static final String VARIANTS_FILE_NAME = "import-defaults/variants.xml";
|
||||
// marks a default that names a datasheet model rather than a variant; older versions find no
|
||||
// variant by that name and simply place the component without a default
|
||||
private static final String DATASHEET_MODEL_PREFIX = "datasheet:";
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(VariantManager.class);
|
||||
|
||||
@@ -261,6 +265,67 @@ public class VariantManager {
|
||||
return defaultTemplateMap.get(key2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the variant the default of its type, replacing whatever variant or model was the
|
||||
* default before, or clears the default if the variant already is it.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setDefaultVariant(ComponentType type, String variantName) {
|
||||
Map<String, String> defaultTemplateMap =
|
||||
(Map<String, String>) configManager.readObject(IPlugInPort.DEFAULT_TEMPLATES_KEY, null);
|
||||
if (defaultTemplateMap == null) {
|
||||
defaultTemplateMap = new HashMap<String, String>();
|
||||
}
|
||||
|
||||
// try by class name and then by old category.type format
|
||||
String key1 = type.getInstanceClass().getCanonicalName();
|
||||
String key2 = type.getCategory() + "." + type.getName();
|
||||
|
||||
if (variantName.equals(defaultTemplateMap.get(key1))
|
||||
|| variantName.equals(defaultTemplateMap.get(key2))) {
|
||||
defaultTemplateMap.remove(key1);
|
||||
defaultTemplateMap.remove(key2);
|
||||
LOG.info(String.format("Cleared default of %s, was %s", type.getName(), variantName));
|
||||
} else {
|
||||
// get rid of legacy key
|
||||
defaultTemplateMap.remove(key2);
|
||||
defaultTemplateMap.put(key1, variantName);
|
||||
LOG.info(String.format("Set default of %s to %s", type.getName(), variantName));
|
||||
}
|
||||
configManager.writeValue(IPlugInPort.DEFAULT_TEMPLATES_KEY, defaultTemplateMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #setDefaultVariant(ComponentType, String)} for a datasheet model, sharing the
|
||||
* single default slot of the type.
|
||||
*/
|
||||
public void setDefaultModel(ComponentType type, String[] model) {
|
||||
setDefaultVariant(type, encodeModel(type, model));
|
||||
}
|
||||
|
||||
public boolean isDefaultModel(ComponentType type, String[] model) {
|
||||
return encodeModel(type, model).equals(getDefaultVariant(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the datasheet row pinned as the default of the type, or null when the default is a
|
||||
* variant, nothing is pinned, or the pinned model is no longer in the datasheet.
|
||||
*/
|
||||
public String[] getDefaultModel(ComponentType type) {
|
||||
String defaultVariant = getDefaultVariant(type);
|
||||
List<String[]> datasheet = type.getDatasheet();
|
||||
if (defaultVariant == null || !defaultVariant.startsWith(DATASHEET_MODEL_PREFIX)
|
||||
|| datasheet == null) {
|
||||
return null;
|
||||
}
|
||||
for (String[] row : datasheet) {
|
||||
if (defaultVariant.equals(encodeModel(type, row))) {
|
||||
return row;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void deleteVariant(ComponentType type, String templateName) {
|
||||
LOG.debug(String.format("deleteTemplate(%s, %s)", type, templateName));
|
||||
@@ -294,4 +359,11 @@ public class VariantManager {
|
||||
}
|
||||
configManager.writeValue(IPlugInPort.TEMPLATES_KEY, templateMap);
|
||||
}
|
||||
|
||||
// a model is identified by its path through the datasheet submenus, the same columns the
|
||||
// toolbox matches when a model is clicked
|
||||
private String encodeModel(ComponentType type, String[] model) {
|
||||
int depth = Math.min(type.getDatasheetCreationStepCount(), model.length);
|
||||
return DATASHEET_MODEL_PREFIX + String.join("|", Arrays.copyOf(model, depth));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,6 +549,10 @@
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Added a datasheet to 'Enclosure' with Hammond 1590, 1550, 1441 and 1444 enclosures and the 125B size - more than 130 variations</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>NEW_FEATURE</changeType>
|
||||
<description>Datasheet models in a component's drop-down menu now have the same pin as saved variants, so a model such as a 2N3904 can be made the default that is placed when the component is clicked; a component has one default, so pinning a model replaces a pinned variant and the other way round</description>
|
||||
</org.diylc.appframework.update.Change>
|
||||
<org.diylc.appframework.update.Change>
|
||||
<changeType>IMPROVEMENT</changeType>
|
||||
<description>Re-modeled factory variants of Resistor component type as datasheet in order to make them available to the AI; this will produce more accurate AI-driven edits</description>
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
|
||||
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.presenter;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.diylc.appframework.miscutils.IConfigListener;
|
||||
import org.diylc.appframework.miscutils.IConfigurationManager;
|
||||
import org.diylc.common.ComponentType;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.core.CreationMethod;
|
||||
import org.diylc.core.annotations.BomPolicy;
|
||||
import org.diylc.core.annotations.KeywordPolicy;
|
||||
import org.diylc.testcomponents.TwoPointTestComponent;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Covers the single default slot that variants and datasheet models share, see
|
||||
* {@code docs/plans/default-datasheet-model.md}.
|
||||
*/
|
||||
public class VariantManagerTest {
|
||||
|
||||
private static final String[] NPN = new String[] {"BJT (NPN)", "2N3904", "BJT_EBC"};
|
||||
private static final String[] PNP = new String[] {"BJT (PNP)", "2N3906", "BJT_EBC"};
|
||||
|
||||
private MapConfigurationManager configManager;
|
||||
private ComponentType type;
|
||||
private VariantManager manager;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
configManager = new MapConfigurationManager();
|
||||
type = createType(new ArrayList<String[]>(Arrays.asList(NPN, PNP)));
|
||||
manager = new VariantManager(configManager, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pinningAModelMakesItTheDefault() {
|
||||
manager.setDefaultModel(type, NPN);
|
||||
|
||||
assertTrue(manager.isDefaultModel(type, NPN));
|
||||
assertFalse(manager.isDefaultModel(type, PNP));
|
||||
assertArrayEquals(NPN, manager.getDefaultModel(type));
|
||||
assertEquals("datasheet:BJT (NPN)|2N3904", manager.getDefaultVariant(type));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pinningTheDefaultModelAgainClearsIt() {
|
||||
manager.setDefaultModel(type, NPN);
|
||||
manager.setDefaultModel(type, NPN);
|
||||
|
||||
assertFalse(manager.isDefaultModel(type, NPN));
|
||||
assertNull(manager.getDefaultModel(type));
|
||||
assertNull(manager.getDefaultVariant(type));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pinningAnotherModelReplacesTheDefault() {
|
||||
manager.setDefaultModel(type, NPN);
|
||||
manager.setDefaultModel(type, PNP);
|
||||
|
||||
assertFalse(manager.isDefaultModel(type, NPN));
|
||||
assertArrayEquals(PNP, manager.getDefaultModel(type));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modelAndVariantReplaceEachOther() {
|
||||
manager.setDefaultVariant(type, "My variant");
|
||||
manager.setDefaultModel(type, NPN);
|
||||
|
||||
assertArrayEquals(NPN, manager.getDefaultModel(type));
|
||||
|
||||
manager.setDefaultVariant(type, "My variant");
|
||||
|
||||
assertEquals("My variant", manager.getDefaultVariant(type));
|
||||
assertNull(manager.getDefaultModel(type));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modelMissingFromTheDatasheetIsNoDefault() {
|
||||
manager.setDefaultModel(type, NPN);
|
||||
ComponentType reduced = createType(new ArrayList<String[]>(Arrays.<String[]>asList(PNP)));
|
||||
|
||||
assertNull(manager.getDefaultModel(reduced));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void legacyKeyIsReadAndReplacedOnWrite() {
|
||||
String legacyKey = type.getCategory() + "." + type.getName();
|
||||
Map<String, String> defaults = new HashMap<String, String>();
|
||||
defaults.put(legacyKey, "Old variant");
|
||||
configManager.writeValue(IPlugInPort.DEFAULT_TEMPLATES_KEY, defaults);
|
||||
|
||||
assertEquals("Old variant", manager.getDefaultVariant(type));
|
||||
|
||||
manager.setDefaultModel(type, PNP);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> written = (Map<String, String>) configManager
|
||||
.readObject(IPlugInPort.DEFAULT_TEMPLATES_KEY, null);
|
||||
assertFalse(written.containsKey(legacyKey));
|
||||
assertArrayEquals(PNP, manager.getDefaultModel(type));
|
||||
}
|
||||
|
||||
private static ComponentType createType(List<String[]> datasheet) {
|
||||
return new ComponentType("Test Transistor", "", CreationMethod.SINGLE_CLICK, "Test", "Q", "",
|
||||
null, TwoPointTestComponent.class, 0, false, BomPolicy.SHOW_ALL_NAMES, false, null,
|
||||
KeywordPolicy.NEVER_SHOW, null, false, datasheet, 2);
|
||||
}
|
||||
|
||||
private static class MapConfigurationManager implements IConfigurationManager<Object> {
|
||||
|
||||
private final Map<String, Object> values = new HashMap<String, Object>();
|
||||
|
||||
@Override
|
||||
public void addConfigListener(String key, IConfigListener listener) {}
|
||||
|
||||
@Override
|
||||
public boolean readBoolean(String key, boolean defaultValue) {
|
||||
return (Boolean) readObject(key, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readString(String key, String defaultValue) {
|
||||
return (String) readObject(key, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInt(String key, int defaultValue) {
|
||||
return (Integer) readObject(key, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float readFloat(String key, float defaultValue) {
|
||||
return (Float) readObject(key, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double readDouble(String key, double defaultValue) {
|
||||
return (Double) readObject(key, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object readObject(String key, Object defaultValue) {
|
||||
return values.containsKey(key) ? values.get(key) : defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeValue(String key, Object value) {
|
||||
values.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSerializer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(String appName) {}
|
||||
}
|
||||
}
|
||||
@@ -351,7 +351,7 @@ public class ComponentPopupMenu extends TranslatedPopupMenu implements Clipboard
|
||||
}
|
||||
|
||||
applyMenu.setEnabled(true);
|
||||
List<Component> datasheetItems = ComponentButtonFactory.createDatasheetItems(plugInPort, componentType, new ArrayList<String>(), model -> {
|
||||
List<Component> datasheetItems = ComponentButtonFactory.createDatasheetItems(plugInPort, componentType, new ArrayList<String>(), false, model -> {
|
||||
LOG.info("Applying datasheet model " + String.join(", ", model));
|
||||
plugInPort.applyModelToSelection(model);
|
||||
});
|
||||
|
||||
+67
-38
@@ -42,9 +42,9 @@ import javax.swing.JMenu;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.MenuSelectionManager;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.event.MenuEvent;
|
||||
import javax.swing.event.MenuListener;
|
||||
@@ -59,6 +59,7 @@ import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.common.IBlockProcessor.InvalidBlockException;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.Template;
|
||||
import org.diylc.lang.LangUtil;
|
||||
import org.diylc.utils.IconLoader;
|
||||
|
||||
/**
|
||||
@@ -170,34 +171,14 @@ public class ComponentButtonFactory {
|
||||
}
|
||||
});
|
||||
|
||||
String defaultVariant = plugInPort.getDefaultVariant(componentType);
|
||||
|
||||
JLabel label =
|
||||
new JLabel(variant.getName().equals(defaultVariant) ? IconLoader.PinGreen.getIcon()
|
||||
: IconLoader.PinGrey.getIcon());
|
||||
label.setToolTipText(variant.getName().equals(defaultVariant) ? "Remove default variant"
|
||||
: "Set default variant");
|
||||
label.addMouseListener(new MouseAdapter() {
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
// Hide the menu
|
||||
Container c = item.getParent();
|
||||
if (c != null && c instanceof JPopupMenu) {
|
||||
JPopupMenu m = (JPopupMenu) c;
|
||||
m.setVisible(false);
|
||||
}
|
||||
plugInPort.setDefaultVariant(componentType, variant.getName());
|
||||
e.consume();
|
||||
}
|
||||
});
|
||||
Border margin = new EmptyBorder(4, 0, 0, 0);
|
||||
label.setBorder(margin);
|
||||
boolean isDefault = variant.getName().equals(plugInPort.getDefaultVariant(componentType));
|
||||
item.setLayout(new FlowLayout(FlowLayout.RIGHT, 0, 0));
|
||||
item.add(label);
|
||||
item.add(createDefaultPin(isDefault,
|
||||
isDefault ? "Remove default variant" : "Set default variant",
|
||||
() -> plugInPort.setDefaultVariant(componentType, variant.getName())));
|
||||
|
||||
label = new JLabel(IconLoader.Garbage.getIcon());
|
||||
label.setToolTipText("Delete variant");
|
||||
JLabel label = new JLabel(IconLoader.Garbage.getIcon());
|
||||
label.setToolTipText(LangUtil.translate("Delete variant"));
|
||||
label.addMouseListener(new MouseAdapter() {
|
||||
|
||||
@Override
|
||||
@@ -209,8 +190,10 @@ public class ComponentButtonFactory {
|
||||
m.setVisible(false);
|
||||
}
|
||||
int result = JOptionPane.showConfirmDialog(SwingUtilities.getRoot(item),
|
||||
"Are you sure you want to delete variant \"" + variant.getName() + "\"", "Delete",
|
||||
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
|
||||
String.format(LangUtil.translate("Are you sure you want to delete variant \"%s\"?"),
|
||||
variant.getName()),
|
||||
LangUtil.translate("Delete"), JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.WARNING_MESSAGE);
|
||||
if (result != JOptionPane.YES_OPTION) {
|
||||
return;
|
||||
}
|
||||
@@ -218,15 +201,18 @@ public class ComponentButtonFactory {
|
||||
e.consume();
|
||||
}
|
||||
});
|
||||
margin = new EmptyBorder(4, 2, 0, 0);
|
||||
label.setBorder(margin);
|
||||
item.setLayout(new FlowLayout(FlowLayout.RIGHT, 0, 0));
|
||||
label.setBorder(new EmptyBorder(4, 2, 0, 0));
|
||||
item.add(label);
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param showDefaultPin whether model items get the pin that makes a model the default of the
|
||||
* type; only meaningful where the items place a new component
|
||||
*/
|
||||
public static List<Component> createDatasheetItems(final IPlugInPort plugInPort,
|
||||
final ComponentType componentType, List<String> path, Consumer<String[]> modelConsumer) {
|
||||
final ComponentType componentType, List<String> path, boolean showDefaultPin,
|
||||
Consumer<String[]> modelConsumer) {
|
||||
int depth = path.size();
|
||||
List<String[]> datasheet = componentType.getDatasheet();
|
||||
if (datasheet == null) {
|
||||
@@ -253,7 +239,8 @@ public class ComponentButtonFactory {
|
||||
submenu.removeAll();
|
||||
List<String> newPath = new ArrayList<String>(path);
|
||||
newPath.add(value);
|
||||
List<Component> childItems = createDatasheetItems(plugInPort, componentType, newPath, modelConsumer);
|
||||
List<Component> childItems = createDatasheetItems(plugInPort, componentType, newPath,
|
||||
showDefaultPin, modelConsumer);
|
||||
if (childItems.size() > MAX_MENU_ITEMS) {
|
||||
List<List<Component>> partitions = Lists.partition(childItems, MAX_MENU_ITEMS);
|
||||
for (List<Component> partition : partitions) {
|
||||
@@ -293,15 +280,24 @@ public class ComponentButtonFactory {
|
||||
return (Component) submenu;
|
||||
}
|
||||
|
||||
JMenuItem item = new JMenuItem(value);
|
||||
Optional<String[]> findFirst =
|
||||
filteredDatasheet.stream().filter(line -> line[depth].equals(value)).findFirst();
|
||||
|
||||
final JMenuItem item = new JMenuItem(value) {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// Customize item size to fit the pin
|
||||
public java.awt.Dimension getPreferredSize() {
|
||||
Dimension d = super.getPreferredSize();
|
||||
return new Dimension(d.width + (showDefaultPin ? 16 : 0), d.height);
|
||||
}
|
||||
};
|
||||
|
||||
item.addActionListener(new ActionListener() {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Optional<String[]> findFirst =
|
||||
filteredDatasheet.stream().filter(line -> line[depth].equals(value)).findFirst();
|
||||
|
||||
if (findFirst.isPresent()) {
|
||||
modelConsumer.accept(findFirst.get());
|
||||
} else {
|
||||
@@ -310,6 +306,15 @@ public class ComponentButtonFactory {
|
||||
}
|
||||
});
|
||||
|
||||
if (showDefaultPin && findFirst.isPresent()) {
|
||||
String[] model = findFirst.get();
|
||||
boolean isDefault = plugInPort.isDefaultModel(componentType, model);
|
||||
item.setLayout(new FlowLayout(FlowLayout.RIGHT, 0, 0));
|
||||
item.add(createDefaultPin(isDefault,
|
||||
isDefault ? "Remove default model" : "Set default model",
|
||||
() -> plugInPort.setDefaultModel(componentType, model)));
|
||||
}
|
||||
|
||||
return (Component) item;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
@@ -347,4 +352,28 @@ public class ComponentButtonFactory {
|
||||
});
|
||||
return button;
|
||||
}
|
||||
|
||||
private static JLabel createDefaultPin(boolean isDefault, String tooltip,
|
||||
final Runnable toggle) {
|
||||
JLabel label = new JLabel(
|
||||
isDefault ? IconLoader.PinGreen.getIcon() : IconLoader.PinGrey.getIcon());
|
||||
label.setToolTipText(LangUtil.translate(tooltip));
|
||||
label.addMouseListener(new MouseAdapter() {
|
||||
|
||||
// a click is not reported when the pointer moves between press and release, which happens
|
||||
// easily on a 16px icon, so react to the release instead, as menu items themselves do
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (!label.contains(e.getPoint())) {
|
||||
return;
|
||||
}
|
||||
// closes the whole cascade, not just the submenu that holds a datasheet model
|
||||
MenuSelectionManager.defaultManager().clearSelectedPath();
|
||||
toggle.run();
|
||||
e.consume();
|
||||
}
|
||||
});
|
||||
label.setBorder(new EmptyBorder(4, 0, 0, 0));
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -508,7 +508,7 @@ class ComponentTabbedPane extends JTabbedPane {
|
||||
variantPopup.add(new JSeparator());
|
||||
|
||||
if (componentType.getDatasheet() != null && !componentType.getDatasheet().isEmpty()) {
|
||||
List<Component> items = ComponentButtonFactory.createDatasheetItems(plugInPort, componentType, new ArrayList<String>(), model -> {
|
||||
List<Component> items = ComponentButtonFactory.createDatasheetItems(plugInPort, componentType, new ArrayList<String>(), true, model -> {
|
||||
LOG.info("Creating datasheet model " + String.join(", ", model));
|
||||
plugInPort.setNewComponentTypeSlot(componentType, null, model, false);
|
||||
});
|
||||
|
||||
@@ -610,7 +610,7 @@ public class TreePanel extends JPanel {
|
||||
boolean showEmpty) {
|
||||
if (componentType.getDatasheet() != null) {
|
||||
List<Component> items = ComponentButtonFactory.createDatasheetItems(plugInPort,
|
||||
componentType, new ArrayList<String>(), model -> {
|
||||
componentType, new ArrayList<String>(), true, model -> {
|
||||
LOG.info("Creating datasheet model " + String.join(", ", model));
|
||||
plugInPort.setNewComponentTypeSlot(componentType, null, model, false);
|
||||
});
|
||||
|
||||
@@ -722,3 +722,10 @@ All|Alle
|
||||
Front and Back|Vorne und hinten
|
||||
Click to choose from %s variants|Klicken, um aus %s Varianten zu wählen
|
||||
Flange|Flansch
|
||||
Set default variant|Als Standardvariante festlegen
|
||||
Remove default variant|Standardvariante entfernen
|
||||
Delete variant|Variante löschen
|
||||
Are you sure you want to delete variant "%s"?|Möchten Sie die Variante "%s" wirklich löschen?
|
||||
Delete|Löschen
|
||||
Set default model|Als Standardmodell festlegen
|
||||
Remove default model|Standardmodell entfernen
|
||||
|
||||
@@ -715,3 +715,10 @@ All
|
||||
Front and Back
|
||||
Click to choose from %s variants
|
||||
Flange
|
||||
Set default variant
|
||||
Remove default variant
|
||||
Delete variant
|
||||
Are you sure you want to delete variant "%s"?
|
||||
Delete
|
||||
Set default model
|
||||
Remove default model
|
||||
|
||||
@@ -718,3 +718,10 @@ All|Todos
|
||||
Front and Back|Frente y parte trasera
|
||||
Click to choose from %s variants|Haga clic para elegir entre %s variantes
|
||||
Flange|Brida
|
||||
Set default variant|Establecer como variante predeterminada
|
||||
Remove default variant|Quitar variante predeterminada
|
||||
Delete variant|Eliminar variante
|
||||
Are you sure you want to delete variant "%s"?|¿Seguro que desea eliminar la variante "%s"?
|
||||
Delete|Eliminar
|
||||
Set default model|Establecer como modelo predeterminado
|
||||
Remove default model|Quitar modelo predeterminado
|
||||
|
||||
@@ -717,3 +717,10 @@ All|Tous
|
||||
Front and Back|Avant et arrière
|
||||
Click to choose from %s variants|Cliquez pour choisir parmi %s variantes
|
||||
Flange|Collerette
|
||||
Set default variant|Définir comme variante par défaut
|
||||
Remove default variant|Retirer la variante par défaut
|
||||
Delete variant|Supprimer la variante
|
||||
Are you sure you want to delete variant "%s"?|Voulez-vous vraiment supprimer la variante "%s" ?
|
||||
Delete|Supprimer
|
||||
Set default model|Définir comme modèle par défaut
|
||||
Remove default model|Retirer le modèle par défaut
|
||||
|
||||
@@ -719,3 +719,10 @@ All|Tutti
|
||||
Front and Back|Fronte e retro
|
||||
Click to choose from %s variants|Fai clic per scegliere tra %s varianti
|
||||
Flange|Flangia
|
||||
Set default variant|Imposta come variante predefinita
|
||||
Remove default variant|Rimuovi variante predefinita
|
||||
Delete variant|Elimina variante
|
||||
Are you sure you want to delete variant "%s"?|Eliminare davvero la variante "%s"?
|
||||
Delete|Elimina
|
||||
Set default model|Imposta come modello predefinito
|
||||
Remove default model|Rimuovi modello predefinito
|
||||
|
||||
@@ -723,3 +723,10 @@ All|Alle
|
||||
Front and Back|Voor- en achterkant
|
||||
Click to choose from %s variants|Klik om te kiezen uit %s varianten
|
||||
Flange|Flens
|
||||
Set default variant|Instellen als standaardvariant
|
||||
Remove default variant|Standaardvariant verwijderen
|
||||
Delete variant|Variant verwijderen
|
||||
Are you sure you want to delete variant "%s"?|Weet u zeker dat u variant "%s" wilt verwijderen?
|
||||
Delete|Verwijderen
|
||||
Set default model|Instellen als standaardmodel
|
||||
Remove default model|Standaardmodel verwijderen
|
||||
|
||||
@@ -718,3 +718,10 @@ All|Todos
|
||||
Front and Back|Frente e traseira
|
||||
Click to choose from %s variants|Clique para escolher entre %s variantes
|
||||
Flange|Flange
|
||||
Set default variant|Definir como variante padrão
|
||||
Remove default variant|Remover variante padrão
|
||||
Delete variant|Excluir variante
|
||||
Are you sure you want to delete variant "%s"?|Tem certeza de que deseja excluir a variante "%s"?
|
||||
Delete|Excluir
|
||||
Set default model|Definir como modelo padrão
|
||||
Remove default model|Remover modelo padrão
|
||||
|
||||
@@ -714,3 +714,10 @@ All|Sve
|
||||
Front and Back|Prednja i zadnja
|
||||
Click to choose from %s variants|Kliknite da izaberete jednu od %s varijanti
|
||||
Flange|Obod
|
||||
Set default variant|Postavi kao podrazumevanu varijantu
|
||||
Remove default variant|Ukloni podrazumevanu varijantu
|
||||
Delete variant|Obriši varijantu
|
||||
Are you sure you want to delete variant "%s"?|Da li ste sigurni da želite da obrišete varijantu "%s"?
|
||||
Delete|Obriši
|
||||
Set default model|Postavi kao podrazumevani model
|
||||
Remove default model|Ukloni podrazumevani model
|
||||
|
||||
@@ -716,3 +716,10 @@ All|सभी
|
||||
Front and Back|आगे और पीछे
|
||||
Click to choose from %s variants|%s वेरिएंट में से चुनने के लिए क्लिक करें
|
||||
Flange|फ़्लैंज
|
||||
Set default variant|डिफ़ॉल्ट वेरिएंट सेट करें
|
||||
Remove default variant|डिफ़ॉल्ट वेरिएंट हटाएँ
|
||||
Delete variant|वेरिएंट हटाएँ
|
||||
Are you sure you want to delete variant "%s"?|क्या आप वाकई वेरिएंट "%s" हटाना चाहते हैं?
|
||||
Delete|हटाएँ
|
||||
Set default model|डिफ़ॉल्ट मॉडल सेट करें
|
||||
Remove default model|डिफ़ॉल्ट मॉडल हटाएँ
|
||||
|
||||
@@ -716,3 +716,10 @@ All|全部
|
||||
Front and Back|前后
|
||||
Click to choose from %s variants|点击从 %s 个变体中选择
|
||||
Flange|法兰
|
||||
Set default variant|设为默认变体
|
||||
Remove default variant|取消默认变体
|
||||
Delete variant|删除变体
|
||||
Are you sure you want to delete variant "%s"?|确定要删除变体“%s”吗?
|
||||
Delete|删除
|
||||
Set default model|设为默认型号
|
||||
Remove default model|取消默认型号
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# Default Datasheet Model
|
||||
|
||||
Letting a datasheet model be pinned as a component's default, the way a user-defined variant can.
|
||||
|
||||
## 1. Goal
|
||||
|
||||
A saved variant in a component's drop-down has two mini buttons: a pin that makes it the default,
|
||||
so a plain click on the component in the toolbox or tree places that variant, and a bin that
|
||||
deletes it. Datasheet models (the TO-1 transistors, resistor series, IC part numbers, ...) sit in
|
||||
the same drop-down but have no pin, so a user who always places a 2N3904 has to walk the submenu
|
||||
every time. Datasheet model leaves get the same pin, and only one default can exist per component
|
||||
type, whether it is a variant or a model.
|
||||
|
||||
## 2. Current state
|
||||
|
||||
| Piece | Where | What it does |
|
||||
| --- | --- | --- |
|
||||
| Storage | `IVariantProcessor.DEFAULT_TEMPLATES_KEY` (`"defaultTemplates"`) | A `Map<String, String>` in the configuration: component class name (or the legacy `category.name` key) to the default variant's name. |
|
||||
| Write | `Presenter.setDefaultVariant` | Toggles: clicking the current default removes it, anything else replaces it. The logic lives in `Presenter`, unlike the read side. |
|
||||
| Read | `VariantManager.getDefaultVariant` | Looks up the class-name key, then the legacy key. |
|
||||
| Apply | `Presenter.setNewComponentTypeSlot` | When called with no template, finds the variant named by the default and uses it. |
|
||||
| UI | `ComponentButtonFactory.createVariantItem` | Adds the pin (`IconLoader.PinGreen` / `PinGrey`) and bin labels to a variant's `JMenuItem`; the pin hides the popup and calls `setDefaultVariant`. |
|
||||
| Model items | `ComponentButtonFactory.createDatasheetItems` | Builds the datasheet submenus recursively; leaves are plain `JMenuItem`s that hand the row to a `Consumer<String[]>`. |
|
||||
|
||||
`createDatasheetItems` has three callers: the variants button in the component tree
|
||||
(`TreePanel.addVariantItems`), the toolbox drop-down (`ComponentTabbedPane`), and the canvas
|
||||
right-click "apply model" submenu (`ComponentPopupMenu`), which applies a model to the selection
|
||||
rather than placing a new component.
|
||||
|
||||
Because the map holds one value per component type, "only one default" already holds as long as
|
||||
models are written to the same map.
|
||||
|
||||
## 3. Design decisions
|
||||
|
||||
| # | Decision | Choice | Rationale |
|
||||
| --- | --- | --- | --- |
|
||||
| D1 | Storage | Same `defaultTemplates` map and key. A model default is stored as the string `datasheet:` followed by the row's menu path, the first `datasheetCreationStepCount` columns joined by `\|`, e.g. `datasheet:BJT (NPN)\|2N3904` | One entry per type means pinning a model replaces a pinned variant and the other way round, with no extra bookkeeping. The menu path is exactly what identifies a leaf in `createDatasheetItems` (it already resolves a click by matching the path and taking the first row). No datasheet contains `\|` today. |
|
||||
| D2 | Compatibility | No config migration. An older DIYLC reading a `datasheet:` value finds no variant of that name and places the component with no default | The configuration is shared between installed versions, so the value has to be harmless to old readers, and it is. |
|
||||
| D3 | Name collision | The `datasheet:` prefix | A variant would have to be named `datasheet:...` to collide. |
|
||||
| D4 | Core API | `IVariantProcessor` gains `setDefaultModel(ComponentType, String[] model)` and `isDefaultModel(ComponentType, String[] model)`; encoding and decoding stay in `VariantManager`. `Presenter.setDefaultVariant`'s body moves into `VariantManager` so both toggles share one write path; `Presenter` only delegates | Keeps the string format out of Swing, and keeps new logic out of `Presenter`. |
|
||||
| D5 | Applying the default | In `setNewComponentTypeSlot`, when both template and model are null: if the default is a model, resolve its path against `componentType.getDatasheet()` and pass that row as the model; otherwise the current variant lookup. A path that no longer matches any row (a model dropped from a datasheet) falls through to no default | Same entry point every toolbox click, tree click, double click and drag already uses. Picking a model from the menu no longer also picks up a pinned variant's shape, which it used to because the lookup only checked for a missing template. |
|
||||
| D6 | Pin in the UI | Only on datasheet **leaves**, and only where the item places a new component: `createDatasheetItems` gets a `boolean showDefaultPin`, true from `TreePanel` and `ComponentTabbedPane`, false from `ComponentPopupMenu` | A default makes no sense in "apply model to selection". |
|
||||
| D7 | Shared pin code | The pin `JLabel` (icon, tooltip, hide-popup-then-toggle listener, border) is extracted from `createVariantItem` into one private helper used by both items; model leaves get only the pin, so their extra preferred width is 16 rather than 32 | Variant and model pins then look and behave identically. |
|
||||
| D8 | Tooltips | "Set default model" / "Remove default model", and the previously hard-coded "Set default variant", "Remove default variant", "Delete variant" and the delete confirmation, all through `LangUtil.translate`, in `English.txt` and all nine translations | Required for new UI text; the variant strings sit in the same code and are localized with it. |
|
||||
| D9 | Visibility of a nested default | Only the pinned leaf shows a green pin; the submenus leading to it do not | Green pins that cannot be clicked next to the one that can would be confusing. Pinning another model or a variant replaces the default anyway. |
|
||||
|
||||
## 4. Changes by file
|
||||
|
||||
| Module | File | Change |
|
||||
| --- | --- | --- |
|
||||
| core | `IVariantProcessor` | Add `setDefaultModel`, `isDefaultModel`. |
|
||||
| core | `VariantManager` | Add `setDefaultVariant` (moved from `Presenter`), `setDefaultModel`, `isDefaultModel`, `getDefaultModel(ComponentType)` returning the resolved row or null, and the private encode helper. |
|
||||
| core | `Presenter` | `setDefaultVariant` delegates; add the two delegating model methods; extend the default lookup in `setNewComponentTypeSlot` per D5. |
|
||||
| swing | `ComponentButtonFactory` | Extract the pin helper; pin on datasheet leaves behind `showDefaultPin`, threaded through the recursion. |
|
||||
| swing | `TreePanel`, `ComponentTabbedPane`, `ComponentPopupMenu` | Pass `showDefaultPin` (true, true, false). |
|
||||
| swing | `lang/*.txt` | Seven new keys in all ten files, each file's own line endings. |
|
||||
| core | `update.xml` | One `NEW_FEATURE` line in the unreleased 6.10.0 block. |
|
||||
|
||||
## 5. Tests
|
||||
|
||||
`VariantManagerTest` in `diylc-core` with a map-backed `IConfigurationManager`:
|
||||
|
||||
- pinning a model stores the prefixed path and `isDefaultModel` reports it;
|
||||
- pinning the same model again clears it;
|
||||
- pinning a model replaces a pinned variant and pinning a variant replaces a pinned model;
|
||||
- `getDefaultModel` resolves the row, and returns null when the path is no longer in the datasheet;
|
||||
- a legacy `category.name` key is still read and is removed on the next write.
|
||||
|
||||
Verification: `mvn -q -pl diylc-core -am test`, then `mvn -q -pl diylc-swing -am compile`, then a
|
||||
manual check in the running app (pin a TO-1 model, click the toolbox button, confirm the part;
|
||||
pin a variant and confirm the model's pin turns grey). No rendering, serialization or netlist
|
||||
change, so no regression run is needed.
|
||||
Reference in New Issue
Block a user