From ebb6a57ee847da1a19028fbc3c330efdd693b607 Mon Sep 17 00:00:00 2001 From: bancika Date: Mon, 26 Dec 2016 17:29:42 +0100 Subject: [PATCH] added searchable component tree --- .../src/org/diylc/images/IconLoader.java | 2 +- .../src/org/diylc/images/hammer.png | Bin 0 -> 874 bytes .../src/org/diylc/images/screwdriver.png | Bin 0 -> 704 bytes .../src/org/diylc/images/search-box.png | Bin 0 -> 3323 bytes .../src/org/diylc/swing/ActionFactory.java | 29 ++ .../src/org/diylc/swing/gui/MainFrame.java | 2 + .../swing/plugins/config/ConfigPlugin.java | 15 +- .../diylc/swing/plugins/toolbox/ToolBox.java | 15 + .../swing/plugins/tree/ComponentTree.java | 72 +++++ .../diylc/swing/plugins/tree/TreePanel.java | 282 ++++++++++++++++++ 10 files changed, 414 insertions(+), 3 deletions(-) create mode 100644 diylc/diylc-swing/src/org/diylc/images/hammer.png create mode 100644 diylc/diylc-swing/src/org/diylc/images/screwdriver.png create mode 100644 diylc/diylc-swing/src/org/diylc/images/search-box.png create mode 100644 diylc/diylc-swing/src/org/diylc/swing/plugins/tree/ComponentTree.java create mode 100644 diylc/diylc-swing/src/org/diylc/swing/plugins/tree/TreePanel.java diff --git a/diylc/diylc-swing/src/org/diylc/images/IconLoader.java b/diylc/diylc-swing/src/org/diylc/images/IconLoader.java index 52d6030d..dec8d92b 100644 --- a/diylc/diylc-swing/src/org/diylc/images/IconLoader.java +++ b/diylc/diylc-swing/src/org/diylc/images/IconLoader.java @@ -36,7 +36,7 @@ public enum IconLoader { "cloud_edit.png"), CloudUpload("cloud_upload.png"), NavLeftBlue("nav_left_blue.png"), NavRightBlue( "nav_right_blue.png"), MissingImage("missing_image.png"), CloudBg("cloud_bg.png"), CloudBig("cloud_big.png"), CloudWait( "cloud_wait.png"), Spinning("spinning.gif"), Megaphone("megaphone.png"), Download("download.png"), Eye("eye.png"), Messages( - "messages.png"); + "messages.png"), SearchBox("search-box.png"), Screwdriver("screwdriver.png"), Hammer("hammer.png"); protected String name; diff --git a/diylc/diylc-swing/src/org/diylc/images/hammer.png b/diylc/diylc-swing/src/org/diylc/images/hammer.png new file mode 100644 index 0000000000000000000000000000000000000000..2d55c7ac9f15925f5a0aa01aa2e61c776b148952 GIT binary patch literal 874 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 getSubscribedEventTypes() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void processMessage(EventType eventType, Object... params) { + // TODO Auto-generated method stub + + } +} 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 new file mode 100644 index 00000000..8a258347 --- /dev/null +++ b/diylc/diylc-swing/src/org/diylc/swing/plugins/tree/TreePanel.java @@ -0,0 +1,282 @@ +package org.diylc.swing.plugins.tree; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.swing.Icon; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.JTree; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; + +import org.diylc.common.ComponentType; +import org.diylc.common.IPlugInPort; +import org.diylc.images.IconLoader; + +public class TreePanel extends JPanel { + + private static final long serialVersionUID = 1L; + + private DefaultTreeModel treeModel; + private JTree tree; + private JTextField searchField; + + private IPlugInPort plugInPort; + + public TreePanel(IPlugInPort plugInPort) { + this.plugInPort = plugInPort; + setLayout(new GridBagLayout()); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weighty = 0; + + add(getSearchField(), gbc); + + gbc.gridy++; + gbc.fill = GridBagConstraints.BOTH; + gbc.weighty = 1; + gbc.weightx = 1; + + JScrollPane scroll = new JScrollPane(getTree()); + scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + add(scroll, gbc); + + setPreferredSize(new Dimension(240, 200)); + } + + public DefaultTreeModel getTreeModel() { + if (treeModel == null) { + final DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Components", true); + Map> componentTypes = plugInPort.getComponentTypes(); + List categories = new ArrayList(componentTypes.keySet()); + Collections.sort(categories); + for (String category : categories) { + final DefaultMutableTreeNode categoryNode = new DefaultMutableTreeNode(new Payload(category)); + rootNode.add(categoryNode); + for (ComponentType type : componentTypes.get(category)) { + final DefaultMutableTreeNode componentNode = new DefaultMutableTreeNode(new Payload(type), false); + categoryNode.add(componentNode); + } + } + treeModel = new DefaultTreeModel(rootNode); + } + return treeModel; + } + + public JTree getTree() { + if (tree == null) { + + + tree = new JTree(getTreeModel()); + tree.setRootVisible(false); + tree.setCellRenderer(new ComponentCellRenderer()); + tree.setRowHeight(0); + tree.addTreeSelectionListener(new TreeSelectionListener() { + + @Override + public void valueChanged(TreeSelectionEvent e) { + Object c = e.getPath().getLastPathComponent(); + if (c != null && c.getClass().equals(DefaultMutableTreeNode.class)) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) c; + Object obj = node.getUserObject(); + if (obj != null && obj.getClass().equals(Payload.class)) { + Payload payload = (Payload) obj; + plugInPort.setNewComponentTypeSlot(payload.getComponentType(), null); + } + } + } + }); + } + return tree; + } + + public JTextField getSearchField() { + if (searchField == null) { + searchField = new JTextField() { + + private static final long serialVersionUID = 1L; + + @Override + public void paint(Graphics g) { + super.paint(g); + + Graphics2D g2d = (Graphics2D) g; + g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f)); + Icon icon = IconLoader.SearchBox.getIcon(); + icon.paintIcon(searchField, g2d, searchField.getWidth() - 18, 3); + + if (searchField.getText().trim().length() == 0 && !searchField.hasFocus()) { + g2d.setColor(Color.gray); + g2d.setFont(searchField.getFont()); + g2d.drawString("Search", 4, 3 + searchField.getFont().getSize()); + } + } + }; + + searchField.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + searchField.repaint(); + } + + @Override + public void focusLost(FocusEvent e) { + searchField.repaint(); + } + }); + + searchField.getDocument().addDocumentListener(new DocumentListener() { + + public void changedUpdate(DocumentEvent e) { + process(); + } + + public void removeUpdate(DocumentEvent e) { + process(); + } + + public void insertUpdate(DocumentEvent e) { + process(); + } + + public void process() { + String text = searchField.getText(); + DefaultTreeModel model = getTreeModel(); + DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode) model.getRoot(); + for (int i = 0; i < model.getChildCount(rootNode); i++) { + int visibleCount = 0; + DefaultMutableTreeNode categoryNode = (DefaultMutableTreeNode) model.getChild(rootNode, i); + for (int j = 0; j < model.getChildCount(categoryNode); j++) { + DefaultMutableTreeNode componentNode = (DefaultMutableTreeNode) model.getChild(categoryNode, j); + Object obj = componentNode.getUserObject(); + if (obj != null && obj.getClass().equals(Payload.class)) { + Payload payload = (Payload) obj; + boolean visible = false; + if (text.trim().length() == 0) { + visible = true; + } else { + ComponentType type = payload.getComponentType(); + visible = + type.getName().toLowerCase().contains(text.toLowerCase()) + || type.getDescription().toLowerCase().contains(text.toLowerCase()) + || type.getCategory().toLowerCase().contains(text.toLowerCase()); + } + if (visible != payload.isVisible()) { + payload.setVisible(visible); + model.nodeStructureChanged(componentNode); + } + if (visible) + visibleCount++; + } + } + + Object obj = categoryNode.getUserObject(); + if (obj != null && obj.getClass().equals(Payload.class)) { + Payload payload = (Payload) obj; + boolean visible = visibleCount > 0; + if (visible != payload.isVisible()) { + payload.setVisible(visible); + model.nodeStructureChanged(rootNode); + } + if (visible && text.trim().length() > 0) { + tree.expandPath(new TreePath(categoryNode.getPath())); + } + } + } + + getTree().invalidate(); + } + }); + } + return searchField; + } + + public class ComponentCellRenderer extends DefaultTreeCellRenderer { + + private static final long serialVersionUID = 1L; + + @Override + public Component getTreeCellRendererComponent(final JTree tree, final Object value, final boolean selected, + final boolean expanded, final boolean leaf, final int row, final boolean hasFocus) { + + super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); + + Object obj = ((DefaultMutableTreeNode) value).getUserObject(); + if (obj != null && obj.getClass().equals(Payload.class)) { + Payload type = (Payload) obj; + if (type.getComponentType() == null) { + if (type.isVisible()) + setPreferredSize(new Dimension(240, 20)); + else + setPreferredSize(new Dimension(0, 0)); + } else { + setIcon(type.getComponentType().getIcon()); + if (type.isVisible()) + setPreferredSize(new Dimension(240, 32)); + else + setPreferredSize(new Dimension(0, 0)); + } + } + + return this; + } + } + + public class Payload { + private ComponentType componentType; + private String category; + private boolean isVisible; + + public Payload(ComponentType componentType) { + super(); + this.componentType = componentType; + this.isVisible = true; + } + + public Payload(String category) { + super(); + this.category = category; + this.isVisible = true; + } + + public ComponentType getComponentType() { + return componentType; + } + + public boolean isVisible() { + return isVisible; + } + + public void setVisible(boolean isVisible) { + this.isVisible = isVisible; + } + + @Override + public String toString() { + return componentType == null ? category : componentType.getName(); + } + } +}