2 * This file is part of lanterna (http://code.google.com/p/lanterna/).
4 * lanterna is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 * Copyright (C) 2010-2015 Martin
19 package com
.googlecode
.lanterna
.gui2
;
21 import com
.googlecode
.lanterna
.TerminalPosition
;
22 import com
.googlecode
.lanterna
.graphics
.PropertiesTheme
;
23 import com
.googlecode
.lanterna
.graphics
.Theme
;
24 import com
.googlecode
.lanterna
.input
.KeyStroke
;
25 import com
.googlecode
.lanterna
.input
.KeyType
;
26 import com
.googlecode
.lanterna
.screen
.Screen
;
28 import java
.io
.EOFException
;
29 import java
.io
.FileInputStream
;
31 import java
.io
.IOException
;
32 import java
.io
.InputStream
;
33 import java
.util
.List
;
34 import java
.util
.Properties
;
35 import java
.util
.concurrent
.CopyOnWriteArrayList
;
38 * This abstract implementation of TextGUI contains some basic management of the underlying Screen and other common code
39 * that can be shared between different implementations.
42 public abstract class AbstractTextGUI
implements TextGUI
{
44 private final Screen screen
;
45 private final List
<Listener
> listeners
;
46 private boolean blockingIO
;
47 private boolean dirty
;
48 private TextGUIThread textGUIThread
;
49 private Theme guiTheme
;
52 * Constructor for {@code AbstractTextGUI} that requires a {@code Screen} and a factory for creating the GUI thread
53 * @param textGUIThreadFactory Factory class to use for creating the {@code TextGUIThread} class
54 * @param screen What underlying {@code Screen} to use for this text GUI
56 protected AbstractTextGUI(TextGUIThreadFactory textGUIThreadFactory
, Screen screen
) {
58 throw new IllegalArgumentException("Creating a TextGUI requires an underlying Screen");
61 this.listeners
= new CopyOnWriteArrayList
<Listener
>();
62 this.blockingIO
= false;
64 this.guiTheme
= new PropertiesTheme(loadDefaultThemeProperties());
65 this.textGUIThread
= textGUIThreadFactory
.createTextGUIThread(this);
68 private static Properties
loadDefaultThemeProperties() {
69 Properties properties
= new Properties();
71 ClassLoader classLoader
= AbstractTextGUI
.class.getClassLoader();
72 InputStream resourceAsStream
= classLoader
.getResourceAsStream("default-theme.properties");
73 if(resourceAsStream
== null) {
74 resourceAsStream
= new FileInputStream("src/main/resources/default-theme.properties");
76 properties
.load(resourceAsStream
);
77 resourceAsStream
.close();
80 catch(IOException e
) {
86 * Reads one key from the input queue, blocking or non-blocking depending on if blocking I/O has been enabled. To
87 * enable blocking I/O (disabled by default), use {@code setBlockingIO(true)}.
88 * @return One piece of user input as a {@code KeyStroke} or {@code null} if blocking I/O is disabled and there was
90 * @throws IOException In case of an I/O error while reading input
92 protected KeyStroke
readKeyStroke() throws IOException
{
93 return blockingIO ? screen
.readInput() : pollInput();
97 * Polls the underlying input queue for user input, returning either a {@code KeyStroke} or {@code null}
98 * @return {@code KeyStroke} representing the user input or {@code null} if there was none
99 * @throws IOException In case of an I/O error while reading input
101 protected KeyStroke
pollInput() throws IOException
{
102 return screen
.pollInput();
106 public synchronized boolean processInput() throws IOException
{
107 boolean gotInput
= false;
108 KeyStroke keyStroke
= readKeyStroke();
109 if(keyStroke
!= null) {
112 if (keyStroke
.getKeyType() == KeyType
.EOF
) {
113 throw new EOFException();
115 boolean handled
= handleInput(keyStroke
);
117 handled
= fireUnhandledKeyStroke(keyStroke
);
119 dirty
= handled
|| dirty
;
120 keyStroke
= pollInput();
121 } while(keyStroke
!= null);
127 public void setTheme(Theme theme
) {
128 this.guiTheme
= theme
;
132 public synchronized void updateScreen() throws IOException
{
133 screen
.doResizeIfNecessary();
134 drawGUI(new TextGUIGraphics(this, screen
.newTextGraphics(), guiTheme
));
135 screen
.setCursorPosition(getCursorPosition());
141 public boolean isPendingUpdate() {
142 return screen
.doResizeIfNecessary() != null || dirty
;
146 public TextGUIThread
getGUIThread() {
147 return textGUIThread
;
151 public void addListener(Listener listener
) {
152 listeners
.add(listener
);
156 public void removeListener(Listener listener
) {
157 listeners
.remove(listener
);
161 * Enables blocking I/O, causing calls to {@code readKeyStroke()} to block until there is input available. Notice
162 * that you can still poll for input using {@code pollInput()}.
163 * @param blockingIO Set this to {@code true} if blocking I/O should be enabled, otherwise {@code false}
165 public void setBlockingIO(boolean blockingIO
) {
166 this.blockingIO
= blockingIO
;
170 * Checks if blocking I/O is enabled or not
171 * @return {@code true} if blocking I/O is enabled, otherwise {@code false}
173 public boolean isBlockingIO() {
178 * This method should be called when there was user input that wasn't handled by the GUI. It will fire the
179 * {@code onUnhandledKeyStroke(..)} method on any registered listener.
180 * @param keyStroke The {@code KeyStroke} that wasn't handled by the GUI
181 * @return {@code true} if at least one of the listeners handled the key stroke, this will signal to the GUI that it
182 * needs to be redrawn again.
184 protected final boolean fireUnhandledKeyStroke(KeyStroke keyStroke
) {
185 boolean handled
= false;
186 for(Listener listener
: listeners
) {
187 handled
= listener
.onUnhandledKeyStroke(this, keyStroke
) || handled
;
193 * Marks the whole text GUI as invalid and that it needs to be redrawn at next opportunity
195 protected void invalidate() {
200 * Draws the entire GUI using a {@code TextGUIGraphics} object
201 * @param graphics Graphics object to draw using
203 protected abstract void drawGUI(TextGUIGraphics graphics
);
206 * Top-level method for drilling in to the GUI and figuring out, in global coordinates, where to place the text
207 * cursor on the screen at this time.
208 * @return Where to place the text cursor, or {@code null} if the cursor should be hidden
210 protected abstract TerminalPosition
getCursorPosition();
213 * This method should take the user input and feed it to the focused component for handling.
214 * @param key {@code KeyStroke} representing the user input
215 * @return {@code true} if the input was recognized and handled by the GUI, indicating that the GUI should be redrawn
217 protected abstract boolean handleInput(KeyStroke key
);