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