/* * 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-2012 Martin */ package com.googlecode.lanterna.gui; import com.googlecode.lanterna.gui.Theme.Category; import com.googlecode.lanterna.gui.component.EmptySpace; import com.googlecode.lanterna.gui.component.Panel; import com.googlecode.lanterna.gui.layout.LayoutParameter; import com.googlecode.lanterna.gui.listener.ComponentAdapter; import com.googlecode.lanterna.gui.listener.ContainerListener; import com.googlecode.lanterna.gui.listener.WindowListener; import com.googlecode.lanterna.input.Key; import com.googlecode.lanterna.terminal.TerminalPosition; import com.googlecode.lanterna.terminal.TerminalSize; import java.util.ArrayList; import java.util.List; /** * The Window class is the basis for Lanternas GUI system. The workflow is to * create and show modal windows and promt the user for input. A window, once * displayed, will take over control by entering an event loop and won't come * back until the window is closed. * @author Martin */ public class Window { private final List<WindowListener> windowListeners; private final List<ComponentInvalidatorAlert> invalidatorAlerts; private GUIScreen owner; private final WindowContentPane contentPane; private Interactable currentlyInFocus; private TerminalSize windowSizeOverride; private boolean soloWindow; /** * Creates a new window * @param title Title for the new window */ public Window(String title) { this.windowListeners = new ArrayList<WindowListener>(); this.invalidatorAlerts = new ArrayList<ComponentInvalidatorAlert>(); this.owner = null; this.contentPane = new WindowContentPane(title); this.currentlyInFocus = null; this.soloWindow = false; this.windowSizeOverride = null; } public void addWindowListener(WindowListener listener) { windowListeners.add(listener); } /** * @return The GUIScreen which this window is displayed on */ public GUIScreen getOwner() { return owner; } void setOwner(GUIScreen owner) { this.owner = owner; } /** * @return The border of this window */ public Border getBorder() { return contentPane.getBorder(); } public void setBorder(Border border) { if(border != null) contentPane.setBorder(border); } /** * Returns the current window size override or {@code null} if none is set * @return Override for the window size or {@code null} */ public TerminalSize getWindowSizeOverride() { return windowSizeOverride; } /** * Sets the size of the window to a fixed value, rather than using automatic size calculation. * If you call this method with {@code windowSizeOverride} parameter set to {@code null}, the * automatic size calculation will be activated again. * @param windowSizeOverride Size the window should have, or {@code null} if you want to use * automatic size calculation */ public void setWindowSizeOverride(TerminalSize windowSizeOverride) { this.windowSizeOverride = windowSizeOverride; } /** * @return How big this window would like to be */ TerminalSize getPreferredSize() { if(windowSizeOverride != null) return windowSizeOverride; else return contentPane.getPreferredSize(); //Automatically calculate the size } void repaint(TextGraphics graphics) { graphics.applyTheme(graphics.getTheme().getDefinition(Category.DIALOG_AREA)); graphics.fillRectangle(' ', new TerminalPosition(0, 0), new TerminalSize(graphics.getWidth(), graphics.getHeight())); contentPane.repaint(graphics); } private void invalidate() { for(WindowListener listener: windowListeners) listener.onWindowInvalidated(this); } /** * Don't use the method, call {@code addComponent(new EmptySpace(1, 1))} instead * @deprecated */ @Deprecated public void addEmptyLine() { addComponent(new EmptySpace(1, 1)); } public void addComponent(Component component, LayoutParameter... layoutParameters) { if(component == null) return; contentPane.addComponent(component, layoutParameters); ComponentInvalidatorAlert invalidatorAlert = new ComponentInvalidatorAlert(component); invalidatorAlerts.add(invalidatorAlert); component.addComponentListener(invalidatorAlert); if(currentlyInFocus == null) setFocus(contentPane.nextFocus(null)); invalidate(); } public void addContainerListener(ContainerListener cl) { contentPane.addContainerListener(cl); } public void removeContainerListener(ContainerListener cl) { contentPane.removeContainerListener(cl); } public Component getComponentAt(int index) { return contentPane.getComponentAt(index); } /** * @return How many top-level components this window has */ public int getComponentCount() { return contentPane.getComponentCount(); } /** * Removes a top-level component from the window * @param component Top-level component to remove */ public void removeComponent(Component component) { if(component instanceof InteractableContainer) { InteractableContainer container = (InteractableContainer)component; if(container.hasInteractable(currentlyInFocus)) { Interactable original = currentlyInFocus; Interactable current = contentPane.nextFocus(original); while(container.hasInteractable(current) && original != current) current = contentPane.nextFocus(current); if(container.hasInteractable(current)) setFocus(null); else setFocus(current); } } else if(component == currentlyInFocus) setFocus(contentPane.nextFocus(currentlyInFocus)); contentPane.removeComponent(component); for(ComponentInvalidatorAlert invalidatorAlert: invalidatorAlerts) { if(component == invalidatorAlert.component) { component.removeComponentListener(invalidatorAlert); invalidatorAlerts.remove(invalidatorAlert); break; } } } /** * Removes all components from the window */ public void removeAllComponents() { while(getComponentCount() > 0) removeComponent(getComponentAt(0)); } TerminalPosition getWindowHotspotPosition() { if(currentlyInFocus == null) return null; else return currentlyInFocus.getHotspot(); } void onKeyPressed(Key key) { if(currentlyInFocus != null) { Interactable.Result result = currentlyInFocus.keyboardInteraction(key); if(result.isNextInteractable()) { Interactable nextItem = contentPane.nextFocus(currentlyInFocus); if(nextItem == null) nextItem = contentPane.nextFocus(null); setFocus(nextItem, result.asFocusChangeDirection()); } else if(result.isPreviousInteractable()) { Interactable prevItem = contentPane.previousFocus(currentlyInFocus); if(prevItem == null) prevItem = contentPane.previousFocus(null); setFocus(prevItem, result.asFocusChangeDirection()); } else if(result == Interactable.Result.EVENT_NOT_HANDLED) { //Try to find a shortcut if(currentlyInFocus instanceof Component) { Container parentContainer = ((Component)currentlyInFocus).getParent(); while(parentContainer != null) { if(parentContainer instanceof InteractableContainer) { //If we could fire off a shortcut, stop here if(((InteractableContainer)parentContainer).triggerShortcut(key)) return; } //Otherwise try one level higher parentContainer = parentContainer.getParent(); } } //There was no shortcut onUnhandledKeyPress(key); } } else { onUnhandledKeyPress(key); } } private void onUnhandledKeyPress(Key key) { for(WindowListener listener: windowListeners) listener.onUnhandledKeyboardInteraction(this, key); } public boolean isSoloWindow() { return soloWindow; } /** * If set to true, when this window is shown, all previous windows are * hidden. Set to false to show them again. */ public void setSoloWindow(boolean soloWindow) { this.soloWindow = soloWindow; } boolean maximisesVertically() { return contentPane.maximisesVertically(); } boolean maximisesHorisontally() { return contentPane.maximisesHorisontally(); } protected void setFocus(Interactable newFocus) { setFocus(newFocus, null); } protected void setFocus(Interactable newFocus, Interactable.FocusChangeDirection direction) { if(currentlyInFocus != null) currentlyInFocus.onLeaveFocus(direction); //Fire the focus changed event for(WindowListener listener: windowListeners) { listener.onFocusChanged(this, currentlyInFocus, newFocus); } currentlyInFocus = newFocus; if(currentlyInFocus != null) currentlyInFocus.onEnterFocus(direction); invalidate(); } public void close() { if(owner != null) owner.closeWindow(this); } protected void onVisible() { for(WindowListener listener: windowListeners) listener.onWindowShown(this); } protected void onClosed() { for(WindowListener listener: windowListeners) listener.onWindowClosed(this); } @Override public String toString() { return contentPane.getTitle(); } private class ComponentInvalidatorAlert extends ComponentAdapter { private Component component; public ComponentInvalidatorAlert(Component component) { this.component = component; } public Component getComponent() { return component; } @Override public void onComponentInvalidated(Component component) { invalidate(); } } /** * Private class to get through some method restrictions */ private class WindowContentPane extends Panel { public WindowContentPane(String title) { super(title); } @Override public Window getWindow() { return Window.this; } } }