View Javadoc

1   /*
2    *  XNap - A P2P framework and client.
3    *
4    *  See the file AUTHORS for copyright information.
5    *
6    *  This program is free software; you can redistribute it and/or modify
7    *  it under the terms of the GNU General Public License as published by
8    *  the Free Software Foundation.
9    *
10   *  This program is distributed in the hope that it will be useful,
11   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   *  GNU General Public License for more details.
14   *
15   *  You should have received a copy of the GNU General Public License
16   *  along with this program; if not, write to the Free Software
17   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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  	//--- Constant(s) ---
50  	
51  	//--- Data field(s) ---
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  	//--- Constructor(s) ---
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     //--- Method(s) ---
107 	
108 	public void enablePopup()
109 	{
110 		mode.getTextComponent().addKeyListener(keyListener);
111 		mode.getTextComponent().addFocusListener(focusListener);
112 
113 		// the model could have changed
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 		/* this breaks completion popup navigateion for the history text field,
126 		   but I guess completion + history is a bit too much anyway.  */
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 	//--- Inner class(es) ---
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 			// catch all up an down events
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 						/* object violation, maybe we should throw an event or
196                            do nothing at all.  */
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 }