diff --git a/src/nt/UI/LigatureSymbols-2.11.ttf b/src/nt/UI/LigatureSymbols-2.11.ttf new file mode 100644 index 0000000..0338d4c Binary files /dev/null and b/src/nt/UI/LigatureSymbols-2.11.ttf differ diff --git a/src/nt/UI/control/JSearchBar.java b/src/nt/UI/control/JSearchBar.java index 9115c02..dc2573e 100644 --- a/src/nt/UI/control/JSearchBar.java +++ b/src/nt/UI/control/JSearchBar.java @@ -1,5 +1,116 @@ package nt.UI.control; -public class JSearchBar { +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontFormatException; +import java.awt.TextField; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.io.File; +import java.io.IOException; + +import javax.swing.DefaultListModel; +import javax.swing.JFrame; +import javax.swing.JList; +import javax.swing.JMenuItem; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.JWindow; +import javax.swing.SwingUtilities; + +import nt.UI.control.interfaces.SearchBarFinder; + +public class JSearchBar extends JSearchTextField{ + + private static final long serialVersionUID = 1L; + protected static final File SYMBOLS_FILE = null; + + protected SearchBarFinder finder; + protected SearchDropDown dropDown; + protected boolean completionActive = false; + protected String lastEntered = ""; + private final JSearchBarController controller; + + public JSearchBar(SearchBarFinder finder) { + this.finder = finder; + this.dropDown = new SearchDropDown(); + setFont(new Font("LigatureSymbols", Font.PLAIN, 13)); + + this.controller = new JSearchBarController(this); + controller.setDefaultState(); + } + + @Override + public void setVisible(boolean b) { + super.setVisible(b); + txtSearch.setText("\uE116 search"); + txtSearch.setHorizontalAlignment(JTextField.CENTER); + } + + protected void doCompletion() { + System.out.println("completion"); + String[] tokens = this.finder.getSearchResults(txtSearch.getText()); + if(tokens.length > 1) { + this.dropDown.putItems(tokens); + if(!this.dropDown.isVisible()) { + this.dropDown.setVisible(true); + } + }else { + if(this.dropDown.isVisible()) { + this.dropDown.setVisible(false); + } + } + } + protected void startCompletion() { + this.completionActive = true; + this.dropDown.setVisible(true); + } + protected void stopCompletion() { + this.completionActive = false; + this.dropDown.setVisible(false); + } + + protected class SearchDropDown extends JWindow{ + + private static final long serialVersionUID = 1L; + private static final int ROW_COUNT = 5; + + protected DefaultListModel data = new DefaultListModel<>(); + protected JList list = new JList<>(data); + protected JScrollPane scrollPane = new JScrollPane(list); + + public SearchDropDown() { + this.list.setVisibleRowCount(ROW_COUNT); + setAlwaysOnTop(true); + add(this.scrollPane); + } + + public void putItems(String[] tokens) { + this.data.clear(); + for(String token : tokens) { + this.data.addElement(token); + } + positionMenu(); + } + + public void positionMenu() { + JSearchBar parent = JSearchBar.this; + if(parent.isShowing()) { + if (this.list.getFixedCellHeight() < 0) { + this.list.setFixedCellHeight(this.getFontMetrics(this.getFont()).getHeight() + 2); + } + int x, y, width, height; + Dimension dim = parent.getSize(); + x = parent.getLocationOnScreen().x; + y = parent.getLocationOnScreen().y + dim.height; + width = dim.width; + height = this.list.getFixedCellHeight() * ROW_COUNT; + setSize(new Dimension(width, height)); + setLocation(x, y); + } + } + } + } diff --git a/src/nt/UI/control/JSearchBarController.java b/src/nt/UI/control/JSearchBarController.java new file mode 100644 index 0000000..413e6a0 --- /dev/null +++ b/src/nt/UI/control/JSearchBarController.java @@ -0,0 +1,202 @@ +package nt.UI.control; + +import java.awt.Font; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.HierarchyBoundsListener; +import java.awt.event.HierarchyEvent; +import java.awt.event.InputEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; + +import javax.swing.AbstractAction; +import javax.swing.ActionMap; +import javax.swing.InputMap; +import javax.swing.JList; +import javax.swing.JMenuItem; +import javax.swing.JTextField; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.event.AncestorEvent; +import javax.swing.event.AncestorListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import nt.UI.util.DocumentAdapter; + +public class JSearchBarController extends KeyAdapter implements DocumentListener, FocusListener, ComponentListener, HierarchyBoundsListener, ListSelectionListener{ + + private static final String COMPLETION_CANCEL_KEY = "comp.cancel"; + private static final String SELECT_KEY = "comp.select"; + + private static final KeyStroke COMPLETION_CANCEL_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); + private static final KeyStroke SELECT_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0); + + private JSearchBar searchBar; + + protected JSearchBarController(JSearchBar searchBar) { + this.searchBar = searchBar; + + searchBar.txtSearch.addFocusListener(this); + searchBar.txtSearch.addKeyListener(this); + searchBar.txtSearch.getDocument().addDocumentListener(this); + InputMap inputMap = searchBar.txtSearch.getInputMap(); + inputMap.put(COMPLETION_CANCEL_STROKE, COMPLETION_CANCEL_KEY); + + ActionMap actionMap = searchBar.txtSearch.getActionMap(); + actionMap.put(COMPLETION_CANCEL_KEY, new AbstractAction() { + private static final long serialVersionUID = 1L; + @Override + public void actionPerformed(ActionEvent e) { + setDefaultState(); + } + }); + + searchBar.txtSearch.addComponentListener(this); + searchBar.txtSearch.addHierarchyBoundsListener(this); + searchBar.addActionListener(e -> setDefaultState()); + + searchBar.dropDown.list.addListSelectionListener(this); + + } + + protected void setDefaultState() { + JTextField txt = this.searchBar.txtSearch; + this.searchBar.showCancelButton(false); + txt.setText("search"); + txt.setFont(txt.getFont().deriveFont(Font.ITALIC)); + this.searchBar.repaint(); + this.searchBar.stopCompletion(); + Window win = SwingUtilities.getWindowAncestor(searchBar); + if(win != null) { + win.requestFocus(); + } + } + protected void setEnteredState() { + JTextField txt = this.searchBar.txtSearch; + this.searchBar.showCancelButton(true); + txt.setFont(txt.getFont().deriveFont(Font.PLAIN)); + } + + @Override + public void focusGained(FocusEvent e) { + JTextField bar = this.searchBar.txtSearch; + if(bar.getText().toLowerCase().equals("search")) { + bar.setText(""); + } + setEnteredState(); + } + + @Override + public void focusLost(FocusEvent e) { + JTextField bar = this.searchBar.txtSearch; + if(bar.getText().isEmpty() || bar.getText().toLowerCase().equals("search")) { + setDefaultState(); + }else { + setEnteredState(); + } + this.searchBar.stopCompletion(); + } + + @Override + public void keyPressed(KeyEvent e) { + if(this.searchBar.completionActive) { + JList list = searchBar.dropDown.list; + int index = list.getSelectedIndex(); + if(e.getKeyCode() == KeyEvent.VK_DOWN) { + index = index >= list.getModel().getSize()? list.getModel().getSize()-1:++index; + }else if(e.getKeyCode() == KeyEvent.VK_UP) { + index = index <= -1? -1 : --index; + } + list.setSelectedIndex(index); + list.ensureIndexIsVisible(index); + } + } + + @Override + public void componentResized(ComponentEvent e) { + if(this.searchBar.completionActive) { + this.searchBar.dropDown.positionMenu(); + } + } + + @Override + public void componentMoved(ComponentEvent e) { + if(this.searchBar.completionActive) { + this.searchBar.dropDown.positionMenu(); + } + } + + @Override + public void componentShown(ComponentEvent e) { + + } + + @Override + public void componentHidden(ComponentEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void ancestorMoved(HierarchyEvent e) { + if(this.searchBar.completionActive) { + this.searchBar.stopCompletion(); + } + } + + @Override + public void ancestorResized(HierarchyEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public void valueChanged(ListSelectionEvent e) { + String val = searchBar.dropDown.list.getSelectedValue(); + if(val != null) { + JTextField field = searchBar.txtSearch; + field.setText(val); + field.setCaretPosition(field.getText().length()); + } + + } + + @Override + public void insertUpdate(DocumentEvent e) { + if(searchBar.txtSearch.getDocument().getLength() > 0) { + if(!searchBar.completionActive) { + searchBar.startCompletion(); + } + searchBar.doCompletion(); + }else { + searchBar.stopCompletion(); + } + } + + @Override + public void removeUpdate(DocumentEvent e) { + searchBar.doCompletion(); + + } + + @Override + public void changedUpdate(DocumentEvent e) { + // TODO Auto-generated method stub + + } + + +} diff --git a/src/nt/UI/control/JSearchTextField.java b/src/nt/UI/control/JSearchTextField.java new file mode 100644 index 0000000..47ec2e8 --- /dev/null +++ b/src/nt/UI/control/JSearchTextField.java @@ -0,0 +1,105 @@ +package nt.UI.control; + +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.border.EmptyBorder; +import javax.swing.border.LineBorder; +import javax.swing.text.JTextComponent; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Font; +import java.awt.event.ActionListener; + +import javax.swing.BoxLayout; +import javax.swing.JLabel; +import javax.swing.JButton; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; + +public class JSearchTextField extends JPanel{ + + + private static final long serialVersionUID = 1L; + private JLabel lblLens; + protected JTextField txtSearch; + protected JButton btnCancel; + private Component horizontalStrut; + private Component horizontalStrut_1; + + public JSearchTextField() { + super(); + setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); + + Font symbols = new Font("LigatureSymbols", Font.PLAIN, 18); + + horizontalStrut_1 = Box.createHorizontalStrut(20); + add(horizontalStrut_1); + lblLens = new JLabel("\uE116"); + lblLens.setFont(symbols); + add(lblLens); + + horizontalStrut = Box.createHorizontalStrut(10); + add(horizontalStrut); + + txtSearch = new JTextField() { + private static final long serialVersionUID = 1L; + + @Override + public void setText(String txt) { + + } + + }; + txtSearch.setText("search"); + txtSearch.setBorder(new EmptyBorder(0, 0, 0, 0)); + txtSearch.setBackground(getBackground()); + add(txtSearch); + txtSearch.setColumns(10); + + btnCancel = new JButton("\uE10f"); + btnCancel.setHorizontalAlignment(SwingConstants.RIGHT); + btnCancel.setBorderPainted(false); + btnCancel.setFont(symbols); + btnCancel.setOpaque(false); + btnCancel.setVisible(false); + add(btnCancel); + + setBorder(new LineBorder(Color.LIGHT_GRAY)); + setPreferredSize(btnCancel.getPreferredSize()); + setMinimumSize(btnCancel.getMinimumSize()); + } + + public void addActionListener(ActionListener l) { + this.btnCancel.addActionListener(l); + } + public void removeActionListener(ActionListener l) { + this.btnCancel.removeActionListener(l); + } + public void showCancelButton(boolean b) { + this.btnCancel.setVisible(b); + System.out.println(b); + revalidate(); + + } + + @Override + public void setBackground(Color bg) { + super.setBackground(bg); + for(Component c : getComponents()) { + c.setBackground(bg); + } + } + + @Override + public void setForeground(Color fg) { + super.setForeground(fg); + for(Component c : getComponents()) { + c.setForeground(fg); + } + } + + +} diff --git a/src/nt/UI/control/interfaces/SearchBarFinder.java b/src/nt/UI/control/interfaces/SearchBarFinder.java new file mode 100644 index 0000000..0189abd --- /dev/null +++ b/src/nt/UI/control/interfaces/SearchBarFinder.java @@ -0,0 +1,7 @@ +package nt.UI.control.interfaces; + +public interface SearchBarFinder { + + public String[] getSearchResults(String token); + +} diff --git a/src/nt/UI/tests/UITestMain.java b/src/nt/UI/tests/UITestMain.java new file mode 100644 index 0000000..690a0eb --- /dev/null +++ b/src/nt/UI/tests/UITestMain.java @@ -0,0 +1,71 @@ +package nt.UI.tests; + +import javax.swing.JFrame; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; +import javax.swing.plaf.metal.MetalLookAndFeel; + +import nt.UI.control.JSearchBar; +import nt.UI.control.JSearchTextField; +import nt.UI.control.interfaces.SearchBarFinder; +import javax.swing.BoxLayout; +import java.awt.Component; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JTextField; + +public class UITestMain extends JFrame{ + + private static final boolean DEBUG_LAF = false; + + public static void main(String[] args) { + if(DEBUG_LAF) { + try { + UIManager.setLookAndFeel(new MetalLookAndFeel()); + } catch (UnsupportedLookAndFeelException e) { + e.printStackTrace(); + } + } + + + UITestMain frame = new UITestMain(); + frame.setVisible(true); + } + + public UITestMain() { + setSize(400,400); + setDefaultCloseOperation(EXIT_ON_CLOSE); + getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); + getContentPane().add(new JSearchBar(new SearchBarFinder() { + + @Override + public String[] getSearchResults(String token) { + List list = Arrays.asList("a aa aaa aaaa b bb bbb bbbb bbbbb ab aabb bbaa baba".split(" ")); + LinkedList results = new LinkedList<>(); + for(String tok : list) { + if(tok.startsWith(token)) { + System.out.println(tok + " " + token); + results.add(tok); + } + } + return results.toArray(new String[0]); + } + })); + + Component verticalStrut = Box.createVerticalStrut(300); + getContentPane().add(verticalStrut); + + JButton btnBtn = new JButton("btn"); + getContentPane().add(btnBtn); + + JSearchTextField searchTextField = new JSearchTextField(); + getContentPane().add(searchTextField); + + searchTextField.requestFocus(); + } + +} diff --git a/src/nt/UI/util/DocumentAdapter.java b/src/nt/UI/util/DocumentAdapter.java new file mode 100644 index 0000000..662d512 --- /dev/null +++ b/src/nt/UI/util/DocumentAdapter.java @@ -0,0 +1,29 @@ +package nt.UI.util; + +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +public abstract class DocumentAdapter implements DocumentListener{ + + public enum DEType { + INSERT, REMOVE, CHANGE + }; + + @Override + public void insertUpdate(DocumentEvent e) { + documentChanged(e, DEType.INSERT); + } + + @Override + public void removeUpdate(DocumentEvent e) { + documentChanged(e, DEType.REMOVE); + } + + @Override + public void changedUpdate(DocumentEvent e) { + documentChanged(e, DEType.CHANGE); + } + + public abstract void documentChanged(DocumentEvent e, DEType type); + +}