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