Source code: org/gjt/sp/jedit/gui/ActionBar.java
1 /*
2 * ActionBar.java - For invoking actions directly
3 * :tabSize=8:indentSize=8:noTabs=false:
4 * :folding=explicit:collapseFolds=1:
5 *
6 * Copyright (C) 2003 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 bsh.NameSpace;
27 import java.awt.event.*;
28 import java.awt.*;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import javax.swing.event.*;
32 import javax.swing.*;
33 import org.gjt.sp.jedit.*;
34 //}}}
35
36 /**
37 * Action invocation bar.
38 */
39 public class ActionBar extends JPanel
40 {
41 //{{{ ActionBar constructor
42 public ActionBar(final View view, boolean temp)
43 {
44 setLayout(new BoxLayout(this,BoxLayout.X_AXIS));
45
46 this.view = view;
47 this.temp = temp;
48
49 add(Box.createHorizontalStrut(2));
50
51 JLabel label = new JLabel(jEdit.getProperty("view.action.prompt"));
52 add(label);
53 add(Box.createHorizontalStrut(12));
54 add(action = new ActionTextField());
55 action.setEnterAddsToHistory(false);
56 Dimension max = action.getPreferredSize();
57 max.width = Integer.MAX_VALUE;
58 action.setMaximumSize(max);
59 action.addActionListener(new ActionHandler());
60 action.getDocument().addDocumentListener(new DocumentHandler());
61
62 if(temp)
63 {
64 close = new RolloverButton(GUIUtilities.loadIcon("closebox.gif"));
65 close.addActionListener(new ActionHandler());
66 close.setToolTipText(jEdit.getProperty(
67 "view.action.close-tooltip"));
68 add(close);
69 }
70
71 // if 'temp' is true, hide search bar after user is done with it
72 this.temp = temp;
73 } //}}}
74
75 //{{{ getField() method
76 public HistoryTextField getField()
77 {
78 return action;
79 } //}}}
80
81 //{{{ goToActionBar() method
82 public void goToActionBar()
83 {
84 repeatCount = view.getInputHandler().getRepeatCount();
85 action.setText(null);
86 action.requestFocus();
87 } //}}}
88
89 //{{{ actionListChanged()
90 /**
91 * Called when plugins are added or removed to notify the action bar
92 * that the action list has changed.
93 * @since jEdit 4.2pre2
94 */
95 public void actionListChanged()
96 {
97 actions = null;
98 } //}}}
99
100 //{{{ Private members
101
102 private static NameSpace namespace = new NameSpace(
103 BeanShell.getNameSpace(),"action bar namespace");
104
105 //{{{ Instance variables
106 private View view;
107 private boolean temp;
108 private int repeatCount;
109 private HistoryTextField action;
110 private CompletionPopup popup;
111 private RolloverButton close;
112 private String[] actions;
113 //}}}
114
115 //{{{ initActions() method
116 private void initActions()
117 {
118 if(actions != null)
119 return;
120
121 actions = jEdit.getActionNames();
122 Arrays.sort(actions,new MiscUtilities.StringICaseCompare());
123 } //}}}
124
125 //{{{ invoke() method
126 private void invoke()
127 {
128 String cmd;
129 if(popup != null)
130 cmd = popup.list.getSelectedValue().toString();
131 else
132 {
133 cmd = action.getText().trim();
134 int index = cmd.indexOf('=');
135 if(index != -1)
136 {
137 action.addCurrentToHistory();
138 String propName = cmd.substring(0,index).trim();
139 String propValue = cmd.substring(index + 1).trim();
140 String code;
141 /* construct a BeanShell snippet instead of
142 * invoking directly so that user can record
143 * property changes in macros. */
144 if(propName.startsWith("buffer."))
145 {
146 if(propName.equals("buffer.mode"))
147 {
148 code = "buffer.setMode(\""
149 + MiscUtilities.charsToEscapes(
150 propValue) + "\");";
151 }
152 else
153 {
154 code = "buffer.setStringProperty(\""
155 + MiscUtilities.charsToEscapes(
156 propName.substring("buffer.".length())
157 ) + "\",\""
158 + MiscUtilities.charsToEscapes(
159 propValue) + "\");";
160 }
161
162 code = code + "\nbuffer.propertiesChanged();";
163 }
164 else if(propName.startsWith("!buffer."))
165 {
166 code = "jEdit.setProperty(\""
167 + MiscUtilities.charsToEscapes(
168 propName.substring(1)) + "\",\""
169 + MiscUtilities.charsToEscapes(
170 propValue) + "\");\n"
171 + "jEdit.propertiesChanged();";
172 }
173 else
174 {
175 code = "jEdit.setProperty(\""
176 + MiscUtilities.charsToEscapes(
177 propName) + "\",\""
178 + MiscUtilities.charsToEscapes(
179 propValue) + "\");\n"
180 + "jEdit.propertiesChanged();"
181 + "EditBus.send(new DockableWindowUpdate(wm,"
182 + "DockableWindowUpdate."
183 + "PROPERTIES_CHANGED,null));";
184 }
185
186 Macros.Recorder recorder = view.getMacroRecorder();
187 if(recorder != null)
188 recorder.record(code);
189 BeanShell.eval(view,namespace,code);
190 cmd = null;
191 }
192 else if(cmd.length() != 0)
193 {
194 String[] completions = getCompletions(cmd);
195 if(completions.length != 0)
196 {
197 cmd = completions[0];
198 }
199 }
200 else
201 cmd = null;
202 }
203
204 if(popup != null)
205 {
206 popup.dispose();
207 popup = null;
208 }
209
210 final String finalCmd = cmd;
211 final EditAction act = (finalCmd == null ? null : jEdit.getAction(finalCmd));
212 if(temp)
213 view.removeToolBar(ActionBar.this);
214
215 SwingUtilities.invokeLater(new Runnable()
216 {
217 public void run()
218 {
219 view.getTextArea().requestFocus();
220 if(act == null)
221 {
222 if(finalCmd != null)
223 {
224 view.getStatus().setMessageAndClear(
225 jEdit.getProperty(
226 "view.action.no-completions"));
227 }
228 }
229 else
230 {
231 view.getInputHandler().setRepeatCount(repeatCount);
232 view.getInputHandler().invokeAction(act);
233 }
234 }
235 });
236 } //}}}
237
238 //{{{ getCompletions() method
239 private String[] getCompletions(String str)
240 {
241 initActions();
242
243 str = str.toLowerCase();
244 ArrayList returnValue = new ArrayList(actions.length);
245 for(int i = 0; i < actions.length; i++)
246 {
247 if(actions[i].toLowerCase().indexOf(str) != -1)
248 returnValue.add(actions[i]);
249 }
250
251 return (String[])returnValue.toArray(new String[returnValue.size()]);
252 } //}}}
253
254 //{{{ complete() method
255 private void complete(boolean insertLongestPrefix)
256 {
257 String text = action.getText().trim();
258 String[] completions = getCompletions(text);
259 if(completions.length == 1)
260 {
261 if(insertLongestPrefix)
262 action.setText(completions[0]);
263 }
264 else if(completions.length != 0)
265 {
266 if(insertLongestPrefix)
267 {
268 String prefix = MiscUtilities.getLongestPrefix(
269 completions,true);
270 if(prefix.indexOf(text) != -1)
271 action.setText(prefix);
272 }
273
274 if(popup != null)
275 popup.setModel(completions);
276 else
277 popup = new CompletionPopup(completions);
278 return;
279 }
280
281 if(popup != null)
282 {
283 popup.dispose();
284 popup = null;
285 }
286 } //}}}
287
288 //}}}
289
290 //{{{ Inner classes
291
292 //{{{ ActionHandler class
293 class ActionHandler implements ActionListener
294 {
295 public void actionPerformed(ActionEvent evt)
296 {
297 if(evt.getSource() == close)
298 view.removeToolBar(ActionBar.this);
299 else
300 invoke();
301 }
302 } //}}}
303
304 //{{{ DocumentHandler class
305 class DocumentHandler implements DocumentListener
306 {
307 //{{{ insertUpdate() method
308 public void insertUpdate(DocumentEvent evt)
309 {
310 if(popup != null)
311 complete(false);
312 } //}}}
313
314 //{{{ removeUpdate() method
315 public void removeUpdate(DocumentEvent evt)
316 {
317 if(popup != null)
318 complete(false);
319 } //}}}
320
321 //{{{ changedUpdate() method
322 public void changedUpdate(DocumentEvent evt) {}
323 //}}}
324 } //}}}
325
326 //{{{ ActionTextField class
327 class ActionTextField extends HistoryTextField
328 {
329 boolean repeat;
330 boolean nonDigit;
331
332 ActionTextField()
333 {
334 super("action");
335 setSelectAllOnFocus(true);
336 }
337
338 public boolean isManagingFocus()
339 {
340 return false;
341 }
342
343 public boolean getFocusTraversalKeysEnabled()
344 {
345 return false;
346 }
347
348 public void processKeyEvent(KeyEvent evt)
349 {
350 evt = KeyEventWorkaround.processKeyEvent(evt);
351 if(evt == null)
352 return;
353
354 switch(evt.getID())
355 {
356 case KeyEvent.KEY_TYPED:
357 char ch = evt.getKeyChar();
358 if(!nonDigit && Character.isDigit(ch))
359 {
360 super.processKeyEvent(evt);
361 repeat = true;
362 repeatCount = Integer.parseInt(action.getText());
363 }
364 else
365 {
366 nonDigit = true;
367 if(repeat)
368 {
369 passToView(evt);
370 }
371 else
372 super.processKeyEvent(evt);
373 }
374 break;
375 case KeyEvent.KEY_PRESSED:
376 int keyCode = evt.getKeyCode();
377 if(evt.isActionKey()
378 || evt.isControlDown()
379 || evt.isAltDown()
380 || evt.isMetaDown()
381 || keyCode == KeyEvent.VK_BACK_SPACE
382 || keyCode == KeyEvent.VK_DELETE
383 || keyCode == KeyEvent.VK_ENTER
384 || keyCode == KeyEvent.VK_TAB
385 || keyCode == KeyEvent.VK_ESCAPE)
386 {
387 nonDigit = true;
388 if(repeat)
389 {
390 passToView(evt);
391 break;
392 }
393 else if(keyCode == KeyEvent.VK_TAB)
394 {
395 complete(true);
396 evt.consume();
397 }
398 else if(keyCode == KeyEvent.VK_ESCAPE)
399 {
400 evt.consume();
401 if(popup != null)
402 {
403 popup.dispose();
404 popup = null;
405 action.requestFocus();
406 }
407 else
408 {
409 if(temp)
410 view.removeToolBar(ActionBar.this);
411 view.getEditPane().focusOnTextArea();
412 }
413 break;
414 }
415 else if((keyCode == KeyEvent.VK_UP
416 || keyCode == KeyEvent.VK_DOWN)
417 && popup != null)
418 {
419 popup.list.processKeyEvent(evt);
420 break;
421 }
422 }
423 super.processKeyEvent(evt);
424 break;
425 }
426 }
427
428 private void passToView(final KeyEvent evt)
429 {
430 if(temp)
431 view.removeToolBar(ActionBar.this);
432 view.getTextArea().requestFocus();
433 SwingUtilities.invokeLater(new Runnable()
434 {
435 public void run()
436 {
437 view.getTextArea().requestFocus();
438 view.getInputHandler().setRepeatCount(repeatCount);
439 view.processKeyEvent(evt,
440 View.ACTION_BAR);
441 }
442 });
443 }
444
445 public void addNotify()
446 {
447 super.addNotify();
448 repeat = nonDigit = false;
449 }
450 } //}}}
451
452 //{{{ CompletionPopup class
453 class CompletionPopup extends JWindow
454 {
455 CompletionList list;
456
457 //{{{ CompletionPopup constructor
458 CompletionPopup(String[] actions)
459 {
460 super(view);
461
462 setContentPane(new JPanel(new BorderLayout())
463 {
464 /**
465 * Returns if this component can be traversed by pressing the
466 * Tab key. This returns false.
467 */
468 public boolean isManagingFocus()
469 {
470 return false;
471 }
472
473 /**
474 * Makes the tab key work in Java 1.4.
475 */
476 public boolean getFocusTraversalKeysEnabled()
477 {
478 return false;
479 }
480 });
481
482 list = new CompletionList(actions);
483 list.setVisibleRowCount(8);
484 list.addMouseListener(new MouseHandler());
485 list.setSelectedIndex(0);
486 list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
487
488 // stupid scrollbar policy is an attempt to work around
489 // bugs people have been seeing with IBM's JDK -- 7 Sep 2000
490 JScrollPane scroller = new JScrollPane(list,
491 JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
492 JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
493
494 getContentPane().add(scroller, BorderLayout.CENTER);
495
496 GUIUtilities.requestFocus(this,list);
497
498 pack();
499 Point p = new Point(0,-getHeight());
500 SwingUtilities.convertPointToScreen(p,action);
501 setLocation(p);
502 show();
503
504 KeyHandler keyHandler = new KeyHandler();
505 addKeyListener(keyHandler);
506 list.addKeyListener(keyHandler);
507 } //}}}
508
509 //{{{ setModel() method
510 void setModel(String[] actions)
511 {
512 list.setListData(actions);
513 list.setSelectedIndex(0);
514 } //}}}
515
516 //{{{ MouseHandler class
517 class MouseHandler extends MouseAdapter
518 {
519 public void mouseClicked(MouseEvent evt)
520 {
521 invoke();
522 }
523 } //}}}
524
525 //{{{ CompletionList class
526 class CompletionList extends JList
527 {
528 CompletionList(Object[] data)
529 {
530 super(data);
531 }
532
533 // we need this public not protected
534 public void processKeyEvent(KeyEvent evt)
535 {
536 super.processKeyEvent(evt);
537 }
538 } //}}}
539
540 //{{{ KeyHandler class
541 class KeyHandler extends KeyAdapter
542 {
543 public void keyTyped(KeyEvent evt)
544 {
545 action.processKeyEvent(evt);
546 }
547
548 public void keyPressed(KeyEvent evt)
549 {
550 int keyCode = evt.getKeyCode();
551 if(keyCode == KeyEvent.VK_ESCAPE)
552 action.processKeyEvent(evt);
553 else if(keyCode == KeyEvent.VK_ENTER)
554 invoke();
555 else if(keyCode == KeyEvent.VK_UP)
556 {
557 int selected = list.getSelectedIndex();
558 if(selected == 0)
559 {
560 list.setSelectedIndex(
561 list.getModel().getSize()
562 - 1);
563 evt.consume();
564 }
565 }
566 else if(keyCode == KeyEvent.VK_DOWN)
567 {
568 int selected = list.getSelectedIndex();
569 if(selected == list.getModel().getSize() - 1)
570 {
571 list.setSelectedIndex(0);
572 evt.consume();
573 }
574 }
575 }
576 } //}}}
577 } //}}}
578
579 //}}}
580 }