1/*
2 * Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23package com.sun.swingset3.demos.textfield;
24
25import java.awt.*;
26import java.awt.event.*;
27import java.util.*;
28import java.util.List;
29import javax.swing.*;
30import javax.swing.border.LineBorder;
31import javax.swing.event.DocumentEvent;
32import javax.swing.event.DocumentListener;
33
34/**
35 * JHistoryTextField
36 *
37 * @author Pavel Porvatov
38 */
39public class JHistoryTextField extends JTextField {
40
41    private static final int MAX_VISIBLE_ROWS = 8;
42
43    private final List<String> history = new ArrayList<String>();
44
45    private final JPopupMenu popup = new JPopupMenu() {
46        @Override
47        public Dimension getPreferredSize() {
48            Dimension dimension = super.getPreferredSize();
49
50            dimension.width = JHistoryTextField.this.getWidth();
51
52            return dimension;
53        }
54    };
55
56    private final JList<String> list = new JList<>(new DefaultListModel<>());
57
58    private String userText;
59
60    private boolean notificationDenied;
61
62    public JHistoryTextField() {
63        JScrollPane scrollPane = new JScrollPane(list,
64                ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
65                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
66        scrollPane.setHorizontalScrollBar(null);
67        scrollPane.setBorder(null);
68
69        list.setFocusable(false);
70
71        popup.add(scrollPane);
72        popup.setFocusable(false);
73        popup.setBorder(new LineBorder(Color.BLACK, 1));
74
75        getDocument().addDocumentListener(new DocumentListener() {
76            @Override
77            public void insertUpdate(DocumentEvent e) {
78                onTextChanged();
79            }
80
81            @Override
82            public void removeUpdate(DocumentEvent e) {
83                onTextChanged();
84            }
85
86            @Override
87            public void changedUpdate(DocumentEvent e) {
88                onTextChanged();
89            }
90        });
91
92        list.addMouseMotionListener(new MouseAdapter() {
93            @Override
94            public void mouseMoved(MouseEvent e) {
95                int index = list.locationToIndex(e.getPoint());
96
97                if (index >= 0 && list.getSelectedIndex() != index) {
98                    list.setSelectedIndex(index);
99                }
100            }
101        });
102
103        list.addMouseListener(new MouseAdapter() {
104            @Override
105            public void mouseReleased(MouseEvent e) {
106                if (SwingUtilities.isLeftMouseButton(e)) {
107                    setTextWithoutNotification(list.getSelectedValue());
108
109                    popup.setVisible(false);
110                }
111            }
112        });
113
114        addFocusListener(new FocusAdapter() {
115            @Override
116            public void focusLost(FocusEvent e) {
117                popup.setVisible(false);
118            }
119        });
120
121        addKeyListener(new KeyAdapter() {
122            @Override
123            public void keyPressed(KeyEvent e) {
124                if (popup.isShowing()) {
125                    switch (e.getKeyCode()) {
126                        case KeyEvent.VK_UP: {
127                            changeListSelectedIndex(-1);
128
129                            break;
130                        }
131
132                        case KeyEvent.VK_PAGE_UP: {
133                            changeListSelectedIndex(-list.getVisibleRowCount());
134
135                            break;
136                        }
137
138                        case KeyEvent.VK_DOWN: {
139                            changeListSelectedIndex(1);
140
141                            break;
142                        }
143
144                        case KeyEvent.VK_PAGE_DOWN: {
145                            changeListSelectedIndex(list.getVisibleRowCount());
146
147                            break;
148                        }
149
150                        case KeyEvent.VK_ESCAPE: {
151                            popup.setVisible(false);
152
153                            setTextWithoutNotification(userText);
154
155                            break;
156                        }
157
158                        case KeyEvent.VK_ENTER:
159                        case KeyEvent.VK_LEFT:
160                        case KeyEvent.VK_RIGHT: {
161                            popup.setVisible(false);
162
163                            break;
164                        }
165                    }
166                } else if (e.getKeyCode() == KeyEvent.VK_DOWN
167                        || e.getKeyCode() == KeyEvent.VK_UP
168                        || e.getKeyCode() == KeyEvent.VK_PAGE_UP
169                        || e.getKeyCode() == KeyEvent.VK_PAGE_DOWN) {
170                    userText = getText();
171
172                    showFilteredHistory();
173                }
174            }
175        });
176    }
177
178    private void changeListSelectedIndex(int delta) {
179        int size = list.getModel().getSize();
180        int index = list.getSelectedIndex();
181
182        int newIndex;
183
184        if (index < 0) {
185            newIndex = delta > 0 ? 0 : size - 1;
186        } else {
187            newIndex = index + delta;
188        }
189
190        if (newIndex >= size || newIndex < 0) {
191            newIndex = newIndex < 0 ? 0 : size - 1;
192
193            if (index == newIndex) {
194                newIndex = -1;
195            }
196        }
197
198        if (newIndex < 0) {
199            list.getSelectionModel().clearSelection();
200            list.ensureIndexIsVisible(0);
201
202            setTextWithoutNotification(userText);
203        } else {
204            list.setSelectedIndex(newIndex);
205            list.ensureIndexIsVisible(newIndex);
206
207            setTextWithoutNotification(list.getSelectedValue());
208        }
209    }
210
211    private void setTextWithoutNotification(String text) {
212        notificationDenied = true;
213
214        try {
215            setText(text);
216        } finally {
217            notificationDenied = false;
218        }
219    }
220
221    private void onTextChanged() {
222        if (!notificationDenied) {
223            userText = getText();
224
225            showFilteredHistory();
226        }
227    }
228
229    private void showFilteredHistory() {
230        list.getSelectionModel().clearSelection();
231
232        DefaultListModel<String> model = (DefaultListModel<String>) list.getModel();
233
234        model.clear();
235
236        for (String s : history) {
237            if (s.contains(userText)) {
238                model.addElement(s);
239            }
240        }
241
242        int size = model.size();
243
244        if (size == 0) {
245            popup.setVisible(false);
246        } else {
247            list.setVisibleRowCount(size < MAX_VISIBLE_ROWS ? size : MAX_VISIBLE_ROWS);
248
249            popup.pack();
250
251            if (!popup.isShowing()) {
252                popup.show(JHistoryTextField.this, 0, getHeight());
253            }
254        }
255    }
256
257    public List<String> getHistory() {
258        return Collections.unmodifiableList(history);
259    }
260
261    public void setHistory(List<? extends String> history) {
262        this.history.clear();
263        this.history.addAll(history);
264    }
265}
266