001: /*
002: * GrabKeyDialog.java - Grabs keys from the keyboard
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 2001, 2002 Slava Pestov
007: *
008: * This program is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU General Public License
010: * as published by the Free Software Foundation; either version 2
011: * of the License, or any later version.
012: *
013: * This program is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: * GNU General Public License for more details.
017: *
018: * You should have received a copy of the GNU General Public License
019: * along with this program; if not, write to the Free Software
020: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
021: */
022:
023: package org.gjt.sp.jedit.gui;
024:
025: //{{{ Imports
026: import javax.swing.border.*;
027: import javax.swing.*;
028: import java.awt.event.*;
029: import java.awt.*;
030: import java.lang.reflect.Field;
031: import java.util.*;
032: import org.gjt.sp.jedit.*;
033: import org.gjt.sp.util.Log;
034:
035: //}}}
036:
037: /**
038: * A dialog for getting shortcut keys.
039: */
040: public class GrabKeyDialog extends JDialog {
041: //{{{ toString() method
042: public static String toString(KeyEvent evt) {
043: String id;
044: switch (evt.getID()) {
045: case KeyEvent.KEY_PRESSED:
046: id = "KEY_PRESSED";
047: break;
048: case KeyEvent.KEY_RELEASED:
049: id = "KEY_RELEASED";
050: break;
051: case KeyEvent.KEY_TYPED:
052: id = "KEY_TYPED";
053: break;
054: default:
055: id = "unknown type";
056: break;
057: }
058:
059: StringBuilder b = new StringBuilder(50);
060:
061: b.append(id + ",keyCode=0x"
062: + Integer.toString(evt.getKeyCode(), 16)
063: + ",keyChar=0x"
064: + Integer.toString(evt.getKeyChar(), 16)
065: + ",modifiers=0x"
066: + Integer.toString(evt.getModifiers(), 16));
067:
068: b.append(",consumed=");
069: b.append(evt.isConsumed() ? '1' : '0');
070:
071: return b.toString();
072: } //}}}
073:
074: //{{{ GrabKeyDialog constructor
075: /**
076: * Create and show a new modal dialog.
077: *
078: * @param parent center dialog on this component.
079: * @param binding the action/macro that should get a binding.
080: * @param allBindings all other key bindings.
081: * @param debugBuffer debug info will be dumped to this buffer
082: * (may be null)
083: * @since jEdit 4.1pre7
084: */
085: public GrabKeyDialog(Dialog parent, KeyBinding binding,
086: Vector allBindings, Buffer debugBuffer) {
087: super (parent, jEdit.getProperty("grab-key.title"), true);
088:
089: init(binding, allBindings, debugBuffer);
090: } //}}}
091:
092: //{{{ GrabKeyDialog constructor
093: /**
094: * Create and show a new modal dialog.
095: *
096: * @param parent center dialog on this component.
097: * @param binding the action/macro that should get a binding.
098: * @param allBindings all other key bindings.
099: * @param debugBuffer debug info will be dumped to this buffer
100: * (may be null)
101: * @since jEdit 4.1pre7
102: */
103: public GrabKeyDialog(Frame parent, KeyBinding binding,
104: Vector allBindings, Buffer debugBuffer) {
105: super (parent, jEdit.getProperty("grab-key.title"), true);
106:
107: init(binding, allBindings, debugBuffer);
108: } //}}}
109:
110: //{{{ getShortcut() method
111: /**
112: * Returns the shortcut, or null if the current shortcut should be
113: * removed or the dialog either has been cancelled. Use isOK()
114: * to determine if the latter is true.
115: */
116: public String getShortcut() {
117: if (isOK)
118: return shortcut.getText();
119: else
120: return null;
121: } //}}}
122:
123: //{{{ isOK() method
124: /**
125: * Returns true, if the dialog has not been cancelled.
126: * @since jEdit 3.2pre9
127: */
128: public boolean isOK() {
129: return isOK;
130: } //}}}
131:
132: //{{{ isManagingFocus() method
133: /**
134: * Returns if this component can be traversed by pressing the
135: * Tab key. This returns false.
136: */
137: public boolean isManagingFocus() {
138: return false;
139: } //}}}
140:
141: //{{{ getFocusTraversalKeysEnabled() method
142: /**
143: * Makes the tab key work in Java 1.4.
144: * @since jEdit 3.2pre4
145: */
146: public boolean getFocusTraversalKeysEnabled() {
147: return false;
148: } //}}}
149:
150: //{{{ processKeyEvent() method
151: protected void processKeyEvent(KeyEvent evt) {
152: shortcut.processKeyEvent(evt);
153: } //}}}
154:
155: //{{{ Private members
156:
157: //{{{ Instance variables
158: private InputPane shortcut; // this is a bad hack
159: private JLabel assignedTo;
160: private JButton ok;
161: private JButton remove;
162: private JButton cancel;
163: private JButton clear;
164: private boolean isOK;
165: private KeyBinding binding;
166: private Vector allBindings;
167: private Buffer debugBuffer;
168:
169: //}}}
170:
171: //{{{ init() method
172: private void init(KeyBinding binding, Vector allBindings,
173: Buffer debugBuffer) {
174: this .binding = binding;
175: this .allBindings = allBindings;
176: this .debugBuffer = debugBuffer;
177:
178: enableEvents(AWTEvent.KEY_EVENT_MASK);
179:
180: // create a panel with a BoxLayout. Can't use Box here
181: // because Box doesn't have setBorder().
182: JPanel content = new JPanel(new GridLayout(0, 1, 0, 6)) {
183: /**
184: * Returns if this component can be traversed by pressing the
185: * Tab key. This returns false.
186: */
187: public boolean isManagingFocus() {
188: return false;
189: }
190:
191: /**
192: * Makes the tab key work in Java 1.4.
193: * @since jEdit 3.2pre4
194: */
195: public boolean getFocusTraversalKeysEnabled() {
196: return false;
197: }
198: };
199: content.setBorder(new EmptyBorder(12, 12, 12, 12));
200: setContentPane(content);
201:
202: JLabel label = new JLabel(debugBuffer == null ? jEdit
203: .getProperty("grab-key.caption",
204: new String[] { binding.label }) : jEdit
205: .getProperty("grab-key.keyboard-test"));
206:
207: Box input = Box.createHorizontalBox();
208:
209: shortcut = new InputPane();
210: Dimension size = shortcut.getPreferredSize();
211: size.width = Integer.MAX_VALUE;
212: shortcut.setMaximumSize(size);
213: input.add(shortcut);
214: input.add(Box.createHorizontalStrut(12));
215:
216: clear = new JButton(jEdit.getProperty("grab-key.clear"));
217: clear.addActionListener(new ActionHandler());
218: input.add(clear);
219:
220: assignedTo = new JLabel();
221: if (debugBuffer == null)
222: updateAssignedTo(null);
223:
224: Box buttons = Box.createHorizontalBox();
225: buttons.add(Box.createGlue());
226:
227: if (debugBuffer == null) {
228: ok = new JButton(jEdit.getProperty("common.ok"));
229: ok.addActionListener(new ActionHandler());
230: buttons.add(ok);
231: buttons.add(Box.createHorizontalStrut(12));
232:
233: if (binding.isAssigned()) {
234: // show "remove" button
235: remove = new JButton(jEdit
236: .getProperty("grab-key.remove"));
237: remove.addActionListener(new ActionHandler());
238: buttons.add(remove);
239: buttons.add(Box.createHorizontalStrut(12));
240: }
241: }
242:
243: cancel = new JButton(jEdit.getProperty("common.cancel"));
244: cancel.addActionListener(new ActionHandler());
245: buttons.add(cancel);
246: buttons.add(Box.createGlue());
247:
248: content.add(label);
249: content.add(input);
250: if (debugBuffer == null)
251: content.add(assignedTo);
252: content.add(buttons);
253:
254: setDefaultCloseOperation(DISPOSE_ON_CLOSE);
255:
256: pack();
257: setLocationRelativeTo(getParent());
258: setResizable(false);
259: setVisible(true);
260: } //}}}
261:
262: //{{{ getSymbolicName() method
263: public static String getSymbolicName(int keyCode) {
264: if (Debug.DUMP_KEY_EVENTS) {
265: Log.log(Log.DEBUG, GrabKeyDialog.class, "getSymbolicName("
266: + keyCode + ").");
267: }
268:
269: if (keyCode == KeyEvent.VK_UNDEFINED)
270: return null;
271: /* else if(keyCode == KeyEvent.VK_OPEN_BRACKET)
272: return "[";
273: else if(keyCode == KeyEvent.VK_CLOSE_BRACKET)
274: return "]"; */
275:
276: if (keyCode >= KeyEvent.VK_A && keyCode <= KeyEvent.VK_Z)
277: return String
278: .valueOf(Character.toLowerCase((char) keyCode));
279:
280: try {
281: Field[] fields = KeyEvent.class.getFields();
282: for (int i = 0; i < fields.length; i++) {
283: Field field = fields[i];
284: String name = field.getName();
285: if (name.startsWith("VK_")
286: && field.getInt(null) == keyCode) {
287: return name.substring(3);
288: }
289: }
290: } catch (Exception e) {
291: Log.log(Log.ERROR, GrabKeyDialog.class, e);
292: }
293:
294: return null;
295: } //}}}
296:
297: //{{{ updateAssignedTo() method
298: private void updateAssignedTo(String shortcut) {
299: String text = jEdit.getProperty("grab-key.assigned-to.none");
300: KeyBinding kb = getKeyBinding(shortcut);
301:
302: if (kb != null)
303: if (kb.isPrefix)
304: text = jEdit.getProperty("grab-key.assigned-to.prefix",
305: new String[] { shortcut });
306: else
307: text = kb.label;
308:
309: if (ok != null)
310: ok.setEnabled(kb == null || !kb.isPrefix);
311:
312: assignedTo.setText(jEdit.getProperty("grab-key.assigned-to",
313: new String[] { text }));
314: } //}}}
315:
316: //{{{ getKeyBinding() method
317: private KeyBinding getKeyBinding(String shortcut) {
318: if (shortcut == null || shortcut.length() == 0)
319: return null;
320:
321: String spacedShortcut = shortcut + " ";
322: Enumeration e = allBindings.elements();
323:
324: while (e.hasMoreElements()) {
325: KeyBinding kb = (KeyBinding) e.nextElement();
326:
327: if (!kb.isAssigned())
328: continue;
329:
330: String spacedKbShortcut = kb.shortcut + " ";
331:
332: // eg, trying to bind C+n C+p if C+n already bound
333: if (spacedShortcut.startsWith(spacedKbShortcut))
334: return kb;
335:
336: // eg, trying to bind C+e if C+e is a prefix
337: if (spacedKbShortcut.startsWith(spacedShortcut)) {
338: // create a temporary (synthetic) prefix
339: // KeyBinding, that won't be saved
340: return new KeyBinding(kb.name, kb.label, shortcut, true);
341: }
342: }
343:
344: return null;
345: } //}}}
346:
347: //}}}
348:
349: //{{{ KeyBinding class
350: /**
351: * A jEdit action or macro with its two possible shortcuts.
352: * @since jEdit 3.2pre8
353: */
354: public static class KeyBinding {
355: public KeyBinding(String name, String label, String shortcut,
356: boolean isPrefix) {
357: this .name = name;
358: this .label = label;
359: this .shortcut = shortcut;
360: this .isPrefix = isPrefix;
361: }
362:
363: public String name;
364: public String label;
365: public String shortcut;
366: public boolean isPrefix;
367:
368: public boolean isAssigned() {
369: return shortcut != null && shortcut.length() > 0;
370: }
371: } //}}}
372:
373: //{{{ InputPane class
374: class InputPane extends JTextField {
375: //{{{ getFocusTraversalKeysEnabled() method
376: /**
377: * Makes the tab key work in Java 1.4.
378: * @since jEdit 3.2pre4
379: */
380: public boolean getFocusTraversalKeysEnabled() {
381: return false;
382: } //}}}
383:
384: //{{{ processKeyEvent() method
385: protected void processKeyEvent(KeyEvent _evt) {
386: KeyEvent evt = KeyEventWorkaround.processKeyEvent(_evt);
387: if (!KeyEventWorkaround.isBindable(_evt.getKeyCode()))
388: evt = null;
389:
390: if (debugBuffer != null) {
391: debugBuffer.insert(debugBuffer.getLength(), "Event "
392: + GrabKeyDialog.toString(_evt)
393: + (evt == null ? " filtered\n" : " passed\n"));
394: }
395:
396: if (evt == null)
397: return;
398:
399: evt.consume();
400:
401: KeyEventTranslator.Key key = KeyEventTranslator
402: .translateKeyEvent(evt);
403:
404: if (Debug.DUMP_KEY_EVENTS) {
405: Log.log(Log.DEBUG, GrabKeyDialog.class,
406: "processKeyEvent() key=" + key + ", _evt="
407: + _evt + ".");
408: }
409:
410: if (key == null)
411: return;
412:
413: if (debugBuffer != null) {
414: debugBuffer.insert(debugBuffer.getLength(),
415: "==> Translated to " + key + "\n");
416: }
417:
418: StringBuffer keyString = new StringBuffer(getText());
419:
420: if (getDocument().getLength() != 0)
421: keyString.append(' ');
422:
423: if (!Options.SIMPLIFIED_KEY_HANDLING) {
424: if (key.modifiers != null)
425: keyString.append(key.modifiers).append('+');
426:
427: if (key.input == ' ')
428: keyString.append("SPACE");
429: else if (key.input != '\0')
430: keyString.append(key.input);
431: else {
432: String symbolicName = getSymbolicName(key.key);
433:
434: if (symbolicName == null)
435: return;
436:
437: keyString.append(symbolicName);
438: }
439: } else {
440: if (key.modifiers != null) {
441: keyString.append(key.modifiers).append('+');
442: }
443:
444: String symbolicName = getSymbolicName(key.key);
445:
446: if (symbolicName != null) {
447: keyString.append(symbolicName);
448: } else {
449: if (key.input != '\0') {
450: if (key.input == ' ') {
451: keyString.append("SPACE");
452: } else {
453: keyString.append(key.input);
454: }
455: } else {
456: return;
457: }
458: }
459:
460: }
461:
462: setText(keyString.toString());
463: if (debugBuffer == null)
464: updateAssignedTo(keyString.toString());
465: } //}}}
466: } //}}}
467:
468: //{{{ ActionHandler class
469: class ActionHandler implements ActionListener {
470: //{{{ actionPerformed() method
471: public void actionPerformed(ActionEvent evt) {
472: if (evt.getSource() == ok) {
473: if (canClose())
474: dispose();
475: } else if (evt.getSource() == remove) {
476: shortcut.setText(null);
477: isOK = true;
478: dispose();
479: } else if (evt.getSource() == cancel)
480: dispose();
481: else if (evt.getSource() == clear) {
482: shortcut.setText(null);
483: if (debugBuffer == null)
484: updateAssignedTo(null);
485: shortcut.requestFocus();
486: }
487: } //}}}
488:
489: //{{{ canClose() method
490: private boolean canClose() {
491: String shortcutString = shortcut.getText();
492: if (shortcutString.length() == 0 && binding.isAssigned()) {
493: // ask whether to remove the old shortcut
494: int answer = GUIUtilities.confirm(GrabKeyDialog.this ,
495: "grab-key.remove-ask", null,
496: JOptionPane.YES_NO_OPTION,
497: JOptionPane.QUESTION_MESSAGE);
498: if (answer == JOptionPane.YES_OPTION) {
499: shortcut.setText(null);
500: isOK = true;
501: } else
502: return false;
503: }
504:
505: // check whether this shortcut already exists
506: KeyBinding other = getKeyBinding(shortcutString);
507: if (other == null || other == binding) {
508: isOK = true;
509: return true;
510: }
511:
512: // check whether the other shortcut is the alt. shortcut
513: if (other.name == binding.name) {
514: // we don't need two identical shortcuts
515: GUIUtilities.error(GrabKeyDialog.this ,
516: "grab-key.duplicate-alt-shortcut", null);
517: return false;
518: }
519:
520: // check whether shortcut is a prefix to others
521: if (other.isPrefix) {
522: // can't override prefix shortcuts
523: GUIUtilities.error(GrabKeyDialog.this ,
524: "grab-key.prefix-shortcut", null);
525: return false;
526: }
527:
528: // ask whether to override that other shortcut
529: int answer = GUIUtilities.confirm(GrabKeyDialog.this ,
530: "grab-key.duplicate-shortcut",
531: new Object[] { other.label },
532: JOptionPane.YES_NO_OPTION,
533: JOptionPane.QUESTION_MESSAGE);
534: if (answer == JOptionPane.YES_OPTION) {
535: if (other.shortcut != null
536: && shortcutString.startsWith(other.shortcut)) {
537: other.shortcut = null;
538: }
539: isOK = true;
540: return true;
541: } else
542: return false;
543: } //}}}
544: } //}}}
545: }
|