1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.xnap.commons.gui.completion;
21
22 import java.awt.ComponentOrientation;
23 import java.awt.Dimension;
24 import java.awt.event.ComponentAdapter;
25 import java.awt.event.ComponentEvent;
26 import java.awt.event.ComponentListener;
27 import java.awt.event.FocusAdapter;
28 import java.awt.event.FocusEvent;
29 import java.awt.event.FocusListener;
30 import java.awt.event.KeyAdapter;
31 import java.awt.event.KeyEvent;
32 import java.awt.event.KeyListener;
33 import java.awt.event.MouseAdapter;
34 import java.awt.event.MouseEvent;
35 import java.beans.PropertyChangeEvent;
36 import java.beans.PropertyChangeListener;
37 import javax.swing.JList;
38 import javax.swing.JPopupMenu;
39 import javax.swing.JScrollPane;
40 import javax.swing.JTextField;
41 import javax.swing.ListSelectionModel;
42 import javax.swing.text.JTextComponent;
43 import org.apache.commons.logging.Log;
44 import org.apache.commons.logging.LogFactory;
45
46 /***
47 * This class presents completed objects in a popup menu below a {@link
48 * JTextComponent}.
49 *
50 * @author Felix Berger
51 */
52 public class CompletionPopup extends JPopupMenu
53 {
54
55 /***
56 * The completion mode handling when and how to complete.
57 */
58 private Completion completion;
59 /***
60 * The list presenting the completed items.
61 */
62 private JList list = new JList();
63
64 private KeyListener keyListener = new KeyHandler();
65
66 private FocusListener focusListener = new FocusHandler();
67
68 private ComponentListener componentListener = new SizeHandler();
69
70 private PropertyChangeListener propertyListener = new PropertyChangeHandler();
71
72 private static Log logger = LogFactory.getLog(CompletionPopup.class);
73
74
75 /***
76 * Constructs a new completion popup.
77 */
78 public CompletionPopup()
79 {
80 list.setVisibleRowCount(4);
81 list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
82 JScrollPane jsp = new JScrollPane(list);
83 add(jsp);
84
85 list.setFocusable(false);
86 jsp.setFocusable(false);
87 setFocusable(false);
88
89 list.addMouseListener(new MouseHandler());
90 }
91
92
93 public void enablePopup(Completion comp)
94 {
95 completion = comp;
96
97 applyComponentOrientation(completion.getTextComponent().getComponentOrientation());
98
99 completion.getTextComponent().addKeyListener(keyListener);
100 completion.getTextComponent().addFocusListener(focusListener);
101 completion.getTextComponent().addPropertyChangeListener
102 ("componentOrientation", propertyListener);
103
104
105 list.setModel(completion.getModel());
106
107 if (completion.isWholeTextCompletion()) {
108 setPreferredSize(new Dimension(completion.getTextComponent().getWidth(),
109 getPreferredSize().height));
110 completion.getTextComponent().addComponentListener(componentListener);
111 }
112 }
113
114 public void disablePopup()
115 {
116
117
118 completion.getTextComponent().removeKeyListener(keyListener);
119 completion.getTextComponent().removeFocusListener(focusListener);
120 completion.getTextComponent().removePropertyChangeListener
121 ("componentOrientation", propertyListener);
122 if (completion.isWholeTextCompletion()) {
123 completion.getTextComponent().removeComponentListener(componentListener);
124 }
125 }
126
127 private void selectNextCompletion()
128 {
129 if (completion.getModel().getSize() > 0) {
130 int cur = (list.getSelectedIndex() + 1) % completion.getModel().getSize();
131 list.setSelectedIndex(cur);
132 list.ensureIndexIsVisible(cur);
133 }
134 }
135
136 private void selectPreviousCompletion()
137 {
138 if (completion.getModel().getSize() > 0) {
139 int cur = (list.getSelectedIndex() == -1) ? 0
140 : list.getSelectedIndex();
141 cur = (cur == 0) ? completion.getModel().getSize() - 1 : cur - 1;
142 list.setSelectedIndex(cur);
143 list.ensureIndexIsVisible(cur);
144 }
145 }
146
147 /***
148 * Accessor for testing.
149 */
150 JList getList()
151 {
152 return list;
153 }
154
155 /***
156 * Listens for size changes of the text component and updates the popup's
157 * width appropriately.
158 *
159 * This makes only sense for {@link JTextField}s.
160 */
161 public class SizeHandler extends ComponentAdapter
162 {
163 public void componentResized(ComponentEvent e)
164 {
165 setPreferredSize
166 (new Dimension(completion.getTextComponent().getWidth(),
167 getPreferredSize().height));
168 }
169 }
170
171 /***
172 * Listens for key events in the text component responsible for navigation
173 * and confirmation.
174 */
175 private class KeyHandler extends KeyAdapter
176 {
177 public void keyPressed(KeyEvent e)
178 {
179 int code = e.getKeyCode();
180 int modifiers = e.getModifiers();
181
182
183 if (CompletionPopup.this.isVisible()) {
184 if (code == KeyEvent.VK_DOWN) {
185 selectNextCompletion();
186 e.consume();
187 }
188 else if (code == KeyEvent.VK_UP) {
189 selectPreviousCompletion();
190 e.consume();
191 }
192 else if (code == KeyEvent.VK_ENTER) {
193 if (list.getSelectedValue() != null) {
194
195
196 completion.setText(list.getSelectedValue().toString());
197 e.consume();
198 }
199 CompletionPopup.this.setVisible(false);
200 }
201 else if (code == KeyEvent.VK_ESCAPE) {
202 CompletionPopup.this.setVisible(false);
203 e.consume();
204 }
205 }
206 }
207 }
208
209 /***
210 * Listens for the loss of focus and hides the popup if necessary.
211 */
212 private class FocusHandler extends FocusAdapter
213 {
214 public void focusLost(FocusEvent e)
215 {
216 if (!e.isTemporary()) {
217 setVisible(false);
218 }
219 }
220 }
221
222 /***
223 * Listens for click events in the completion list to update the text
224 * component's text appropriately.
225 */
226 private class MouseHandler extends MouseAdapter
227 {
228 public void mouseClicked(MouseEvent e)
229 {
230 completion.setText(list.getSelectedValue().toString());
231 CompletionPopup.this.setVisible(false);
232 }
233 }
234
235 private class PropertyChangeHandler implements PropertyChangeListener
236 {
237
238 public void propertyChange(PropertyChangeEvent evt)
239 {
240 ComponentOrientation o = (ComponentOrientation)evt.getNewValue();
241 applyComponentOrientation(o);
242 }
243
244 }
245 }