/*
* TextAreaInputHandler.java - Manages key bindings and executes actions
* :tabSize=4:indentSize=4:noTabs=false:
* :folding=explicit:collapseFolds=1:
*
* Copyright (C) 2006 Matthieu Casanova
*
* 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.input;
//{{{ Imports
import org.gjt.sp.jedit.Debug;
import org.gjt.sp.jedit.gui.KeyEventTranslator;
import org.gjt.sp.jedit.gui.KeyEventWorkaround;
import org.gjt.sp.jedit.textarea.TextArea;
import org.gjt.sp.util.Log;
import org.gjt.sp.util.StandardUtilities;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.util.Hashtable;
import org.gjt.sp.jedit.JEditBeanShellAction;
import org.gjt.sp.jedit.buffer.JEditBuffer;
import org.gjt.sp.jedit.gui.ShortcutPrefixActiveEvent;
//}}}
/**
* This class manage the key bindings and execute the actions binded on the
* keyboard events for the standalone textarea.
*
* @author Matthieu Casanova
* @version $Id: FoldHandler.java 5568 2006-07-10 20:52:23Z kpouer $
*/
public abstract class TextAreaInputHandler extends AbstractInputHandler<JEditBeanShellAction>
{
private final TextArea textArea;
//{{{ TextAreaInputHandler constructor
protected TextAreaInputHandler(TextArea textArea)
{
this.textArea = textArea;
bindings = currentBindings = new Hashtable();
} //}}}
//{{{ processKeyEvent() method
/**
* Forwards key events directly to the input handler.
* This is slightly faster than using a KeyListener
* because some Swing overhead is avoided.
* @param evt the keyboard event
* @param from the source of the event. Since this is the input handler of the textarea, it should always be 1
* @param global it is only true if the event comes from the DefaultKeyboardFocusManager
* @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 : "
+ toString(evt) + " from " + from);
// Log.log(Log.DEBUG,this,view+".isFocused()="+view.isFocused()+'.',new Exception());
}
if(evt.isConsumed())
return;
if(Debug.DUMP_KEY_EVENTS)
{
Log.log(Log.DEBUG,this,"Key event (preprocessing) : "
+ toString(evt));
}
evt = KeyEventWorkaround.processKeyEvent(evt);
if(evt == null)
return;
if(Debug.DUMP_KEY_EVENTS)
{
Log.log(Log.DEBUG,this,"Key event after workaround: "
+ toString(evt) + " from " + from);
}
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(keyEventInterceptor != null)
keyEventInterceptor.keyTyped(evt);
else if(isPrefixActive() || textArea.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()))
{
processKeyEventKeyStrokeHandling(evt,from,"press",global);
processKeyEventSub(focusOnTextArea);
}
break;
case KeyEvent.KEY_RELEASED:
if(keyEventInterceptor != null)
keyEventInterceptor.keyReleased(evt);
break;
}
} //}}}
//{{{ processKeyEventSub() method
private void processKeyEventSub(boolean focusOnTextArea)
{
// 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() && focusOnTextArea)
{
textArea.requestFocus();
}
} //}}}
//{{{ getAction() method
protected abstract JEditBeanShellAction getAction(String action);
//}}}
//{{{ 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(getAction(action));
} //}}}
//{{{ invokeAction() method
/**
* Invokes the specified action, repeating and recording it as
* necessary.
* @param action The action
*/
@Override
public void invokeAction(JEditBeanShellAction action)
{
JEditBuffer buffer = textArea.getBuffer();
/* if(buffer.insideCompoundEdit())
buffer.endCompoundEdit(); */
// remember the last executed action
if(!action.noRememberLast())
{
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(textArea);
else
{
try
{
buffer.beginCompoundEdit();
for(int i = 0; i < _repeatCount; i++)
action.invoke(textArea);
}
finally
{
buffer.endCompoundEdit();
}
}
// 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;
}
} //}}}
//{{{ handleKey() method
/**
* Handles the given keystroke.
* @param keyStroke The key stroke
* @param dryRun only calculate the return value, do not have any other effect
* @since jEdit 4.2pre5
*/
@Override
public boolean handleKey(KeyEventTranslator.Key keyStroke,boolean dryRun)
{
char input = '\0';
if(keyStroke.modifiers == null
|| keyStroke.modifiers.equals("S"))
{
switch(keyStroke.key)
{
case '\n':
case '\t':
input = (char)keyStroke.key;
break;
default:
input = keyStroke.input;
break;
}
}
if(readNextChar != null)
{
if(input != '\0')
{
if (!dryRun)
{
setCurrentBindings(bindings);
invokeReadNextChar(input);
repeatCount = 1;
}
return true;
}
else
{
if (!dryRun)
{
readNextChar = null;
}
}
}
Object o = currentBindings.get(keyStroke);
if(o == null)
{
if (!dryRun)
{
// Don't beep if the user presses some
// key we don't know about unless a
// prefix is active. Otherwise it will
// beep when caps lock is pressed, etc.
if(currentBindings != bindings)
{
javax.swing.UIManager.getLookAndFeel().provideErrorFeedback(null);
// F10 should be passed on, but C+e F10
// shouldn't
repeatCount = 1;
setCurrentBindings(bindings);
}
else if(input != '\0')
{
if (!keyStroke.isFromGlobalContext())
{ // let user input be only local
userInput(input);
}
}
sendShortcutPrefixOff();
}
}
else if(o instanceof Hashtable)
{
if (!dryRun)
{
setCurrentBindings((Hashtable)o);
ShortcutPrefixActiveEvent.firePrefixStateChange(currentBindings, true);
shortcutOn = true;
}
return true;
}
else if(o instanceof String)
{
if (!dryRun)
{
setCurrentBindings(bindings);
sendShortcutPrefixOff();
invokeAction((String)o);
}
return true;
}
else if(o instanceof JEditBeanShellAction)
{
if (!dryRun)
{
setCurrentBindings(bindings);
sendShortcutPrefixOff();
invokeAction((JEditBeanShellAction)o);
}
return true;
}
if (!dryRun)
{
sendShortcutPrefixOff();
}
return false;
} //}}}
//{{{ userInput() method
protected void userInput(char ch)
{
lastActionCount = 0;
if(repeatCount == 1)
textArea.userInput(ch);
repeatCount = 1;
} //}}}
//{{{ invokeReadNextChar() method
protected void invokeReadNextChar(char ch)
{
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);
}
readNextChar = null;
} //}}}
}