/*
* This file is part of lanterna (http://code.google.com/p/lanterna/).
*
* lanterna is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2010-2017 Martin Berglund
*/
package com.googlecode.lanterna.gui2;
import com.googlecode.lanterna.TerminalPosition;
import com.googlecode.lanterna.input.KeyStroke;
/**
* Default implementation of Interactable that extends from AbstractComponent. If you want to write your own component
* that is interactable, i.e. can receive keyboard (and mouse) input, you probably want to extend from this class as
* it contains some common implementations of the methods from {@code Interactable} interface
* @param <T> Should always be itself, see {@code AbstractComponent}
* @author Martin
*/
public abstract class AbstractInteractableComponent<T extends AbstractInteractableComponent<T>> extends AbstractComponent<T> implements Interactable {
private InputFilter inputFilter;
private boolean inFocus;
private boolean enabled;
/**
* Default constructor
*/
protected AbstractInteractableComponent() {
inputFilter = null;
inFocus = false;
enabled = true;
}
@Override
public T takeFocus() {
if(!isEnabled()) {
return self();
}
BasePane basePane = getBasePane();
if(basePane != null) {
basePane.setFocusedInteractable(this);
}
return self();
}
/**
* {@inheritDoc}
* <p>
* This method is final in {@code AbstractInteractableComponent}, please override {@code afterEnterFocus} instead
*/
@Override
public final void onEnterFocus(FocusChangeDirection direction, Interactable previouslyInFocus) {
inFocus = true;
afterEnterFocus(direction, previouslyInFocus);
}
/**
* Called by {@code AbstractInteractableComponent} automatically after this component has received input focus. You
* can override this method if you need to trigger some action based on this.
* @param direction How focus was transferred, keep in mind this is from the previous component's point of view so
* if this parameter has value DOWN, focus came in from above
* @param previouslyInFocus Which interactable component had focus previously
*/
@SuppressWarnings("EmptyMethod")
protected void afterEnterFocus(FocusChangeDirection direction, Interactable previouslyInFocus) {
//By default no action
}
/**
* {@inheritDoc}
* <p>
* This method is final in {@code AbstractInteractableComponent}, please override {@code afterLeaveFocus} instead
*/
@Override
public final void onLeaveFocus(FocusChangeDirection direction, Interactable nextInFocus) {
inFocus = false;
afterLeaveFocus(direction, nextInFocus);
}
/**
* Called by {@code AbstractInteractableComponent} automatically after this component has lost input focus. You
* can override this method if you need to trigger some action based on this.
* @param direction How focus was transferred, keep in mind this is from the this component's point of view so
* if this parameter has value DOWN, focus is moving down to a component below
* @param nextInFocus Which interactable component is going to receive focus
*/
@SuppressWarnings("EmptyMethod")
protected void afterLeaveFocus(FocusChangeDirection direction, Interactable nextInFocus) {
//By default no action
}
@Override
protected abstract InteractableRenderer<T> createDefaultRenderer();
@Override
public InteractableRenderer<T> getRenderer() {
return (InteractableRenderer<T>)super.getRenderer();
}
@Override
public boolean isFocused() {
return inFocus;
}
@Override
public synchronized T setEnabled(boolean enabled) {
this.enabled = enabled;
if(!enabled && isFocused()) {
BasePane basePane = getBasePane();
if(basePane != null) {
basePane.setFocusedInteractable(null);
}
}
return self();
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public boolean isFocusable() {
return true;
}
@Override
public final synchronized Result handleInput(KeyStroke keyStroke) {
if(inputFilter == null || inputFilter.onInput(this, keyStroke)) {
return handleKeyStroke(keyStroke);
}
else {
return Result.UNHANDLED;
}
}
/**
* This method can be overridden to handle various user input (mostly from the keyboard) when this component is in
* focus. The input method from the interface, {@code handleInput(..)} is final in
* {@code AbstractInteractableComponent} to ensure the input filter is properly handled. If the filter decides that
* this event should be processed, it will call this method.
* @param keyStroke What input was entered by the user
* @return Result of processing the key-stroke
*/
protected Result handleKeyStroke(KeyStroke keyStroke) {
// Skip the keystroke if ctrl, alt or shift was down
if(!keyStroke.isAltDown() && !keyStroke.isCtrlDown() && !keyStroke.isShiftDown()) {
switch(keyStroke.getKeyType()) {
case ArrowDown:
return Result.MOVE_FOCUS_DOWN;
case ArrowLeft:
return Result.MOVE_FOCUS_LEFT;
case ArrowRight:
return Result.MOVE_FOCUS_RIGHT;
case ArrowUp:
return Result.MOVE_FOCUS_UP;
case Tab:
return Result.MOVE_FOCUS_NEXT;
case ReverseTab:
return Result.MOVE_FOCUS_PREVIOUS;
case MouseEvent:
getBasePane().setFocusedInteractable(this);
return Result.HANDLED;
default:
}
}
return Result.UNHANDLED;
}
@Override
public TerminalPosition getCursorLocation() {
return getRenderer().getCursorLocation(self());
}
@Override
public InputFilter getInputFilter() {
return inputFilter;
}
@Override
public synchronized T setInputFilter(InputFilter inputFilter) {
this.inputFilter = inputFilter;
return self();
}
}