/**
* Copyright (c) 2009, 2010 Mark Feber, MulgaSoft
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
*/
package com.mulgasoft.emacsplus.minibuffer;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.jface.bindings.Binding;
import org.eclipse.jface.bindings.Trigger;
import org.eclipse.jface.bindings.keys.KeySequence;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.keys.IBindingService;
import org.eclipse.ui.texteditor.ITextEditor;
import com.mulgasoft.emacsplus.RingBuffer;
import com.mulgasoft.emacsplus.execute.IBindingResult;
/**
* Handle key input and determine binding information about it
*
* Some info gleaned from: org.eclipse.jface.bindings.keys.KeySequenceText
*
* @author Mark Feber - initial API and implementation
*/
public class KeyHandlerMinibuffer extends ExecutingMinibuffer {
private String prefix = null;
private List<Trigger> keys = new ArrayList<Trigger>();
private Binding binding = null;
private KeySequence trigger = null;
private int keyCharacter = 0;
private IBindingService bindingService;
/**
* @see com.mulgasoft.emacsplus.minibuffer.WithMinibuffer#beginSession(ITextEditor, IWorkbenchPage, ExecutionEvent)
*/
@Override
public boolean beginSession(ITextEditor editor, IWorkbenchPage page, ExecutionEvent event) {
bindingService = (IBindingService) PlatformUI.getWorkbench().getService(IBindingService.class);
return super.beginSession(editor, page, event);
}
/**
* @return the keyCharacter
*/
protected int getKeyCharacter() {
return keyCharacter;
}
/**
* @param keyCharacter the keyCharacter to set
*/
protected void setKeyCharacter(int keyCharacter) {
this.keyCharacter = keyCharacter;
}
/**
* @return the binding
*/
protected Binding getBinding() {
return binding;
}
/**
* @param binding the binding to set
*/
protected void setBinding(Binding binding) {
this.binding = binding;
}
/**
* @return the trigger
*/
protected KeySequence getTrigger() {
return trigger;
}
/**
* @param trigger the trigger to set
*/
protected void setTrigger(KeySequence trigger) {
this.trigger = trigger;
}
protected boolean hasTrigger() {
return getTrigger() != null;
}
/**
* Reset state of keys processed so far
*/
protected void resetKeys() {
trigger = null;
keys = new ArrayList<Trigger>();
}
public String getMinibufferPrefix() {
if (prefix == null) {
prefix = super.getMinibufferPrefix();
}
return prefix;
}
protected void setMinibufferPrefix(String nextfix) {
this.prefix = super.getMinibufferPrefix() + nextfix;
}
/**
* @param executable
*/
public KeyHandlerMinibuffer(IMinibufferExecutable executable) {
super(executable);
}
/**
* @see com.mulgasoft.emacsplus.minibuffer.HistoryMinibuffer#getHistoryRing()
*/
@Override
protected RingBuffer<Object> getHistoryRing() {
return null;
}
/**
* @see com.mulgasoft.emacsplus.minibuffer.WithMinibuffer#handlesCtrl()
*/
@Override
protected boolean handlesCtrl() {
return true;
}
/**
* @see com.mulgasoft.emacsplus.minibuffer.WithMinibuffer#handlesTab()
*/
@Override
protected boolean handlesTab() {
return true;
}
/**
* @see com.mulgasoft.emacsplus.minibuffer.WithMinibuffer#charEvent(org.eclipse.swt.events.VerifyEvent)
*/
protected void charEvent(VerifyEvent event) {
if (processKey(event)) {
leave();
}
}
/**
* @see com.mulgasoft.emacsplus.minibuffer.WithMinibuffer#noCharEvent(org.eclipse.swt.events.VerifyEvent)
*/
protected void noCharEvent(VerifyEvent event) {
// process if unicode or more than just modifier keys
if (((event.keyCode & SWT.KEYCODE_BIT) != 0) || (event.keyCode & SWT.MODIFIER_MASK) == 0) {
if (processKey(event)) {
leave();
}
} else {
event.doit = false;
}
}
private boolean processKey(VerifyEvent event) {
return processKey(checkKey(event), event.keyCode);
}
protected boolean processKey(boolean checked, int keyCode) {
boolean result = checked;
final String triggerString = ((trigger != null) ? trigger.format() : String.valueOf(keyCode));
setMinibufferPrefix(triggerString);
updateStatusLine(EMPTY_STR);
if (result) {
if (binding != null) {
// we have an exact match
result = executeResult(getEditor(), getResult(binding,trigger,triggerString));
} else if (trigger != null) {
result = false;
}
} else {
result = executeResult(getEditor(), getResult(null,trigger, triggerString));
}
return result;
}
private boolean checkKey(VerifyEvent event) {
event.doit = false;
return checkKey(event.stateMask, event.keyCode, event.character);
}
/**
* Check if the (accumulated) key strokes have a single binding
*
* @param state
* @param keyCode
* @param character
*
* @return true if most unique binding, else false
*/
protected boolean checkKey(int state, int keyCode, int character) {
boolean result = true;
keys.add(getKey(state,keyCode,character));
trigger = KeySequence.getInstance(keys);
binding = bindingService.getPerfectMatch(trigger);
boolean partial = bindingService.isPartialMatch(trigger);
if (binding == null) {
if (!partial) {
keyCharacter = character;
result = false;
}
} else if (partial) {
// keep looking when there are additional partial matches
binding = null;
}
return result;
}
private KeyStroke getKey(int state, int keyCode,int character) {
int result = keyCode;
if (state ==0 && keys.size() == 0) {
result = keyCode;
} else if (state == SWT.SHIFT) {
// handle characters with different shift values
state = 0;
result = character;
} else {
result = Character.toUpperCase(keyCode);
}
return KeyStroke.getInstance(state,result);
}
protected IBindingResult getResult(final Binding binding, final KeySequence trigger, final String triggerString) {
return new IBindingResult() {
public Binding getKeyBinding() { return binding; }
public String getKeyString() { return triggerString; }
public KeySequence getTrigger() { return trigger; }
};
}
private Traverser traverser = new Traverser();
/**
* @see com.mulgasoft.emacsplus.minibuffer.HistoryMinibuffer#addOtherListeners(IWorkbenchPage, ISourceViewer, StyledText)
*/
protected void addOtherListeners(IWorkbenchPage page, ISourceViewer viewer, StyledText widget) {
Display.getCurrent().addFilter(SWT.Traverse, traverser);
}
/**
* @see com.mulgasoft.emacsplus.minibuffer.HistoryMinibuffer#removeOtherListeners(IWorkbenchPage, ISourceViewer, StyledText)
*/
protected void removeOtherListeners(IWorkbenchPage page, ISourceViewer viewer, StyledText widget) {
Display.getCurrent().removeFilter(SWT.Traverse, traverser);
}
private class Traverser implements Listener {
public void handleEvent(Event event) {
if (processKey(checkKey(event.stateMask, event.keyCode, event.character),event.keyCode)) {
leave();
}
// setting detail to NONE but doit=true disables further processing
event.type = SWT.None;
event.detail = SWT.TRAVERSE_NONE;
event.doit = true;
}
}
}