Fix UTF8 bug, create first executable JAR file
[jvcard.git] / src / com / googlecode / lanterna / gui2 / AbstractTextGUI.java
... / ...
CommitLineData
1/*
2 * This file is part of lanterna (http://code.google.com/p/lanterna/).
3 *
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.
8 *
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.
13 *
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/>.
16 *
17 * Copyright (C) 2010-2015 Martin
18 */
19package com.googlecode.lanterna.gui2;
20
21import com.googlecode.lanterna.TerminalPosition;
22import com.googlecode.lanterna.graphics.PropertiesTheme;
23import com.googlecode.lanterna.graphics.Theme;
24import com.googlecode.lanterna.input.KeyStroke;
25import com.googlecode.lanterna.input.KeyType;
26import com.googlecode.lanterna.screen.Screen;
27
28import java.io.EOFException;
29import java.io.FileInputStream;
30
31import java.io.IOException;
32import java.io.InputStream;
33import java.util.List;
34import java.util.Properties;
35import java.util.concurrent.CopyOnWriteArrayList;
36
37/**
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.
40 * @author Martin
41 */
42public abstract class AbstractTextGUI implements TextGUI {
43
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;
50
51 /**
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
55 */
56 protected AbstractTextGUI(TextGUIThreadFactory textGUIThreadFactory, Screen screen) {
57 if(screen == null) {
58 throw new IllegalArgumentException("Creating a TextGUI requires an underlying Screen");
59 }
60 this.screen = screen;
61 this.listeners = new CopyOnWriteArrayList<Listener>();
62 this.blockingIO = false;
63 this.dirty = false;
64 this.guiTheme = new PropertiesTheme(loadDefaultThemeProperties());
65 this.textGUIThread = textGUIThreadFactory.createTextGUIThread(this);
66 }
67
68 private static Properties loadDefaultThemeProperties() {
69 Properties properties = new Properties();
70 try {
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");
75 }
76 properties.load(resourceAsStream);
77 resourceAsStream.close();
78 return properties;
79 }
80 catch(IOException e) {
81 return properties;
82 }
83 }
84
85 /**
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
89 * no input waiting
90 * @throws IOException In case of an I/O error while reading input
91 */
92 protected KeyStroke readKeyStroke() throws IOException {
93 return blockingIO ? screen.readInput() : pollInput();
94 }
95
96 /**
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
100 */
101 protected KeyStroke pollInput() throws IOException {
102 return screen.pollInput();
103 }
104
105 @Override
106 public synchronized boolean processInput() throws IOException {
107 boolean gotInput = false;
108 KeyStroke keyStroke = readKeyStroke();
109 if(keyStroke != null) {
110 gotInput = true;
111 do {
112 if (keyStroke.getKeyType() == KeyType.EOF) {
113 throw new EOFException();
114 }
115 boolean handled = handleInput(keyStroke);
116 if(!handled) {
117 handled = fireUnhandledKeyStroke(keyStroke);
118 }
119 dirty = handled || dirty;
120 keyStroke = pollInput();
121 } while(keyStroke != null);
122 }
123 return gotInput;
124 }
125
126 @Override
127 public void setTheme(Theme theme) {
128 this.guiTheme = theme;
129 }
130
131 @Override
132 public synchronized void updateScreen() throws IOException {
133 screen.doResizeIfNecessary();
134 drawGUI(new TextGUIGraphics(this, screen.newTextGraphics(), guiTheme));
135 screen.setCursorPosition(getCursorPosition());
136 screen.refresh();
137 dirty = false;
138 }
139
140 @Override
141 public boolean isPendingUpdate() {
142 return screen.doResizeIfNecessary() != null || dirty;
143 }
144
145 @Override
146 public TextGUIThread getGUIThread() {
147 return textGUIThread;
148 }
149
150 @Override
151 public void addListener(Listener listener) {
152 listeners.add(listener);
153 }
154
155 @Override
156 public void removeListener(Listener listener) {
157 listeners.remove(listener);
158 }
159
160 /**
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}
164 */
165 public void setBlockingIO(boolean blockingIO) {
166 this.blockingIO = blockingIO;
167 }
168
169 /**
170 * Checks if blocking I/O is enabled or not
171 * @return {@code true} if blocking I/O is enabled, otherwise {@code false}
172 */
173 public boolean isBlockingIO() {
174 return blockingIO;
175 }
176
177 /**
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.
183 */
184 protected final boolean fireUnhandledKeyStroke(KeyStroke keyStroke) {
185 boolean handled = false;
186 for(Listener listener: listeners) {
187 handled = listener.onUnhandledKeyStroke(this, keyStroke) || handled;
188 }
189 return handled;
190 }
191
192 /**
193 * Marks the whole text GUI as invalid and that it needs to be redrawn at next opportunity
194 */
195 protected void invalidate() {
196 dirty = true;
197 }
198
199 /**
200 * Draws the entire GUI using a {@code TextGUIGraphics} object
201 * @param graphics Graphics object to draw using
202 */
203 protected abstract void drawGUI(TextGUIGraphics graphics);
204
205 /**
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
209 */
210 protected abstract TerminalPosition getCursorPosition();
211
212 /**
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
216 */
217 protected abstract boolean handleInput(KeyStroke key);
218}