diff --git a/diylc/diylc-core/src/org/diylc/common/Favorite.java b/diylc/diylc-core/src/org/diylc/common/Favorite.java new file mode 100644 index 00000000..b102c12f --- /dev/null +++ b/diylc/diylc-core/src/org/diylc/common/Favorite.java @@ -0,0 +1,88 @@ +/* + + DIY Layout Creator (DIYLC). + Copyright (c) 2009-2018 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 . + +*/ +package org.diylc.common; + +import java.io.Serializable; + +public class Favorite implements Serializable, Comparable { + + private static final long serialVersionUID = 1L; + + private FavoriteType type; + private String name; + + public Favorite(FavoriteType type, String name) { + super(); + this.type = type; + this.name = name; + } + + public FavoriteType getType() { + return type; + } + + public String getName() { + return name; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Favorite other = (Favorite) obj; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (type != other.type) + return false; + return true; + } + + @Override + public String toString() { + return type + ":" + name; + } + + public enum FavoriteType { + Component, Block + } + + @Override + public int compareTo(Favorite o) { + return name.compareToIgnoreCase(o.getName()); + } +} diff --git a/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java b/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java index 4972d299..c1871f81 100644 --- a/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java +++ b/diylc/diylc-core/src/org/diylc/common/IPlugInPort.java @@ -74,7 +74,8 @@ public interface IPlugInPort extends ISelectionProcessor, IMouseProcessor, IKeyP public static final String SHOW_GRID_KEY = "showGrid"; public static final String HIGHLIGHT_CONTINUITY_AREA = "highlightContinuityArea"; public static final String HARDWARE_ACCELERATION = "hardwareAcceleration"; - public static final String EXTRA_SPACE_KEY = "extraSpace"; + public static final String EXTRA_SPACE_KEY = "extraSpace"; + public static final String FAVORITES_KEY = "favorites"; public static final int DND_TOGGLE_STICKY = 0x1; public static final int DND_TOGGLE_SNAP = 0x40000000; diff --git a/diylc/diylc-swing/src/org/diylc/images/IconLoader.java b/diylc/diylc-swing/src/org/diylc/images/IconLoader.java index 127c37dd..dade75d4 100644 --- a/diylc/diylc-swing/src/org/diylc/images/IconLoader.java +++ b/diylc/diylc-swing/src/org/diylc/images/IconLoader.java @@ -63,7 +63,7 @@ public enum IconLoader { "component_add.png"), History("history.png"), DocumentPlain("document_plain.png"), FitToSize("fit_to_size.png"), DocumentsGear( "documents_gear.png"), SplashResistor("splash_resistor.png"), SplashCeramic("splash_ceramic.png"), SplashElectrolytic( "splash_electrolytic.png"), SplashFilm("splash_film.png"), Splash("splash.png"), Help("help2.png"), ScrollInformation("scroll_information.png"), - Node("node.png"), Web("web.png"), Guitar("guitar.png"), Scientist("scientist.png"); + Node("node.png"), Web("web.png"), Guitar("guitar.png"), Scientist("scientist.png"), StarGrey("star_grey.png"), StarBlue("star_blue.png"); protected String name; diff --git a/diylc/diylc-swing/src/org/diylc/images/star_blue.png b/diylc/diylc-swing/src/org/diylc/images/star_blue.png new file mode 100644 index 00000000..711f7155 Binary files /dev/null and b/diylc/diylc-swing/src/org/diylc/images/star_blue.png differ diff --git a/diylc/diylc-swing/src/org/diylc/swing/plugins/tree/TreePanel.java b/diylc/diylc-swing/src/org/diylc/swing/plugins/tree/TreePanel.java index 97e1e353..f6a50963 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/plugins/tree/TreePanel.java +++ b/diylc/diylc-swing/src/org/diylc/swing/plugins/tree/TreePanel.java @@ -36,7 +36,6 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -63,6 +62,7 @@ import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; +import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; @@ -72,9 +72,12 @@ import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import org.apache.log4j.Logger; +import org.apache.log4j.lf5.viewer.categoryexplorer.TreeModelAdapter; import org.diylc.appframework.miscutils.ConfigurationManager; import org.diylc.appframework.miscutils.IConfigListener; import org.diylc.common.ComponentType; +import org.diylc.common.Favorite; +import org.diylc.common.Favorite.FavoriteType; import org.diylc.common.IBlockProcessor.InvalidBlockException; import org.diylc.common.IPlugInPort; import org.diylc.core.IDIYComponent; @@ -99,11 +102,14 @@ public class TreePanel extends JPanel { private JTextField searchField; private DefaultMutableTreeNode recentNode; private DefaultMutableTreeNode blocksNode; + private DefaultMutableTreeNode favoritesNode; private List recentComponents; + private List favorites; private List blocks; private IPlugInPort plugInPort; private ISwingUI swingUI; + private boolean initializing = false; private JPopupMenu popup; @@ -142,7 +148,7 @@ public class TreePanel extends JPanel { LOG.info("Detected recent component change"); refreshRecentComponents(newComponents); } else - LOG.info("Detected no recent component change"); + LOG.info("Detected no recent component change"); TreePanel.this.recentComponents = new ArrayList(newComponents); } }); @@ -174,6 +180,20 @@ public class TreePanel extends JPanel { repaint(); } }); + + ConfigurationManager.getInstance().addConfigListener(IPlugInPort.FAVORITES_KEY, new IConfigListener() { + + @SuppressWarnings("unchecked") + @Override + public void valueChanged(String key, Object value) { + List newFavorites = (List) value; + if (newFavorites != null && !newFavorites.equals(TreePanel.this.favorites)) { + LOG.info("Detected favorites change"); + refreshFavorites(newFavorites); + } else + LOG.info("Detected no favorites change"); + } + }); getTree().expandRow(0); @@ -199,6 +219,7 @@ public class TreePanel extends JPanel { public DefaultMutableTreeNode getRecentNode() { if (this.recentNode == null) { this.recentNode = new DefaultMutableTreeNode(new Payload("(Recently Used)", null), true); + @SuppressWarnings("unchecked") List recent = (List) ConfigurationManager.getInstance().readObject(IPlugInPort.RECENT_COMPONENTS_KEY, null); @@ -215,6 +236,7 @@ public class TreePanel extends JPanel { public DefaultMutableTreeNode getBlocksNode() { if (this.blocksNode == null) { this.blocksNode = new DefaultMutableTreeNode(new Payload("(Building Blocks)", null), true); + @SuppressWarnings("unchecked") Map>> newBlocks = (Map>>) ConfigurationManager.getInstance().readObject( @@ -223,13 +245,27 @@ public class TreePanel extends JPanel { List blockNames = new ArrayList(newBlocks.keySet()); Collections.sort(blockNames); refreshBuildingBlocks(blockNames); - this.blocks = blockNames; } else { this.blocks = new ArrayList(); } } return this.blocksNode; } + + public DefaultMutableTreeNode getFavoritesNode() { + if (this.favoritesNode == null) { + this.favoritesNode = new DefaultMutableTreeNode(new Payload("(Favorites)", null), true); + + @SuppressWarnings("unchecked") + List favorites = (List) ConfigurationManager.getInstance().readObject(IPlugInPort.FAVORITES_KEY, null); + if (favorites != null) { + refreshFavorites(favorites); + } else { + this.favorites = new ArrayList(); + } + } + return this.favoritesNode; + } @SuppressWarnings("unchecked") private void refreshRecentComponents(List recentComponentClassList) { @@ -260,7 +296,8 @@ public class TreePanel extends JPanel { LOG.error("Could not create recent component button for " + componentClassName, e); } } - getTreeModel().nodeStructureChanged(getRecentNode()); + if (!initializing) + getTreeModel().nodeStructureChanged(getRecentNode()); } private void refreshBuildingBlocks(List blocks) { @@ -291,14 +328,72 @@ public class TreePanel extends JPanel { payload.setVisible(visible); getBlocksNode().add(componentNode); } - getTreeModel().nodeStructureChanged(getBlocksNode()); + if (!initializing) + getTreeModel().nodeStructureChanged(getBlocksNode()); + } + + private void refreshFavorites(List favorites) { + this.favorites = new ArrayList(favorites); + + getFavoritesNode().removeAllChildren(); + + Map> types = plugInPort.getComponentTypes(); + Map typesByClass = new HashMap(); + for (Map.Entry> e : types.entrySet()) + for (ComponentType c : e.getValue()) + typesByClass.put(c.getInstanceClass().getCanonicalName(), c); + + for (Favorite f : favorites) { + if (f.getType() == FavoriteType.Component) { + final ComponentType type = typesByClass.get(f.getName()); + if (type != null) { + DefaultMutableTreeNode componentNode = new DefaultMutableTreeNode(new Payload(type, new MouseAdapter() { + + @Override + public void mouseClicked(MouseEvent e) { + plugInPort.setNewComponentTypeSlot(type, null, false); + } + }), false); + getFavoritesNode().add(componentNode); + } + } else if (f.getType() == FavoriteType.Block) { + final String block = f.getName(); + Payload payload = new Payload(block, 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(block); + } catch (InvalidBlockException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + } + } + }); + final DefaultMutableTreeNode componentNode = new DefaultMutableTreeNode(payload, false); + String text = getSearchField().getText(); + boolean visible = text.trim().length() == 0 || block.toLowerCase().contains(text.toLowerCase()); + payload.setVisible(visible); + getFavoritesNode().add(componentNode); + } + } + if (!initializing) + getTreeModel().nodeStructureChanged(getFavoritesNode()); } public DefaultTreeModel getTreeModel() { if (treeModel == null) { final DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Components", true); + initializing = true; + rootNode.add(getFavoritesNode()); rootNode.add(getRecentNode()); rootNode.add(getBlocksNode()); + initializing = false; Map> componentTypes = plugInPort.getComponentTypes(); List categories = new ArrayList(componentTypes.keySet()); Collections.sort(categories); @@ -317,6 +412,18 @@ public class TreePanel extends JPanel { } } treeModel = new DefaultTreeModel(rootNode); + treeModel.addTreeModelListener(new TreeModelAdapter() { + @Override + public void treeNodesRemoved(TreeModelEvent e) { + super.treeNodesRemoved(e); + } + + @Override + public void treeNodesChanged(TreeModelEvent e) { + // TODO Auto-generated method stub + super.treeNodesChanged(e); + } + }); } return treeModel; } @@ -459,6 +566,27 @@ public class TreePanel extends JPanel { }); shortcutSubmenu.add(item); } + + final Favorite fav = new Favorite(componentType == null ? FavoriteType.Block : FavoriteType.Component, componentType == null ? payload.toString() : componentType.getInstanceClass() + .getCanonicalName()); + final boolean isFavorite = favorites != null && favorites.indexOf(fav) >= 0; + final JMenuItem favoritesItem = new JMenuItem(isFavorite ? "Remove From Favorites" : "Add To Favorites", + isFavorite ? IconLoader.StarBlue.getIcon() : IconLoader.StarGrey.getIcon()); + favoritesItem.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + List favorites = new ArrayList(TreePanel.this.favorites); + if (isFavorite) { + favorites.remove(fav); + } else { + favorites.add(fav); + Collections.sort(favorites); + } + ConfigurationManager.getInstance().writeValue(IPlugInPort.FAVORITES_KEY, favorites); + } + }); + popup.add(favoritesItem); if (componentType != null) { popup.add(new SelectAllAction(plugInPort, componentType)); diff --git a/diylc/diylc-swing/update.xml b/diylc/diylc-swing/update.xml index 58398335..29a62f28 100644 --- a/diylc/diylc-swing/update.xml +++ b/diylc/diylc-swing/update.xml @@ -2577,7 +2577,11 @@ 2019-03-02 00:00:00.000 CET - + + + NEW_FEATURE + Ability to tag components and building blocks as favorites + BUG_FIX "Highlight Connected Areas" doesn't follow multiple chained wires and jumpers