mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-13 10:42:09 +10:00
Building blocks (#1072)
* composite block support * naming * Added ability to set node name (or names) for some connectivity components * cleanup * Include building blocks in the Recently Used category * Prompt to confirm the overwrite when saving over the existing building blocks; When saving building blocks, present a drop down list with most recently used building blocks * add alpha to composite * Update CompositeComponentTest.java * cleanup * Update CompositeComponent.java * cleanup * Update CompositeComponent.java * <org.diylc.appframework.update.Change> <changeType>IMPROVEMENT</changeType> <description>When saving building blocks, present a drop down list with most recently used building blocks for quick selection</description> </org.diylc.appframework.update.Change> * Diode components now label their terminal nodes as 'A' (anode) and 'K' (cathode) instead of '1' and '2' * Capacitor components now label their terminal nodes as '+' and '-' instead of '1' and '2' when 'Polarized' property is ticked
This commit is contained in:
+77
-5
@@ -1,9 +1,18 @@
|
||||
package org.diylc.swing.actions.edit;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.common.IBlockProcessor;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.swing.ActionFactory;
|
||||
import org.diylc.utils.IconLoader;
|
||||
@@ -12,6 +21,9 @@ public class SaveAsBlockAction extends AbstractAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** How many recently used building blocks to offer in the name drop-down. */
|
||||
private static final int MAX_RECENT_BLOCKS = 10;
|
||||
|
||||
private IPlugInPort plugInPort;
|
||||
|
||||
public SaveAsBlockAction(IPlugInPort plugInPort) {
|
||||
@@ -24,10 +36,70 @@ public class SaveAsBlockAction extends AbstractAction {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ActionFactory.LOG.info("Save as building block triggered");
|
||||
String templateName = JOptionPane.showInputDialog(null, "Block name:",
|
||||
"Save as Building Block", JOptionPane.PLAIN_MESSAGE);
|
||||
if (templateName != null && !templateName.trim().isEmpty()) {
|
||||
plugInPort.saveSelectionAsBlock(templateName);
|
||||
|
||||
JComboBox<String> nameBox =
|
||||
new JComboBox<String>(getRecentBlockNames().toArray(new String[0]));
|
||||
nameBox.setEditable(true);
|
||||
// Start empty so an existing block is never overwritten by accident. The drop-down still
|
||||
// offers the most recently used blocks (most recent first), so a user editing a block can
|
||||
// pick its name back with a single click instead of retyping it.
|
||||
nameBox.setSelectedItem("");
|
||||
|
||||
JPanel panel = new JPanel(new BorderLayout(5, 5));
|
||||
panel.add(new JLabel("Block name:"), BorderLayout.NORTH);
|
||||
panel.add(nameBox, BorderLayout.CENTER);
|
||||
|
||||
// Loop so that declining the overwrite prompt returns to the name dialog (with whatever was
|
||||
// typed still in it) rather than cancelling the whole action.
|
||||
while (true) {
|
||||
int result = JOptionPane.showConfirmDialog(null, panel, "Save as Building Block",
|
||||
JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
|
||||
if (result != JOptionPane.OK_OPTION)
|
||||
return;
|
||||
|
||||
// getEditor().getItem() captures in-progress typing as well as a picked item
|
||||
Object selected = nameBox.getEditor().getItem();
|
||||
String blockName = selected == null ? null : selected.toString().trim();
|
||||
if (blockName == null || blockName.isEmpty())
|
||||
return;
|
||||
|
||||
if (blockExists(blockName)) {
|
||||
int overwrite = JOptionPane.showConfirmDialog(null,
|
||||
"A building block named \"" + blockName + "\" already exists. Overwrite it?",
|
||||
"Save as Building Block", JOptionPane.YES_NO_CANCEL_OPTION,
|
||||
JOptionPane.WARNING_MESSAGE);
|
||||
if (overwrite == JOptionPane.NO_OPTION)
|
||||
continue; // back to the name dialog
|
||||
if (overwrite != JOptionPane.YES_OPTION)
|
||||
return; // Cancel or dialog closed
|
||||
}
|
||||
|
||||
plugInPort.saveSelectionAsBlock(blockName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean blockExists(String blockName) {
|
||||
Map<String, ?> blocks = (Map<String, ?>) ConfigurationManager.getInstance()
|
||||
.readObject(IBlockProcessor.BLOCKS_KEY, null);
|
||||
return blocks != null && blocks.containsKey(blockName);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<String> getRecentBlockNames() {
|
||||
List<String> recent = (List<String>) ConfigurationManager.getInstance()
|
||||
.readObject(IPlugInPort.RECENT_COMPONENTS_KEY, null);
|
||||
List<String> blockNames = new ArrayList<String>();
|
||||
if (recent != null) {
|
||||
for (String identifier : recent) {
|
||||
if (identifier.startsWith(IBlockProcessor.BLOCK_PREFIX)) {
|
||||
blockNames.add(identifier.substring(IBlockProcessor.BLOCK_PREFIX.length()));
|
||||
if (blockNames.size() >= MAX_RECENT_BLOCKS)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return blockNames;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,8 +45,10 @@ import org.apache.log4j.Logger;
|
||||
import org.diylc.appframework.miscutils.IConfigListener;
|
||||
import org.diylc.appframework.miscutils.IConfigurationManager;
|
||||
|
||||
import org.diylc.common.BlockInstantiationMode;
|
||||
import org.diylc.common.ComponentType;
|
||||
import org.diylc.common.DrawOption;
|
||||
import org.diylc.common.IBlockProcessor;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.common.IBlockProcessor.InvalidBlockException;
|
||||
import org.diylc.swing.plugins.tree.TreePanel;
|
||||
@@ -223,6 +225,17 @@ public class CanvasPanel extends JComponent implements Autoscroll {
|
||||
(List<String>) configManager.readObject(IPlugInPort.RECENT_COMPONENTS_KEY, null);
|
||||
if (recent != null && !recent.isEmpty()) {
|
||||
String clazz = recent.get(0);
|
||||
if (clazz.startsWith(IBlockProcessor.BLOCK_PREFIX)) {
|
||||
String blockName = clazz.substring(IBlockProcessor.BLOCK_PREFIX.length());
|
||||
try {
|
||||
CanvasPanel.this.plugInPort.loadBlock(blockName, BlockInstantiationMode.COMPOSITE);
|
||||
// hack: fake mouse movement to repaint
|
||||
CanvasPanel.this.plugInPort.mouseMoved(getMousePosition(), false, false, false);
|
||||
} catch (InvalidBlockException ex) {
|
||||
LOG.error("Could not repeat last building block: " + blockName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Map<String, List<ComponentType>> componentTypes =
|
||||
CanvasPanel.this.plugInPort.getComponentTypes();
|
||||
for (Map.Entry<String, List<ComponentType>> entry : componentTypes.entrySet()) {
|
||||
@@ -249,10 +262,10 @@ public class CanvasPanel extends JComponent implements Autoscroll {
|
||||
String typeName = shortcutMap.get("F" + i);
|
||||
if (typeName == null)
|
||||
return;
|
||||
if (typeName.startsWith("block:")) {
|
||||
String blockName = typeName.substring(6);
|
||||
if (typeName.startsWith(IBlockProcessor.BLOCK_PREFIX)) {
|
||||
String blockName = typeName.substring(IBlockProcessor.BLOCK_PREFIX.length());
|
||||
try {
|
||||
plugInPort.loadBlock(blockName);
|
||||
plugInPort.loadBlock(blockName, BlockInstantiationMode.COMPOSITE);
|
||||
} catch (InvalidBlockException e) {
|
||||
LOG.error("Could not find block assigned to shortcut: " + blockName);
|
||||
}
|
||||
|
||||
+2
-1
@@ -53,6 +53,7 @@ import org.diylc.appframework.miscutils.Utils;
|
||||
import org.diylc.swingframework.openide.DropDownButtonFactory;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import org.diylc.common.BlockInstantiationMode;
|
||||
import org.diylc.common.ComponentType;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.common.IBlockProcessor.InvalidBlockException;
|
||||
@@ -316,7 +317,7 @@ public class ComponentButtonFactory {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
try {
|
||||
plugInPort.loadBlock(blockName);
|
||||
plugInPort.loadBlock(blockName, BlockInstantiationMode.COMPOSITE);
|
||||
} catch (InvalidBlockException e1) {
|
||||
LOG.error("Error loading building block", e1);
|
||||
}
|
||||
|
||||
+19
-9
@@ -48,6 +48,7 @@ import org.diylc.appframework.miscutils.IConfigListener;
|
||||
|
||||
import org.diylc.common.ComponentType;
|
||||
import org.diylc.common.Favorite;
|
||||
import org.diylc.common.IBlockProcessor;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.common.Favorite.FavoriteType;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
@@ -59,6 +60,7 @@ import org.diylc.swing.ISwingUI;
|
||||
import org.diylc.utils.IconLoader;
|
||||
import org.diylc.swing.plugins.tree.TreePanel;
|
||||
import org.diylc.swing.plugins.tree.TreePanel.DeleteBlockAction;
|
||||
import org.diylc.swing.plugins.tree.TreePanel.InsertBlockAsGroupAction;
|
||||
import org.diylc.swing.plugins.tree.TreePanel.SelectAllAction;
|
||||
|
||||
/**
|
||||
@@ -286,16 +288,22 @@ class ComponentTabbedPane extends JTabbedPane {
|
||||
private void refreshRecentComponentsToolbar(List<String> recentComponentClassList) {
|
||||
Container toolbar = getRecentToolbar();
|
||||
toolbar.removeAll();
|
||||
for (String componentClassName : recentComponentClassList) {
|
||||
ComponentType componentType;
|
||||
for (String identifier : recentComponentClassList) {
|
||||
try {
|
||||
componentType = ComponentProcessor.getInstance().extractComponentTypeFrom(
|
||||
(Class<? extends IDIYComponent<?>>) Class.forName(componentClassName));
|
||||
Component button = ComponentButtonFactory.create(plugInPort, componentType,
|
||||
createVariantPopup(componentType, null));
|
||||
toolbar.add(button);
|
||||
if (identifier.startsWith(IBlockProcessor.BLOCK_PREFIX)) {
|
||||
String blockName = identifier.substring(IBlockProcessor.BLOCK_PREFIX.length());
|
||||
Component button = ComponentButtonFactory.createBuildingBlockButton(plugInPort, blockName,
|
||||
createVariantPopup(null, blockName));
|
||||
toolbar.add(button);
|
||||
} else {
|
||||
ComponentType componentType = ComponentProcessor.getInstance().extractComponentTypeFrom(
|
||||
(Class<? extends IDIYComponent<?>>) Class.forName(identifier));
|
||||
Component button = ComponentButtonFactory.create(plugInPort, componentType,
|
||||
createVariantPopup(componentType, null));
|
||||
toolbar.add(button);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Could not create recent component button for " + componentClassName, e);
|
||||
LOG.error("Could not create recent component button for " + identifier, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -335,7 +343,8 @@ class ComponentTabbedPane extends JTabbedPane {
|
||||
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
||||
variantPopup.removeAll();
|
||||
|
||||
final String identifier = componentType == null ? "block:" + blockName
|
||||
final String identifier = componentType == null
|
||||
? IBlockProcessor.BLOCK_PREFIX + blockName
|
||||
: componentType.getInstanceClass().getCanonicalName();
|
||||
|
||||
JMenu shortcutSubmenu = new JMenu("Assign Shortcut");
|
||||
@@ -456,6 +465,7 @@ class ComponentTabbedPane extends JTabbedPane {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
variantPopup.add(new InsertBlockAsGroupAction(plugInPort, blockName));
|
||||
variantPopup.add(shortcutSubmenu);
|
||||
variantPopup.add(new DeleteBlockAction(plugInPort, swingUI, blockName));
|
||||
}
|
||||
|
||||
@@ -37,8 +37,10 @@ import org.apache.log4j.Logger;
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.appframework.miscutils.IConfigListener;
|
||||
|
||||
import org.diylc.common.BlockInstantiationMode;
|
||||
import org.diylc.common.ComponentType;
|
||||
import org.diylc.common.Favorite;
|
||||
import org.diylc.common.IBlockProcessor;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.common.Favorite.FavoriteType;
|
||||
import org.diylc.common.IBlockProcessor.InvalidBlockException;
|
||||
@@ -142,7 +144,7 @@ public class CustomTreeModel implements TreeModel {
|
||||
&& System.currentTimeMillis() - previousActionTime > 100) {
|
||||
previousActionTime = System.currentTimeMillis();
|
||||
try {
|
||||
plugInPort.loadBlock(fav.getName());
|
||||
plugInPort.loadBlock(fav.getName(), BlockInstantiationMode.COMPOSITE);
|
||||
} catch (InvalidBlockException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
@@ -155,8 +157,37 @@ public class CustomTreeModel implements TreeModel {
|
||||
}
|
||||
|
||||
if (RECENTLY_USED.equals(category)) {
|
||||
for (String className : this.recentComponents) {
|
||||
ComponentType type = this.typesByClass.get(className);
|
||||
for (String identifier : this.recentComponents) {
|
||||
if (identifier.startsWith(IBlockProcessor.BLOCK_PREFIX)) {
|
||||
final String blockName = identifier.substring(IBlockProcessor.BLOCK_PREFIX.length());
|
||||
// skip blocks that no longer exist
|
||||
if (!this.blocks.contains(blockName))
|
||||
continue;
|
||||
if (this.searchText != null && !blockName.toLowerCase().contains(this.searchText))
|
||||
continue;
|
||||
visibleTypes.add(new TreeNode(blockName, new MouseAdapter() {
|
||||
|
||||
long previousActionTime = 0;
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e == null || SwingUtilities.isLeftMouseButton(e)
|
||||
&& System.currentTimeMillis() - previousActionTime > 100) {
|
||||
previousActionTime = System.currentTimeMillis();
|
||||
try {
|
||||
plugInPort.loadBlock(blockName, BlockInstantiationMode.COMPOSITE);
|
||||
} catch (InvalidBlockException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, true));
|
||||
continue;
|
||||
}
|
||||
final ComponentType type = this.typesByClass.get(identifier);
|
||||
// skip unknown component classes
|
||||
if (type == null)
|
||||
continue;
|
||||
if (this.searchText == null || type.getName().toLowerCase().contains(this.searchText)) {
|
||||
visibleTypes.add(new TreeNode(type, new MouseAdapter() {
|
||||
|
||||
@@ -182,7 +213,7 @@ public class CustomTreeModel implements TreeModel {
|
||||
&& System.currentTimeMillis() - previousActionTime > 100) {
|
||||
previousActionTime = System.currentTimeMillis();
|
||||
try {
|
||||
plugInPort.loadBlock(block);
|
||||
plugInPort.loadBlock(block, BlockInstantiationMode.COMPOSITE);
|
||||
} catch (InvalidBlockException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
@@ -233,10 +264,16 @@ public class CustomTreeModel implements TreeModel {
|
||||
}
|
||||
|
||||
if (RECENTLY_USED.equals(category)) {
|
||||
for (String className : this.recentComponents) {
|
||||
ComponentType type = typesByClass.get(className);
|
||||
if (type.getName().toLowerCase().contains(searchText))
|
||||
return true;
|
||||
for (String identifier : this.recentComponents) {
|
||||
if (identifier.startsWith(IBlockProcessor.BLOCK_PREFIX)) {
|
||||
if (identifier.substring(IBlockProcessor.BLOCK_PREFIX.length()).toLowerCase()
|
||||
.contains(searchText))
|
||||
return true;
|
||||
} else {
|
||||
ComponentType type = typesByClass.get(identifier);
|
||||
if (type != null && type.getName().toLowerCase().contains(searchText))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,10 +60,13 @@ import org.apache.log4j.lf5.viewer.categoryexplorer.TreeModelAdapter;
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.appframework.miscutils.IConfigListener;
|
||||
|
||||
import org.diylc.common.BlockInstantiationMode;
|
||||
import org.diylc.common.ComponentType;
|
||||
import org.diylc.common.Favorite;
|
||||
import org.diylc.common.IBlockProcessor;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.common.Favorite.FavoriteType;
|
||||
import org.diylc.common.IBlockProcessor.InvalidBlockException;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.IView;
|
||||
import org.diylc.core.Template;
|
||||
@@ -313,7 +316,8 @@ public class TreePanel extends JPanel {
|
||||
TreeNode payload = (TreeNode) selectedNode;
|
||||
final ComponentType componentType = payload.getComponentType();
|
||||
|
||||
final String identifier = componentType == null ? "block:" + payload.toString()
|
||||
final String identifier = componentType == null
|
||||
? IBlockProcessor.BLOCK_PREFIX + payload.toString()
|
||||
: componentType.getInstanceClass().getCanonicalName();
|
||||
|
||||
JMenu shortcutSubmenu = new JMenu("Assign Shortcut");
|
||||
@@ -434,6 +438,7 @@ public class TreePanel extends JPanel {
|
||||
}
|
||||
}
|
||||
} else if (getTreeModel().isLeaf(selectedNode)) {
|
||||
popup.add(new InsertBlockAsGroupAction(plugInPort, payload.toString()));
|
||||
popup.add(shortcutSubmenu);
|
||||
popup.add(new DeleteBlockAction(plugInPort, swingUI, payload.toString()));
|
||||
}
|
||||
@@ -494,7 +499,8 @@ public class TreePanel extends JPanel {
|
||||
|
||||
HashMap<String, String> shortcutMap = (HashMap<String, String>) ConfigurationManager
|
||||
.getInstance().readObject(TreePanel.COMPONENT_SHORTCUT_KEY, null);
|
||||
String identifier = payload.getComponentType() == null ? "block:" + payload.toString()
|
||||
String identifier = payload.getComponentType() == null
|
||||
? IBlockProcessor.BLOCK_PREFIX + payload.toString()
|
||||
: payload.getComponentType().getInstanceClass().getCanonicalName();
|
||||
if (shortcutMap != null && shortcutMap.containsValue(identifier)) {
|
||||
for (String key : shortcutMap.keySet()) {
|
||||
@@ -565,6 +571,37 @@ public class TreePanel extends JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Left click on a building block node inserts it in composite mode (a single rigid component)
|
||||
* by default - see design decision D10 in {@code docs/plans/composite-building-blocks.md}.
|
||||
* This action offers the one extra click needed to fall back to the original behavior:
|
||||
* every component of the block instantiated separately and auto-grouped.
|
||||
*/
|
||||
public static class InsertBlockAsGroupAction extends AbstractAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private IPlugInPort plugInPort;
|
||||
private String blockName;
|
||||
|
||||
public InsertBlockAsGroupAction(IPlugInPort plugInPort, String blockName) {
|
||||
super();
|
||||
this.plugInPort = plugInPort;
|
||||
this.blockName = blockName;
|
||||
putValue(AbstractAction.NAME, "Insert as Grouped Components");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
LOG.info(getValue(AbstractAction.NAME) + " triggered");
|
||||
try {
|
||||
plugInPort.loadBlock(blockName, BlockInstantiationMode.GROUP);
|
||||
} catch (InvalidBlockException e1) {
|
||||
LOG.error("Could not find building block: " + blockName, e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class DeleteBlockAction extends AbstractAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
Reference in New Issue
Block a user