Source code: org/gjt/sp/jedit/gui/HistoryTextField.java
1 /*
2 * HistoryTextField.java - Text field with a history
3 * :tabSize=8:indentSize=8:noTabs=false:
4 * :folding=explicit:collapseFolds=1:
5 *
6 * Copyright (C) 1999, 2000, 2001 Slava Pestov
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 */
22
23 package org.gjt.sp.jedit.gui;
24
25 //{{{ Imports
26 import javax.swing.*;
27 import javax.swing.border.Border;
28 import javax.swing.border.AbstractBorder;
29 import javax.swing.border.CompoundBorder;
30 import javax.swing.event.MouseInputAdapter;
31 import java.awt.*;
32 import java.awt.event.*;
33 import org.gjt.sp.jedit.*;
34 //}}}
35
36 /**
37 * Text field with an arrow-key accessable history.
38 * @author Slava Pestov
39 * @version $Id: HistoryTextField.java,v 1.11 2003/11/12 00:24:11 spestov Exp $
40 */
41 public class HistoryTextField extends JTextField
42 {
43 //{{{ HistoryTextField constructor
44 /**
45 * Creates a new history text field.
46 * @since jEdit 3.2pre5
47 */
48 public HistoryTextField()
49 {
50 this(null);
51 } //}}}
52
53 //{{{ HistoryTextField constructor
54 /**
55 * Creates a new history text field.
56 * @param name The history model name
57 */
58 public HistoryTextField(String name)
59 {
60 this(name,false,true);
61 } //}}}
62
63 //{{{ HistoryTextField constructor
64 /**
65 * Creates a new history text field.
66 * @param name The history model name
67 * @param instantPopup If true, selecting a value from the history
68 * popup will immediately fire an ActionEvent. If false, the user
69 * will have to press 'Enter' first
70 *
71 * @since jEdit 2.2pre5
72 */
73 public HistoryTextField(String name, boolean instantPopups)
74 {
75 this(name,instantPopups,true);
76 } //}}}
77
78 //{{{ HistoryTextField constructor
79 /**
80 * Creates a new history text field.
81 * @param name The history model name
82 * @param instantPopups If true, selecting a value from the history
83 * popup will immediately fire an ActionEvent. If false, the user
84 * will have to press 'Enter' first
85 * @param enterAddsToHistory If true, pressing the Enter key will
86 * automatically add the currently entered text to the history.
87 *
88 * @since jEdit 2.6pre5
89 */
90 public HistoryTextField(String name, boolean instantPopups,
91 boolean enterAddsToHistory)
92 {
93 setModel(name);
94
95 MouseHandler mouseHandler = new MouseHandler();
96 addMouseListener(mouseHandler);
97 addMouseMotionListener(mouseHandler);
98
99 this.instantPopups = instantPopups;
100 this.enterAddsToHistory = enterAddsToHistory;
101
102 index = -1;
103 } //}}}
104
105 //{{{ setInstantPopups() method
106 /**
107 * Sets if selecting a value from the popup should immediately fire
108 * an ActionEvent.
109 * @since jEdit 4.0pre3
110 */
111 public void setInstantPopups(boolean instantPopups)
112 {
113 this.instantPopups = instantPopups;
114 } //}}}
115
116 //{{{ getInstantPopups() method
117 /**
118 * Returns if selecting a value from the popup should immediately fire
119 * an ActionEvent.
120 * @since jEdit 4.0pre3
121 */
122 public boolean getInstantPopups()
123 {
124 return instantPopups;
125 } //}}}
126
127 //{{{ setEnterAddsToHistory() method
128 /**
129 * Sets if pressing Enter should automatically add the currently
130 * entered text to the history.
131 * @since jEdit 4.0pre3
132 */
133 public void setEnterAddsToHistory(boolean enterAddsToHistory)
134 {
135 this.enterAddsToHistory = enterAddsToHistory;
136 } //}}}
137
138 //{{{ getEnterAddsToHistory() method
139 /**
140 * Returns if pressing Enter should automatically add the currently
141 * entered text to the history.
142 * @since jEdit 4.0pre3
143 */
144 public boolean setEnterAddsToHistory()
145 {
146 return enterAddsToHistory;
147 } //}}}
148
149 //{{{ setSelectAllOnFocus() method
150 /**
151 * Sets if all text should be selected when the field gets focus.
152 * @since jEdit 4.0pre3
153 */
154 public void setSelectAllOnFocus(boolean selectAllOnFocus)
155 {
156 this.selectAllOnFocus = selectAllOnFocus;
157 } //}}}
158
159 //{{{ getSelectAllOnFocus() method
160 /**
161 * Returns if all text should be selected when the field gets focus.
162 * @since jEdit 4.0pre3
163 */
164 public boolean setSelectAllOnFocus()
165 {
166 return selectAllOnFocus;
167 } //}}}
168
169 //{{{ getModel() method
170 /**
171 * Returns the underlying history model.
172 */
173 public HistoryModel getModel()
174 {
175 return historyModel;
176 } //}}}
177
178 //{{{ setModel() method
179 /**
180 * Sets the history list model.
181 * @param name The model name
182 * @since jEdit 2.3pre3
183 */
184 public void setModel(String name)
185 {
186 Border textFieldBorder = UIManager.getBorder("TextField.border");
187
188 if(name == null)
189 {
190 historyModel = null;
191 if(textFieldBorder != null)
192 setBorder(textFieldBorder);
193 }
194 else
195 {
196 historyModel = HistoryModel.getModel(name);
197 if(textFieldBorder != null)
198 {
199 setBorder(new CompoundBorder(textFieldBorder,
200 new HistoryBorder()));
201 }
202 }
203 index = -1;
204 repaint();
205 } //}}}
206
207 //{{{ addCurrentToHistory() method
208 /**
209 * Adds the currently entered item to the history.
210 */
211 public void addCurrentToHistory()
212 {
213 if(historyModel != null)
214 historyModel.addItem(getText());
215 index = 0;
216 } //}}}
217
218 //{{{ setText() method
219 /**
220 * Sets the displayed text.
221 */
222 public void setText(String text)
223 {
224 super.setText(text);
225 index = -1;
226 } //}}}
227
228 //{{{ fireActionPerformed() method
229 /**
230 * Fires an action event to all listeners. This is public so
231 * that inner classes can access it.
232 */
233 public void fireActionPerformed()
234 {
235 super.fireActionPerformed();
236 } //}}}
237
238 //{{{ Protected members
239
240 //{{{ processKeyEvent() method
241 protected void processKeyEvent(KeyEvent evt)
242 {
243 if(!isEnabled())
244 return;
245
246 /*evt = KeyEventWorkaround.processKeyEvent(evt);
247 if(evt == null)
248 return;*/
249
250 if(evt.getID() == KeyEvent.KEY_PRESSED)
251 {
252 if(evt.getKeyCode() == KeyEvent.VK_ENTER)
253 {
254 if(enterAddsToHistory)
255 addCurrentToHistory();
256
257 if(evt.getModifiers() == 0)
258 {
259 fireActionPerformed();
260 evt.consume();
261 }
262 }
263 else if(evt.getKeyCode() == KeyEvent.VK_UP)
264 {
265 if(evt.isShiftDown())
266 doBackwardSearch();
267 else
268 historyPrevious();
269 evt.consume();
270 }
271 else if(evt.getKeyCode() == KeyEvent.VK_DOWN)
272 {
273 if(evt.isShiftDown())
274 doForwardSearch();
275 else
276 historyNext();
277 evt.consume();
278 }
279 else if(evt.getKeyCode() == KeyEvent.VK_TAB
280 && evt.isControlDown())
281 {
282 doBackwardSearch();
283 evt.consume();
284 }
285 }
286
287 if(!evt.isConsumed())
288 super.processKeyEvent(evt);
289 } //}}}
290
291 //{{{ processMouseEvent() method
292 protected void processMouseEvent(MouseEvent evt)
293 {
294 if(!isEnabled())
295 return;
296
297 switch(evt.getID())
298 {
299 case MouseEvent.MOUSE_PRESSED:
300 Border border = getBorder();
301 Insets insets = border.getBorderInsets(HistoryTextField.this);
302
303 if(evt.getX() >= getWidth() - insets.right
304 || GUIUtilities.isPopupTrigger(evt))
305 {
306 if(evt.isShiftDown())
307 showPopupMenu(getText().substring(0,
308 getSelectionStart()),0,getHeight());
309 else
310 showPopupMenu("",0,getHeight());
311 }
312 else
313 super.processMouseEvent(evt);
314
315 break;
316 case MouseEvent.MOUSE_EXITED:
317 setCursor(Cursor.getDefaultCursor());
318 super.processMouseEvent(evt);
319 break;
320 default:
321 super.processMouseEvent(evt);
322 break;
323 }
324 } //}}}
325
326 //}}}
327
328 //{{{ Private members
329
330 //{{{ Instance variables
331 private HistoryModel historyModel;
332 private JPopupMenu popup;
333 private boolean instantPopups;
334 private boolean enterAddsToHistory;
335 private boolean selectAllOnFocus;
336 private String current;
337 private int index;
338 //}}}
339
340 //{{{ doBackwardSearch() method
341 private void doBackwardSearch()
342 {
343 if(historyModel == null)
344 return;
345
346 if(getSelectionEnd() != getDocument().getLength())
347 {
348 setCaretPosition(getDocument().getLength());
349 }
350
351 String text = getText().substring(0,getSelectionStart());
352 if(text == null)
353 {
354 historyPrevious();
355 return;
356 }
357
358 for(int i = index + 1; i < historyModel.getSize(); i++)
359 {
360 String item = historyModel.getItem(i);
361 if(item.startsWith(text))
362 {
363 replaceSelection(item.substring(text.length()));
364 select(text.length(),getDocument().getLength());
365 index = i;
366 return;
367 }
368 }
369
370 getToolkit().beep();
371 } //}}}
372
373 //{{{ doForwardSearch() method
374 private void doForwardSearch()
375 {
376 if(historyModel == null)
377 return;
378
379 if(getSelectionEnd() != getDocument().getLength())
380 {
381 setCaretPosition(getDocument().getLength());
382 }
383
384 String text = getText().substring(0,getSelectionStart());
385 if(text == null)
386 {
387 historyNext();
388 return;
389 }
390
391 for(int i = index - 1; i >= 0; i--)
392 {
393 String item = historyModel.getItem(i);
394 if(item.startsWith(text))
395 {
396 replaceSelection(item.substring(text.length()));
397 select(text.length(),getDocument().getLength());
398 index = i;
399 return;
400 }
401 }
402
403 getToolkit().beep();
404 } //}}}
405
406 //{{{ historyPrevious() method
407 private void historyPrevious()
408 {
409 if(historyModel == null)
410 return;
411
412 if(index == historyModel.getSize() - 1)
413 getToolkit().beep();
414 else if(index == -1)
415 {
416 current = getText();
417 setText(historyModel.getItem(0));
418 index = 0;
419 }
420 else
421 {
422 // have to do this because setText() sets index to -1
423 int newIndex = index + 1;
424 setText(historyModel.getItem(newIndex));
425 index = newIndex;
426 }
427 } //}}}
428
429 //{{{ historyNext() method
430 private void historyNext()
431 {
432 if(historyModel == null)
433 return;
434
435 if(index == -1)
436 getToolkit().beep();
437 else if(index == 0)
438 setText(current);
439 else
440 {
441 // have to do this because setText() sets index to -1
442 int newIndex = index - 1;
443 setText(historyModel.getItem(newIndex));
444 index = newIndex;
445 }
446 } //}}}
447
448 //{{{ showPopupMenu() method
449 private void showPopupMenu(String text, int x, int y)
450 {
451 if(historyModel == null)
452 return;
453
454 requestFocus();
455
456 if(popup != null && popup.isVisible())
457 {
458 popup.setVisible(false);
459 return;
460 }
461
462 ActionHandler actionListener = new ActionHandler();
463
464 popup = new JPopupMenu();
465 JMenuItem caption = new JMenuItem(jEdit.getProperty(
466 "history.caption"));
467 caption.getModel().setEnabled(false);
468 popup.add(caption);
469 popup.addSeparator();
470
471 for(int i = 0; i < historyModel.getSize(); i++)
472 {
473 String item = historyModel.getItem(i);
474 if(item.startsWith(text))
475 {
476 JMenuItem menuItem = new JMenuItem(item);
477 menuItem.setActionCommand(String.valueOf(i));
478 menuItem.addActionListener(actionListener);
479 popup.add(menuItem);
480 }
481 }
482
483 GUIUtilities.showPopupMenu(popup,this,x,y,false);
484 } //}}}
485
486 //}}}
487
488 //{{{ Inner classes
489
490 //{{{ ActionHandler class
491 class ActionHandler implements ActionListener
492 {
493 public void actionPerformed(ActionEvent evt)
494 {
495 int ind = Integer.parseInt(evt.getActionCommand());
496 if(ind == -1)
497 {
498 if(index != -1)
499 setText(current);
500 }
501 else
502 {
503 setText(historyModel.getItem(ind));
504 index = ind;
505 }
506 if(instantPopups)
507 {
508 addCurrentToHistory();
509 fireActionPerformed();
510 }
511 }
512 } //}}}
513
514 //{{{ MouseHandler class
515 class MouseHandler extends MouseInputAdapter
516 {
517 boolean selectAll;
518
519 //{{{ mousePressed() method
520 public void mousePressed(MouseEvent evt)
521 {
522 selectAll = (!hasFocus() && selectAllOnFocus);
523 } //}}}
524
525 //{{{ mouseReleased() method
526 public void mouseReleased(MouseEvent evt)
527 {
528 SwingUtilities.invokeLater(new Runnable()
529 {
530 public void run()
531 {
532 if(selectAll)
533 selectAll();
534 }
535 });
536 } //}}}
537
538 //{{{ mouseMoved() method
539 public void mouseMoved(MouseEvent evt)
540 {
541 Border border = getBorder();
542 Insets insets = border.getBorderInsets(HistoryTextField.this);
543
544 if(evt.getX() >= getWidth() - insets.right)
545 setCursor(Cursor.getDefaultCursor());
546 else
547 setCursor(Cursor.getPredefinedCursor(
548 Cursor.TEXT_CURSOR));
549 } //}}}
550
551 //{{{ mouseDragged() method
552 public void mouseDragged(MouseEvent evt)
553 {
554 selectAll = false;
555 } //}}}
556 } //}}}
557
558 //{{{ HistoryBorder class
559 static class HistoryBorder extends AbstractBorder
560 {
561 static final int WIDTH = 16;
562
563 public void paintBorder(Component c, Graphics g,
564 int x, int y, int w, int h)
565 {
566 g.translate(x+w-WIDTH,y-1);
567
568 //if(c.isEnabled())
569 //{
570 // // vertical separation line
571 // g.setColor(UIManager.getColor("controlDkShadow"));
572 // g.drawLine(0,0,0,h);
573 //}
574
575 // down arrow
576 int w2 = WIDTH/2;
577 int h2 = h/2;
578 g.setColor(UIManager.getColor(c.isEnabled()
579 && ((HistoryTextField)c).getModel() != null
580 ? "TextField.foreground" : "TextField.disabledForeground"));
581 g.drawLine(w2-5,h2-2,w2+4,h2-2);
582 g.drawLine(w2-4,h2-1,w2+3,h2-1);
583 g.drawLine(w2-3,h2 ,w2+2,h2 );
584 g.drawLine(w2-2,h2+1,w2+1,h2+1);
585 g.drawLine(w2-1,h2+2,w2 ,h2+2);
586
587 g.translate(-(x+w-WIDTH),-(y-1));
588 }
589
590 public Insets getBorderInsets(Component c)
591 {
592 return new Insets(0,0,0,WIDTH);
593 }
594 } //}}}
595
596 //}}}
597 }