/* * 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 . * * Copyright (C) 2010-2015 Martin */ package com.googlecode.lanterna.gui2; import com.googlecode.lanterna.TerminalPosition; import com.googlecode.lanterna.TerminalSize; import com.googlecode.lanterna.input.KeyStroke; import com.googlecode.lanterna.input.KeyType; import com.googlecode.lanterna.input.MouseAction; /** * This abstract implementation of {@code BasePane} has the common code shared by all different concrete * implementations. */ public abstract class AbstractBasePane implements BasePane { protected final ContentHolder contentHolder; protected InteractableLookupMap interactableLookupMap; private Interactable focusedInteractable; private boolean invalid; private boolean strictFocusChange; private boolean enableDirectionBasedMovements; protected AbstractBasePane() { this.contentHolder = new ContentHolder(); this.interactableLookupMap = new InteractableLookupMap(new TerminalSize(80, 25)); this.invalid = false; this.strictFocusChange = false; this.enableDirectionBasedMovements = true; } @Override public boolean isInvalid() { return invalid || contentHolder.isInvalid(); } @Override public void invalidate() { invalid = true; //Propagate contentHolder.invalidate(); } @Override public void draw(TextGUIGraphics graphics) { graphics.applyThemeStyle(graphics.getThemeDefinition(Window.class).getNormal()); graphics.fill(' '); contentHolder.draw(graphics); if(!interactableLookupMap.getSize().equals(graphics.getSize())) { interactableLookupMap = new InteractableLookupMap(graphics.getSize()); } else { interactableLookupMap.reset(); } contentHolder.updateLookupMap(interactableLookupMap); //interactableLookupMap.debug(); invalid = false; } @Override public boolean handleInput(KeyStroke key) { if(key.getKeyType() == KeyType.MouseEvent) { MouseAction mouseAction = (MouseAction)key; TerminalPosition localCoordinates = fromGlobal(mouseAction.getPosition()); Interactable interactable = interactableLookupMap.getInteractableAt(localCoordinates); interactable.handleInput(key); } else if(focusedInteractable != null) { Interactable next = null; Interactable.FocusChangeDirection direction = Interactable.FocusChangeDirection.TELEPORT; //Default Interactable.Result result = focusedInteractable.handleInput(key); if(!enableDirectionBasedMovements) { if(result == Interactable.Result.MOVE_FOCUS_DOWN || result == Interactable.Result.MOVE_FOCUS_RIGHT) { result = Interactable.Result.MOVE_FOCUS_NEXT; } else if(result == Interactable.Result.MOVE_FOCUS_UP || result == Interactable.Result.MOVE_FOCUS_LEFT) { result = Interactable.Result.MOVE_FOCUS_PREVIOUS; } } switch (result) { case HANDLED: return true; case UNHANDLED: //Filter the event recursively through all parent containers until we hit null; give the containers //a chance to absorb the event Container parent = focusedInteractable.getParent(); while(parent != null) { if(parent.handleInput(key)) { return true; } parent = parent.getParent(); } return false; case MOVE_FOCUS_NEXT: next = contentHolder.nextFocus(focusedInteractable); if(next == null) { next = contentHolder.nextFocus(null); } direction = Interactable.FocusChangeDirection.NEXT; break; case MOVE_FOCUS_PREVIOUS: next = contentHolder.previousFocus(focusedInteractable); if(next == null) { next = contentHolder.previousFocus(null); } direction = Interactable.FocusChangeDirection.PREVIOUS; break; case MOVE_FOCUS_DOWN: next = interactableLookupMap.findNextDown(focusedInteractable); direction = Interactable.FocusChangeDirection.DOWN; if(next == null && !strictFocusChange) { next = contentHolder.nextFocus(focusedInteractable); direction = Interactable.FocusChangeDirection.NEXT; } break; case MOVE_FOCUS_LEFT: next = interactableLookupMap.findNextLeft(focusedInteractable); direction = Interactable.FocusChangeDirection.LEFT; break; case MOVE_FOCUS_RIGHT: next = interactableLookupMap.findNextRight(focusedInteractable); direction = Interactable.FocusChangeDirection.RIGHT; break; case MOVE_FOCUS_UP: next = interactableLookupMap.findNextUp(focusedInteractable); direction = Interactable.FocusChangeDirection.UP; if(next == null && !strictFocusChange) { next = contentHolder.previousFocus(focusedInteractable); direction = Interactable.FocusChangeDirection.PREVIOUS; } break; } if(next != null) { setFocusedInteractable(next, direction); } return true; } return false; } @Override public Component getComponent() { return contentHolder.getComponent(); } @Override public void setComponent(Component component) { contentHolder.setComponent(component); } @Override public Interactable getFocusedInteractable() { return focusedInteractable; } @Override public TerminalPosition getCursorPosition() { if(focusedInteractable == null) { return null; } TerminalPosition position = focusedInteractable.getCursorLocation(); if(position == null) { return null; } //Don't allow the component to set the cursor outside of its own boundaries if(position.getColumn() < 0 || position.getRow() < 0 || position.getColumn() >= focusedInteractable.getSize().getColumns() || position.getRow() >= focusedInteractable.getSize().getRows()) { return null; } return focusedInteractable.toBasePane(position); } @Override public void setFocusedInteractable(Interactable toFocus) { setFocusedInteractable(toFocus, toFocus != null ? Interactable.FocusChangeDirection.TELEPORT : Interactable.FocusChangeDirection.RESET); } protected void setFocusedInteractable(Interactable toFocus, Interactable.FocusChangeDirection direction) { if(focusedInteractable == toFocus) { return; } if(focusedInteractable != null) { focusedInteractable.onLeaveFocus(direction, focusedInteractable); } Interactable previous = focusedInteractable; focusedInteractable = toFocus; if(toFocus != null) { toFocus.onEnterFocus(direction, previous); } invalidate(); } @Override public void setStrictFocusChange(boolean strictFocusChange) { this.strictFocusChange = strictFocusChange; } @Override public void setEnableDirectionBasedMovements(boolean enableDirectionBasedMovements) { this.enableDirectionBasedMovements = enableDirectionBasedMovements; } protected class ContentHolder extends AbstractComposite { @Override public void setComponent(Component component) { if(getComponent() == component) { return; } setFocusedInteractable(null); super.setComponent(component); if(focusedInteractable == null && component instanceof Interactable) { setFocusedInteractable((Interactable)component); } else if(focusedInteractable == null && component instanceof Container) { setFocusedInteractable(((Container)component).nextFocus(null)); } } public boolean removeComponent(Component component) { boolean removed = super.removeComponent(component); if (removed) { focusedInteractable = null; } return removed; } @Override public TextGUI getTextGUI() { return AbstractBasePane.this.getTextGUI(); } @Override protected ComponentRenderer createDefaultRenderer() { return new ComponentRenderer() { @Override public TerminalSize getPreferredSize(Container component) { Component subComponent = getComponent(); if(subComponent == null) { return TerminalSize.ZERO; } return subComponent.getPreferredSize(); } @Override public void drawComponent(TextGUIGraphics graphics, Container component) { Component subComponent = getComponent(); if(subComponent == null) { return; } subComponent.draw(graphics); } }; } @Override public TerminalPosition toGlobal(TerminalPosition position) { return AbstractBasePane.this.toGlobal(position); } @Override public TerminalPosition toBasePane(TerminalPosition position) { return position; } @Override public BasePane getBasePane() { return AbstractBasePane.this; } } }