PageRenderTime 179ms CodeModel.GetById 137ms app.highlight 31ms RepoModel.GetById 6ms app.codeStats 0ms

/jEdit/tags/jedit-4-0-pre5/org/gjt/sp/jedit/gui/GrabKeyDialog.java

#
Java | 579 lines | 379 code | 73 blank | 127 comment | 104 complexity | 33702e7de84c9802a6fbc4648deac8a7 MD5 | raw file
  1/*
  2 * GrabKeyDialog.java - Grabs keys from the keyboard
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright (C) 2001, 2002 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
 23package org.gjt.sp.jedit.gui;
 24
 25//{{{ Imports
 26import javax.swing.border.*;
 27import javax.swing.*;
 28import java.awt.event.*;
 29import java.awt.*;
 30import java.lang.reflect.Field;
 31import java.util.*;
 32import org.gjt.sp.jedit.*;
 33import org.gjt.sp.util.Log;
 34//}}}
 35
 36/**
 37 * A dialog for getting shortcut keys.
 38 */
 39public class GrabKeyDialog extends JDialog
 40{
 41	//{{{ keyboardTest() method
 42	public static void keyboardTest(View view)
 43	{
 44		Buffer buffer = jEdit.newFile(view);
 45		new GrabKeyDialog(view,null,null,buffer);
 46	} //}}}
 47
 48	//{{{ GrabKeyDialog constructor
 49	/**
 50	 * Create and show a new modal dialog.
 51	 *
 52	 * @param  comp  center dialog on this component.
 53	 * @param  binding  the action/macro that should get a binding.
 54	 * @param  allBindings  all other key bindings.
 55	 */
 56	public GrabKeyDialog(Component comp, KeyBinding binding,
 57		Vector allBindings)
 58	{
 59		this(comp,binding,allBindings,null);
 60	} //}}}
 61
 62	//{{{ GrabKeyDialog constructor
 63	/**
 64	 * Create and show a new modal dialog.
 65	 *
 66	 * @param  comp  center dialog on this component.
 67	 * @param  debugBuffer  debug info will be dumped to this buffer
 68	 * (may be null)
 69	 */
 70	public GrabKeyDialog(Component comp, Buffer debugBuffer)
 71	{
 72		this(comp,null,null,debugBuffer);
 73	} //}}}
 74
 75	//{{{ GrabKeyDialog constructor
 76	/**
 77	 * Create and show a new modal dialog.
 78	 *
 79	 * @param  comp  center dialog on this component.
 80	 * @param  binding  the action/macro that should get a binding.
 81	 * @param  allBindings  all other key bindings.
 82	 * @param  debugBuffer  debug info will be dumped to this buffer
 83	 * (may be null)
 84	 */
 85	public GrabKeyDialog(Component comp, KeyBinding binding,
 86		Vector allBindings, Buffer debugBuffer)
 87	{
 88		super(JOptionPane.getFrameForComponent(comp),
 89			jEdit.getProperty("grab-key.title"),true);
 90		this.binding = binding;
 91		this.allBindings = allBindings;
 92		this.debugBuffer = debugBuffer;
 93
 94		enableEvents(AWTEvent.KEY_EVENT_MASK);
 95
 96		// create a panel with a BoxLayout. Can't use Box here
 97		// because Box doesn't have setBorder().
 98		JPanel content = new JPanel(new GridLayout(0,1,0,6))
 99		{
100			/**
101			 * Returns if this component can be traversed by pressing the
102			 * Tab key. This returns false.
103			 */
104			public boolean isManagingFocus()
105			{
106				return false;
107			}
108
109			/**
110			 * Makes the tab key work in Java 1.4.
111			 * @since jEdit 3.2pre4
112			 */
113			public boolean getFocusTraversalKeysEnabled()
114			{
115				return false;
116			}
117		};
118		content.setBorder(new EmptyBorder(12,12,12,12));
119		setContentPane(content);
120
121		JLabel label = new JLabel(
122			debugBuffer == null ? jEdit.getProperty(
123			"grab-key.caption",new String[] { binding.label })
124			: jEdit.getProperty("grab-key.keyboard-test"));
125
126		Box input = Box.createHorizontalBox();
127
128		shortcut = new InputPane();
129		input.add(shortcut);
130		input.add(Box.createHorizontalStrut(12));
131
132		clear = new JButton(jEdit.getProperty("grab-key.clear"));
133		clear.addActionListener(new ActionHandler());
134		input.add(clear);
135
136		assignedTo = new JLabel();
137		if(debugBuffer == null)
138			updateAssignedTo(null);
139
140		Box buttons = Box.createHorizontalBox();
141		buttons.add(Box.createGlue());
142
143		if(debugBuffer == null)
144		{
145			ok = new JButton(jEdit.getProperty("common.ok"));
146			ok.addActionListener(new ActionHandler());
147			buttons.add(ok);
148			buttons.add(Box.createHorizontalStrut(12));
149
150			if(binding.isAssigned()) {
151				// show "remove" button
152				remove = new JButton(jEdit.getProperty("grab-key.remove"));
153				remove.addActionListener(new ActionHandler());
154				buttons.add(remove);
155				buttons.add(Box.createHorizontalStrut(12));
156			}
157		}
158
159		cancel = new JButton(jEdit.getProperty("common.cancel"));
160		cancel.addActionListener(new ActionHandler());
161		buttons.add(cancel);
162		buttons.add(Box.createGlue());
163
164		content.add(label);
165		content.add(input);
166		if(debugBuffer == null)
167			content.add(assignedTo);
168		content.add(buttons);
169
170		setDefaultCloseOperation(DISPOSE_ON_CLOSE);
171
172		pack();
173		setLocationRelativeTo(comp);
174		setResizable(false);
175		show();
176	} //}}}
177
178	//{{{ getShortcut() method
179	/**
180	 * Returns the shortcut, or null if the current shortcut should be
181	 * removed or the dialog either has been cancelled. Use isOK()
182	 * to determine if the latter is true.
183	 */
184	public String getShortcut()
185	{
186		if(isOK)
187			return shortcut.getText();
188		else
189			return null;
190	} //}}}
191
192	//{{{ isOK() method
193	/**
194	 * Returns true, if the dialog has not been cancelled.
195	 * @since jEdit 3.2pre9
196	 */
197	public boolean isOK()
198	{
199		return isOK;
200	} //}}}
201
202	//{{{ isManagingFocus() method
203	/**
204	 * Returns if this component can be traversed by pressing the
205	 * Tab key. This returns false.
206	 */
207	public boolean isManagingFocus()
208	{
209		return false;
210	} //}}}
211
212	//{{{ getFocusTraversalKeysEnabled() method
213	/**
214	 * Makes the tab key work in Java 1.4.
215	 * @since jEdit 3.2pre4
216	 */
217	public boolean getFocusTraversalKeysEnabled()
218	{
219		return false;
220	} //}}}
221
222	//{{{ processKeyEvent() method
223	protected void processKeyEvent(KeyEvent evt)
224	{
225		shortcut.processKeyEvent(evt);
226	} //}}}
227
228	//{{{ Private members
229
230	//{{{ Instance variables
231	private InputPane shortcut; // this is a bad hack
232	private JLabel assignedTo;
233	private JButton ok;
234	private JButton remove;
235	private JButton cancel;
236	private JButton clear;
237	private boolean isOK;
238	private KeyBinding binding;
239	private Vector allBindings;
240	private Buffer debugBuffer;
241	//}}}
242
243	//{{{ getSymbolicName() method
244	private String getSymbolicName(int keyCode)
245	{
246		if(keyCode == KeyEvent.VK_UNDEFINED)
247			return null;
248		/* else if(keyCode == KeyEvent.VK_OPEN_BRACKET)
249			return "[";
250		else if(keyCode == KeyEvent.VK_CLOSE_BRACKET)
251			return "]"; */
252
253		if(keyCode >= KeyEvent.VK_A && keyCode <= KeyEvent.VK_Z)
254			return String.valueOf(Character.toLowerCase((char)keyCode));
255
256		try
257		{
258			Field[] fields = KeyEvent.class.getFields();
259			for(int i = 0; i < fields.length; i++)
260			{
261				Field field = fields[i];
262				String name = field.getName();
263				if(name.startsWith("VK_")
264					&& field.getInt(null) == keyCode)
265				{
266					return name.substring(3);
267				}
268			}
269		}
270		catch(Exception e)
271		{
272			Log.log(Log.ERROR,this,e);
273		}
274
275		return null;
276	} //}}}
277
278	//{{{ updateAssignedTo() method
279	private void updateAssignedTo(String shortcut)
280	{
281		String text = jEdit.getProperty("grab-key.assigned-to.none");
282		KeyBinding kb = getKeyBinding(shortcut);
283
284		if(kb != null)
285			if(kb.isPrefix)
286				text = jEdit.getProperty(
287					"grab-key.assigned-to.prefix",
288					new String[] { shortcut });
289			else
290				text = kb.label;
291
292		if(ok != null)
293			ok.setEnabled(kb == null || !kb.isPrefix);
294
295		assignedTo.setText(
296			jEdit.getProperty("grab-key.assigned-to",
297				new String[] { text }));
298	} //}}}
299
300	//{{{ getKeyBinding() method
301	private KeyBinding getKeyBinding(String shortcut)
302	{
303		if(shortcut == null || shortcut.length() == 0)
304			return null;
305
306		String spacedShortcut = shortcut + " ";
307		Enumeration enum = allBindings.elements();
308
309		while(enum.hasMoreElements())
310		{
311			KeyBinding kb = (KeyBinding)enum.nextElement();
312
313			if(!kb.isAssigned())
314				continue;
315
316			String spacedKbShortcut = kb.shortcut + " ";
317
318			// eg, trying to bind C+n C+p if C+n already bound
319			if(spacedShortcut.startsWith(spacedKbShortcut))
320				return kb;
321
322			// eg, trying to bind C+e if C+e is a prefix
323			if(spacedKbShortcut.startsWith(spacedShortcut))
324			{
325				// create a temporary (synthetic) prefix
326				// KeyBinding, that won't be saved
327				return new KeyBinding(kb.name,kb.label,
328					shortcut,true);
329			}
330		}
331
332		return null;
333	} //}}}
334
335	//}}}
336
337	//{{{ KeyBinding class
338	/**
339	 * A jEdit action or macro with its two possible shortcuts.
340	 * @since jEdit 3.2pre8
341	 */
342	public static class KeyBinding
343	{
344		public KeyBinding(String name, String label,
345			String shortcut, boolean isPrefix)
346		{
347			this.name = name;
348			this.label = label;
349			this.shortcut = shortcut;
350			this.isPrefix = isPrefix;
351		}
352
353		public String name;
354		public String label;
355		public String shortcut;
356		public boolean isPrefix;
357
358		public boolean isAssigned()
359		{
360			return shortcut != null && shortcut.length() > 0;
361		}
362	} //}}}
363
364	//{{{ InputPane class
365	class InputPane extends JTextField
366	{
367		//{{{ getFocusTraversalKeysEnabled() method
368		/**
369		 * Makes the tab key work in Java 1.4.
370		 * @since jEdit 3.2pre4
371		 */
372		public boolean getFocusTraversalKeysEnabled()
373		{
374			return false;
375		} //}}}
376
377		//{{{ processKeyEvent() method
378		protected void processKeyEvent(KeyEvent _evt)
379		{
380			KeyEvent evt = KeyEventWorkaround.processKeyEvent(_evt);
381			if(evt == null)
382			{
383				if(debugBuffer != null)
384				{
385					debugBuffer.insert(debugBuffer.getLength(),
386						"Event " + _evt + " filtered\n");
387				}
388				return;
389			}
390			else
391			{
392				if(debugBuffer != null)
393				{
394					debugBuffer.insert(debugBuffer.getLength(),
395						"Event " + _evt + " passed\n");
396				}
397			}
398
399			evt.consume();
400
401			StringBuffer keyString = new StringBuffer(getText());
402
403			if(getDocument().getLength() != 0)
404				keyString.append(' ');
405
406			if(evt.getID() == KeyEvent.KEY_TYPED)
407			{
408				if(!Character.isLetterOrDigit(evt.getKeyChar()))
409					return;
410
411				keyString.append(evt.getKeyChar());
412			}
413			else if(evt.getID() == KeyEvent.KEY_PRESSED)
414			{
415				boolean appendPlus = false;
416
417				// On MacOS, C+ is command, M+ is control
418				// so that jEdit's default shortcuts are
419				// mapped to the Command
420				if(evt.isControlDown())
421				{
422					keyString.append(OperatingSystem.isMacOS()
423						? 'M' : 'C');
424					appendPlus = true;
425				}
426
427				if(evt.isAltDown())
428				{
429					keyString.append('A');
430					appendPlus = true;
431				}
432
433				if(evt.isMetaDown())
434				{
435					keyString.append(OperatingSystem.isMacOS()
436						? 'C' : 'M');
437					appendPlus = true;
438				}
439
440				if(evt.isShiftDown())
441				{
442					keyString.append('S');
443					appendPlus = true;
444				}
445
446				if(appendPlus)
447					keyString.append('+');
448
449				int keyCode = evt.getKeyCode();
450
451				if(((keyCode >= KeyEvent.VK_A && keyCode <= KeyEvent.VK_Z)
452					|| (keyCode >= KeyEvent.VK_0 && keyCode <= KeyEvent.VK_9))
453					&& !evt.isAltDown()
454					&& !evt.isControlDown()
455					&& !evt.isMetaDown()
456					&& !evt.isShiftDown())
457				{
458					// will be handled by KEY_TYPED
459					return;
460				}
461
462				String symbolicName = getSymbolicName(keyCode);
463
464				if(symbolicName == null)
465					return;
466
467				keyString.append(symbolicName);
468			}
469			else if(evt.getID() == KeyEvent.KEY_RELEASED)
470				return;
471
472			setText(keyString.toString());
473			if(debugBuffer == null)
474				updateAssignedTo(keyString.toString());
475		} //}}}
476	} //}}}
477
478	//{{{ ActionHandler class
479	class ActionHandler implements ActionListener
480	{
481		//{{{ actionPerformed() method
482		public void actionPerformed(ActionEvent evt)
483		{
484			if(evt.getSource() == ok)
485			{
486				if(canClose())
487					dispose();
488			}
489			else if(evt.getSource() == remove)
490			{
491				shortcut.setText(null);
492				isOK = true;
493				dispose();
494			}
495			else if(evt.getSource() == cancel)
496				dispose();
497			else if(evt.getSource() == clear)
498			{
499				shortcut.setText(null);
500				if(debugBuffer == null)
501					updateAssignedTo(null);
502			}
503		} //}}}
504
505		//{{{ canClose() method
506		private boolean canClose()
507		{
508			String shortcutString = shortcut.getText();
509			if(shortcutString.length() == 0
510				&& binding.isAssigned())
511			{
512				// ask whether to remove the old shortcut
513				int answer = GUIUtilities.confirm(
514					GrabKeyDialog.this,
515					"grab-key.remove-ask",
516					null,
517					JOptionPane.YES_NO_CANCEL_OPTION,
518					JOptionPane.QUESTION_MESSAGE);
519				if(answer == JOptionPane.YES_OPTION)
520				{
521					shortcut.setText(null);
522					isOK = true;
523				}
524				else if(answer == JOptionPane.CANCEL_OPTION)
525					return false;
526				return true;
527			}
528
529			// check whether this shortcut already exists
530			KeyBinding other = getKeyBinding(shortcutString);
531			if(other == null || other == binding)
532			{
533				isOK = true;
534				return true;
535			}
536
537			// check whether the other shortcut is the alt. shortcut
538			if(other.name == binding.name)
539			{
540				// we don't need two identical shortcuts
541				GUIUtilities.error(GrabKeyDialog.this,
542					"grab-key.duplicate-alt-shortcut",
543					null);
544				return false;
545			}
546
547			// check whether shortcut is a prefix to others
548			if(other.isPrefix)
549			{
550				// can't override prefix shortcuts
551				GUIUtilities.error(GrabKeyDialog.this,
552					"grab-key.prefix-shortcut",
553					null);
554				return false;
555			}
556
557			// ask whether to override that other shortcut
558			int answer = GUIUtilities.confirm(GrabKeyDialog.this,
559				"grab-key.duplicate-shortcut",
560				new Object[] { other.label },
561				JOptionPane.YES_NO_CANCEL_OPTION,
562				JOptionPane.QUESTION_MESSAGE);
563			if(answer == JOptionPane.YES_OPTION)
564			{
565				if(other.shortcut != null
566					&& shortcutString.startsWith(other.shortcut))
567				{
568					other.shortcut = null;
569				}
570				isOK = true;
571				return true;
572			}
573			else if(answer == JOptionPane.CANCEL_OPTION)
574				return false;
575
576			return true;
577		} //}}}
578	} //}}}
579}