diff --git a/src/main/java/de/neemann/digital/gui/Main.java b/src/main/java/de/neemann/digital/gui/Main.java index 64309499c..284fd35a3 100644 --- a/src/main/java/de/neemann/digital/gui/Main.java +++ b/src/main/java/de/neemann/digital/gui/Main.java @@ -63,6 +63,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.text.DefaultEditorKit; import java.awt.*; @@ -391,7 +393,7 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS if (treeCheckBox.isSelected()) { JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); treeModel = new LibraryTreeModel(library); - split.setLeftComponent(new JScrollPane(new SelectTree(treeModel, circuitComponent, shapeFactory, insertHistory))); + split.setLeftComponent(createTreeComponent()); split.setRightComponent(circuitScrollPanel); getContentPane().add(split); componentOnPane = split; @@ -450,6 +452,48 @@ public final class Main extends JFrame implements ClosingWindowListener.ConfirmS view.add(viewHelp.createJMenuItem()); } + private JComponent createTreeComponent() { + JPanel panel = new JPanel(new BorderLayout()); + JPanel field = new JPanel(new BorderLayout()); + JTextField textField = new SearchTextField(); + field.add(textField); + JButton clearButton = new JButton(new AbstractAction("\u2717") { + @Override + public void actionPerformed(ActionEvent actionEvent) { + textField.setText(""); + } + }); + clearButton.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5)); + field.add(clearButton, BorderLayout.EAST); + panel.add(field, BorderLayout.NORTH); + + textField.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + SelectTree tree = new SelectTree(treeModel, circuitComponent, shapeFactory, insertHistory); + textField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent documentEvent) { + changedUpdate(documentEvent); + } + + @Override + public void removeUpdate(DocumentEvent documentEvent) { + changedUpdate(documentEvent); + } + + @Override + public void changedUpdate(DocumentEvent documentEvent) { + String text = textField.getText().trim(); + if (text.isEmpty()) + treeModel = new LibraryTreeModel(library); + else + treeModel = new LibraryTreeModel(library, new TextSearchFilter(text)); + tree.setModel(treeModel); + } + }); + panel.add(new JScrollPane(tree)); + return panel; + } + private void clearPane() { circuitComponent.setCircuit(new Circuit()); setFilename(null, true); diff --git a/src/main/java/de/neemann/digital/gui/SearchTextField.java b/src/main/java/de/neemann/digital/gui/SearchTextField.java new file mode 100644 index 000000000..ee2597889 --- /dev/null +++ b/src/main/java/de/neemann/digital/gui/SearchTextField.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.gui; + +import de.neemann.digital.lang.Lang; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; + +/** + * JTextField which shows the text "search" if the field is empty. + */ +public class SearchTextField extends JTextField { + + /** + * Creates a new instance + */ + public SearchTextField() { + addFocusListener(new FocusListener() { + @Override + public void focusGained(FocusEvent e) { + repaint(); + } + + @Override + public void focusLost(FocusEvent e) { + repaint(); + } + }); + } + + @Override + public void paint(Graphics g) { + super.paint(g); + if (getText().isEmpty() && !hasFocus()) { + g.setColor(Color.GRAY); + g.drawString(Lang.get("msg_search"), 5, (getHeight() + getFont().getSize()) / 2); + } + } +} diff --git a/src/main/java/de/neemann/digital/gui/TextSearchFilter.java b/src/main/java/de/neemann/digital/gui/TextSearchFilter.java new file mode 100644 index 000000000..4a10a1c9c --- /dev/null +++ b/src/main/java/de/neemann/digital/gui/TextSearchFilter.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.gui; + +import de.neemann.digital.draw.library.LibraryNode; +import de.neemann.digital.gui.components.tree.LibraryTreeModel; + +/** + * Used to filter nodes in the tree view + */ +public class TextSearchFilter implements LibraryTreeModel.Filter { + private final String filterStr; + + /** + * Creates a new filter + * + * @param filterStr the search string + */ + public TextSearchFilter(String filterStr) { + this.filterStr = filterStr.toLowerCase(); + } + + @Override + public boolean accept(LibraryNode node) { + return node.getName().toLowerCase().contains(filterStr) + || node.getTranslatedName().toLowerCase().contains(filterStr); + } +} diff --git a/src/main/java/de/neemann/digital/gui/components/tree/LibraryTreeModel.java b/src/main/java/de/neemann/digital/gui/components/tree/LibraryTreeModel.java index 21f8edf7e..ef6e5149c 100644 --- a/src/main/java/de/neemann/digital/gui/components/tree/LibraryTreeModel.java +++ b/src/main/java/de/neemann/digital/gui/components/tree/LibraryTreeModel.java @@ -111,6 +111,13 @@ public class LibraryTreeModel implements TreeModel, LibraryListener { } } + /** + * @return true if this model is filtered + */ + public boolean isFiltered() { + return filter != null; + } + private Container getContainer(LibraryNode libraryNode) { Container c = map.get(libraryNode); if (c == null) { diff --git a/src/main/java/de/neemann/digital/gui/components/tree/SelectTree.java b/src/main/java/de/neemann/digital/gui/components/tree/SelectTree.java index 301b4a80d..bf5b96dc3 100644 --- a/src/main/java/de/neemann/digital/gui/components/tree/SelectTree.java +++ b/src/main/java/de/neemann/digital/gui/components/tree/SelectTree.java @@ -22,6 +22,7 @@ import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.IOException; +import java.util.Enumeration; /** * Tree to select items @@ -29,6 +30,7 @@ import java.io.IOException; public class SelectTree extends JTree { private final ShapeFactory shapeFactory; + private Enumeration storedExpanded; /** * Create a new instance @@ -68,6 +70,27 @@ public class SelectTree extends JTree { expandPath(new TreePath(model.getFirstLeafParent().getPath())); } + /** + * Sets a new model to this SelectTree. + * + * @param newModel the new model + */ + public void setModel(LibraryTreeModel newModel) { + LibraryTreeModel oldModel = (LibraryTreeModel) getModel(); + if (!oldModel.isFiltered() && newModel.isFiltered()) + storedExpanded = getExpandedDescendants(new TreePath(getModel().getRoot())); + + boolean restore = oldModel.isFiltered() && !newModel.isFiltered(); + + super.setModel(newModel); + if (restore && storedExpanded != null) { + while (storedExpanded.hasMoreElements()) + expandPath(storedExpanded.nextElement()); + storedExpanded = null; + } else + expandPath(new TreePath(newModel.getFirstLeafParent().getPath())); + } + @Override public String getToolTipText(MouseEvent e) { TreePath selPath = getPathForLocation(e.getX(), e.getY()); diff --git a/src/main/resources/lang/lang_de.xml b/src/main/resources/lang/lang_de.xml index 4cbc6414d..498c1aef9 100644 --- a/src/main/resources/lang/lang_de.xml +++ b/src/main/resources/lang/lang_de.xml @@ -51,7 +51,7 @@ Eingänge Ausgänge Veränderbare Attribute - + Suche diff --git a/src/main/resources/lang/lang_en.xml b/src/main/resources/lang/lang_en.xml index 52381429a..bb5b2edaf 100644 --- a/src/main/resources/lang/lang_en.xml +++ b/src/main/resources/lang/lang_en.xml @@ -51,6 +51,7 @@ Inputs Outputs Attributes + search