/*
* InputHandler.java - Manages key bindings and executes actions
* :tabSize=4:indentSize=4:noTabs=false:
* :folding=explicit:collapseFolds=1:
*
* Copyright (C) 1999, 2003 Slava Pestov
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.gjt.sp.jedit.gui;
//{{{ Imports
import javax.swing.*;
import javax.swing.text.JTextComponent;
import org.gjt.sp.jedit.textarea.JEditTextArea;
import org.gjt.sp.jedit.*;
import org.gjt.sp.jedit.buffer.JEditBuffer;
import org.gjt.sp.jedit.input.AbstractInputHandler;
import org.gjt.sp.util.GenericGUIUtilities;
import org.gjt.sp.util.Log;
import org.gjt.sp.util.StandardUtilities;
import java.awt.event.KeyEvent;
import java.awt.*;
//}}}
/**
* An input handler converts the user's key strokes into concrete actions.
* It also takes care of macro recording and action repetition.<p>
*
* This class provides all the necessary support code for an input
* handler, but doesn't actually do any key binding logic. It is up
* to the implementations of this class to do so.
*
* @author Slava Pestov
* @version $Id$
* @see org.gjt.sp.jedit.gui.DefaultInputHandler
*/
public abstract class InputHandler extends AbstractInputHandler<EditAction>
{
//{{{ InputHandler constructor
/**
* Creates a new input handler.
* @param view The view
*/
protected InputHandler(View view)
{
this.view = view;
} //}}}
//{{{ handleKey() method
/**
* Handles a keystroke.
* @param keyStroke The key stroke.
* @return true if the input could be handled.
* @since jEdit 4.2pre5
*/
public final boolean handleKey(KeyEventTranslator.Key keyStroke)
{
return handleKey(keyStroke, false);
} //}}}
//{{{ processKeyEvent() method
/**
* Forwards key events directly to the input handler.
* This is slightly faster than using a KeyListener
* because some Swing overhead is avoided.
* @since 4.3pre7
*/
@Override
public void processKeyEvent(KeyEvent evt, int from, boolean global)
{
if(Debug.DUMP_KEY_EVENTS)
{
Log.log(Log.DEBUG,this,"Key event : "
+ AbstractInputHandler.toString(evt) + " from " + from);
Log.log(Log.DEBUG,this,view+".isFocused()="+view.isFocused()+'.',new Exception());
}
if(view.getTextArea().hasFocus() && from == View.VIEW)
return;
evt = _preprocessKeyEvent(evt);
if(evt == null)
return;
if(Debug.DUMP_KEY_EVENTS)
{
Log.log(Log.DEBUG,this,"Key event after workaround: "
+ AbstractInputHandler.toString(evt) + " from " + from);
}
Component prefixFocusOwner = view.getPrefixFocusOwner();
boolean focusOnTextArea = false;
switch(evt.getID())
{
case KeyEvent.KEY_TYPED:
// if the user pressed eg C+e n n in the
// search bar we want focus to go back there
// after the prefix is done
if(prefixFocusOwner != null)
{
if(prefixFocusOwner.isShowing())
{
prefixFocusOwner.requestFocus();
focusOnTextArea = true;
}
}
if(keyEventInterceptor != null)
keyEventInterceptor.keyTyped(evt);
else if(from == View.ACTION_BAR
|| isPrefixActive()
|| view.getTextArea().hasFocus())
{
processKeyEventKeyStrokeHandling(evt,from,"type ",global);
}
processKeyEventSub(focusOnTextArea);
break;
case KeyEvent.KEY_PRESSED:
if(keyEventInterceptor != null)
keyEventInterceptor.keyPressed(evt);
else if(KeyEventWorkaround.isBindable(evt.getKeyCode()))
{
if(prefixFocusOwner != null)
{
if(prefixFocusOwner.isShowing())
{
prefixFocusOwner.requestFocus();
focusOnTextArea = true;
}
view.setPrefixFocusOwner(null);
}
processKeyEventKeyStrokeHandling(evt,from,"press",global);
processKeyEventSub(focusOnTextArea);
}
break;
case KeyEvent.KEY_RELEASED:
if(keyEventInterceptor != null)
keyEventInterceptor.keyReleased(evt);
break;
}
} //}}}
//{{{ _preprocessKeyEvent() method
private KeyEvent _preprocessKeyEvent(KeyEvent evt)
{
if(view.isClosed())
return null;
Component focusOwner = view.getFocusOwner();
if(focusOwner instanceof JComponent)
{
JComponent comp = (JComponent)focusOwner;
InputMap map = comp.getInputMap();
ActionMap am = comp.getActionMap();
if(map != null && am != null && comp.isEnabled())
{
KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(evt);
Object binding = map.get(keyStroke);
if(binding != null && am.get(binding) != null)
{
return null;
}
}
}
if(focusOwner instanceof JTextComponent)
{
// fix for the bug where key events in JTextComponents
// inside views are also handled by the input handler
if(evt.getID() == KeyEvent.KEY_PRESSED)
{
switch(evt.getKeyCode())
{
case KeyEvent.VK_ENTER:
case KeyEvent.VK_TAB:
case KeyEvent.VK_BACK_SPACE:
case KeyEvent.VK_SPACE:
return null;
}
}
}
if(evt.isConsumed())
return null;
if(Debug.DUMP_KEY_EVENTS)
{
Log.log(Log.DEBUG,this,"Key event (preprocessing) : "
+ AbstractInputHandler.toString(evt));
}
return KeyEventWorkaround.processKeyEvent(evt);
} //}}}
//{{{ processKeyEventSub() method
private void processKeyEventSub(boolean focusOnTextArea)
{
// we might have been closed as a result of
// the above
if(view.isClosed())
return;
// this is a weird hack.
// we don't want C+e a to insert 'a' in the
// search bar if the search bar has focus...
if(isPrefixActive())
{
Component focusOwner = view.getFocusOwner();
if(focusOwner instanceof JTextComponent)
{
view.setPrefixFocusOwner(focusOwner);
view.getTextArea().requestFocus();
}
else if(focusOnTextArea)
{
view.getTextArea().requestFocus();
}
else
{
view.setPrefixFocusOwner(null);
}
}
else
{
view.setPrefixFocusOwner(null);
}
}
//}}}
//{{{ getRepeatCount() method
/**
* Returns the number of times the next action will be repeated.
*/
public int getRepeatCount()
{
return repeatCount;
} //}}}
//{{{ setRepeatCount() method
/**
* Sets the number of times the next action will be repeated.
* @param repeatCount The repeat count
*/
public void setRepeatCount(int repeatCount)
{
int oldRepeatCount = this.repeatCount;
this.repeatCount = repeatCount;
if(oldRepeatCount != repeatCount)
view.getStatus().setMessage(null);
} //}}}
//{{{ getLastAction() method
/**
* Returns the last executed action.
* @since jEdit 2.5pre5
*/
public EditAction getLastAction()
{
return lastAction;
} //}}}
//{{{ readNextChar() method
/**
* Invokes the specified BeanShell code, replacing __char__ in the
* code with the next input character.
* @param msg The prompt to display in the status bar
* @param code The code
* @since jEdit 3.2pre2
*/
public void readNextChar(String msg, String code)
{
view.getStatus().setMessage(msg);
readNextChar = code;
} //}}}
//{{{ invokeAction() method
/**
* Invokes the specified action, repeating and recording it as
* necessary.
* @param action The action
* @since jEdit 4.2pre1
*/
@Override
public void invokeAction(String action)
{
invokeAction(jEdit.getAction(action));
} //}}}
//{{{ invokeAction() method
/**
* Invokes the specified action, repeating and recording it as
* necessary.
* @param action The action
*/
@Override
public void invokeAction(EditAction action)
{
JEditBuffer buffer = view.getBuffer();
/* if(buffer.insideCompoundEdit())
buffer.endCompoundEdit(); */
// remember the last executed action
if(!action.noRememberLast())
{
HistoryModel.getModel("action").addItem(action.getName());
if(lastAction == action)
lastActionCount++;
else
{
lastAction = action;
lastActionCount = 1;
}
}
// remember old values, in case action changes them
int _repeatCount = repeatCount;
// execute the action
if(action.noRepeat() || _repeatCount == 1)
action.invoke(view);
else
{
// stop people doing dumb stuff like C+ENTER 100 C+n
if(_repeatCount > REPEAT_COUNT_THRESHOLD)
{
String label = action.getLabel();
if(label == null)
label = action.getName();
else
label = GenericGUIUtilities.prettifyMenuLabel(label);
Object[] pp = { label, _repeatCount };
if(GUIUtilities.confirm(view,"large-repeat-count",pp,
JOptionPane.WARNING_MESSAGE,
JOptionPane.YES_NO_OPTION)
!= JOptionPane.YES_OPTION)
{
repeatCount = 1;
view.getStatus().setMessage(null);
return;
}
}
try
{
buffer.beginCompoundEdit();
for(int i = 0; i < _repeatCount; i++)
action.invoke(view);
}
finally
{
buffer.endCompoundEdit();
}
}
Macros.Recorder recorder = view.getMacroRecorder();
if(recorder != null && !action.noRecord())
recorder.record(_repeatCount,action.getCode());
// If repeat was true originally, clear it
// Otherwise it might have been set by the action, etc
if(_repeatCount != 1)
{
// first of all, if this action set a
// readNextChar, do not clear the repeat
if(readNextChar != null)
return;
repeatCount = 1;
view.getStatus().setMessage(null);
}
} //}}}
//{{{ invokeLastAction() method
public void invokeLastAction()
{
if(lastAction == null)
javax.swing.UIManager.getLookAndFeel().provideErrorFeedback(null);
else
invokeAction(lastAction);
} //}}}
//{{{ Instance variables
protected final View view;
//}}}
//{{{ userInput() method
protected void userInput(char ch)
{
lastActionCount = 0;
JEditTextArea textArea = view.getTextArea();
/* Buffer buffer = view.getBuffer();
if(!buffer.insideCompoundEdit())
buffer.beginCompoundEdit(); */
if(repeatCount == 1)
textArea.userInput(ch);
else
{
// stop people doing dumb stuff like C+ENTER 100 C+n
if(repeatCount > REPEAT_COUNT_THRESHOLD)
{
Object[] pp = { String.valueOf(ch),
repeatCount };
if(GUIUtilities.confirm(view,
"large-repeat-count.user-input",pp,
JOptionPane.WARNING_MESSAGE,
JOptionPane.YES_NO_OPTION)
!= JOptionPane.YES_OPTION)
{
repeatCount = 1;
view.getStatus().setMessage(null);
return;
}
}
JEditBuffer buffer = view.getBuffer();
try
{
if(repeatCount != 1)
buffer.beginCompoundEdit();
for(int i = 0; i < repeatCount; i++)
textArea.userInput(ch);
}
finally
{
if(repeatCount != 1)
buffer.endCompoundEdit();
}
}
Macros.Recorder recorder = view.getMacroRecorder();
if(recorder != null)
{
recorder.recordInput(repeatCount,ch,
textArea.isOverwriteEnabled());
}
repeatCount = 1;
} //}}}
//{{{ invokeReadNextChar() method
protected void invokeReadNextChar(char ch)
{
JEditBuffer buffer = view.getBuffer();
/* if(buffer.insideCompoundEdit())
buffer.endCompoundEdit(); */
String charStr = StandardUtilities.charsToEscapes(String.valueOf(ch));
// this might be a bit slow if __char__ occurs a lot
int index;
while((index = readNextChar.indexOf("__char__")) != -1)
{
readNextChar = readNextChar.substring(0,index)
+ '\'' + charStr + '\''
+ readNextChar.substring(index + 8);
}
Macros.Recorder recorder = view.getMacroRecorder();
if(recorder != null)
recorder.record(getRepeatCount(),readNextChar);
view.getStatus().setMessage(null);
if(getRepeatCount() != 1)
{
try
{
buffer.beginCompoundEdit();
BeanShell.eval(view,BeanShell.getNameSpace(),
"for(int i = 1; i < "
+ getRepeatCount() + "; i++)\n{\n"
+ readNextChar + "\n}");
}
finally
{
buffer.endCompoundEdit();
}
}
else
BeanShell.eval(view,BeanShell.getNameSpace(),readNextChar);
readNextChar = null;
} //}}}
}