Fix UTF8 bug, create first executable JAR file
[jvcard.git] / src / com / googlecode / lanterna / gui2 / AbstractComponent.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.TerminalSize;
23
24/**
25 * AbstractComponent provides some good default behaviour for a {@code Component}, all components in Lanterna extends
26 * from this class in some way. If you want to write your own component that isn't interactable or theme:able, you
27 * probably want to extend from this class.
28 * <p>
29 * The way you want to declare your new {@code Component} is to pass in itself as the generic parameter, like this:
30 * <pre>
31 * {@code
32 * public class MyComponent extends AbstractComponent<MyComponent> {
33 * ...
34 * }
35 * }
36 * </pre>
37 * This was, the component renderer will be correctly setup type-wise and you will need to do fewer typecastings when
38 * you implement the drawing method your new component.
39 *
40 * @author Martin
41 * @param <T> Should always be itself, this value will be used for the {@code ComponentRenderer} declaration
42 */
43public abstract class AbstractComponent<T extends Component> implements Component {
44 private ComponentRenderer<T> renderer;
45 private Container parent;
46 private TerminalSize size;
47 private TerminalSize explicitPreferredSize; //This is keeping the value set by the user (if setPreferredSize() is used)
48 private TerminalPosition position;
49 private LayoutData layoutData;
50 private boolean invalid;
51
52 /**
53 * Default constructor
54 */
55 public AbstractComponent() {
56 size = TerminalSize.ZERO;
57 position = TerminalPosition.TOP_LEFT_CORNER;
58 explicitPreferredSize = null;
59 layoutData = null;
60 invalid = true;
61 parent = null;
62 renderer = null; //Will be set on the first call to getRenderer()
63 }
64
65 /**
66 * When you create a custom component, you need to implement this method and return a Renderer which is responsible
67 * for taking care of sizing the component, rendering it and choosing where to place the cursor (if Interactable).
68 * This value is intended to be overridden by custom themes.
69 * @return Renderer to use when sizing and drawing this component
70 */
71 protected abstract ComponentRenderer<T> createDefaultRenderer();
72
73 /**
74 * This will attempt to dynamically construct a {@code ComponentRenderer} class from a string, assumed to be passed
75 * in from a theme. This makes it possible to create themes that supplies their own {@code ComponentRenderers} that
76 * can even replace the ones built into lanterna and used for the bundled components.
77 *
78 * @param className Fully qualified name of the {@code ComponentRenderer} we want to instatiate
79 * @return {@code null} if {@code className} was null, otherwise the {@code ComponentRenderer} instance
80 * @throws RuntimeException If there were any problems instatiating the class
81 */
82 @SuppressWarnings("unchecked")
83 protected ComponentRenderer<T> getRendererFromTheme(String className) {
84 if(className == null) {
85 return null;
86 }
87 try {
88 return (ComponentRenderer<T>)Class.forName(className).newInstance();
89 } catch (InstantiationException e) {
90 throw new RuntimeException(e);
91 } catch (IllegalAccessException e) {
92 throw new RuntimeException(e);
93 } catch (ClassNotFoundException e) {
94 throw new RuntimeException(e);
95 }
96 }
97
98 /**
99 * Takes a {@code Runnable} and immediately executes it if this is called on the designated GUI thread, otherwise
100 * schedules it for later invocation.
101 * @param runnable {@code Runnable} to execute on the GUI thread
102 */
103 protected void runOnGUIThreadIfExistsOtherwiseRunDirect(Runnable runnable) {
104 if(getTextGUI() != null && getTextGUI().getGUIThread() != null) {
105 getTextGUI().getGUIThread().invokeLater(runnable);
106 }
107 else {
108 runnable.run();
109 }
110 }
111
112 /**
113 * Explicitly sets the {@code ComponentRenderer} to be used when drawing this component.
114 * @param renderer {@code ComponentRenderer} to be used when drawing this component
115 * @return Itself
116 */
117 public T setRenderer(ComponentRenderer<T> renderer) {
118 this.renderer = renderer;
119 return self();
120 }
121
122 @Override
123 public synchronized ComponentRenderer<T> getRenderer() {
124 if(renderer == null) {
125 renderer = createDefaultRenderer();
126 if(renderer == null) {
127 throw new IllegalStateException(getClass() + " returns a null default renderer");
128 }
129 }
130 return renderer;
131 }
132
133 @Override
134 public void invalidate() {
135 invalid = true;
136 }
137
138 @Override
139 public synchronized T setSize(TerminalSize size) {
140 this.size = size;
141 return self();
142 }
143
144 @Override
145 public TerminalSize getSize() {
146 return size;
147 }
148
149 @Override
150 public final TerminalSize getPreferredSize() {
151 if(explicitPreferredSize != null) {
152 return explicitPreferredSize;
153 }
154 else {
155 return calculatePreferredSize();
156 }
157 }
158
159 @Override
160 public final synchronized T setPreferredSize(TerminalSize explicitPreferredSize) {
161 this.explicitPreferredSize = explicitPreferredSize;
162 return self();
163 }
164
165 /**
166 * Invokes the component renderer's size calculation logic and returns the result. This value represents the
167 * preferred size and isn't necessarily what it will eventually be assigned later on.
168 * @return Size that the component renderer believes the component should be
169 */
170 protected synchronized TerminalSize calculatePreferredSize() {
171 return getRenderer().getPreferredSize(self());
172 }
173
174 @Override
175 public synchronized T setPosition(TerminalPosition position) {
176 this.position = position;
177 return self();
178 }
179
180 @Override
181 public TerminalPosition getPosition() {
182 return position;
183 }
184
185 @Override
186 public boolean isInvalid() {
187 return invalid;
188 }
189
190 @Override
191 public final synchronized void draw(final TextGUIGraphics graphics) {
192 if(getRenderer() == null) {
193 ComponentRenderer<T> renderer = getRendererFromTheme(graphics.getThemeDefinition(getClass()).getRenderer());
194 if(renderer == null) {
195 renderer = createDefaultRenderer();
196 if(renderer == null) {
197 throw new IllegalStateException(getClass() + " returned a null default renderer");
198 }
199 }
200 setRenderer(renderer);
201 }
202 //Delegate drawing the component to the renderer
203 setSize(graphics.getSize());
204 onBeforeDrawing();
205 getRenderer().drawComponent(graphics, self());
206 onAfterDrawing(graphics);
207 invalid = false;
208 }
209
210 /**
211 * This method is called just before the component's renderer is invoked for the drawing operation. You can use this
212 * hook to do some last-minute adjustments to the component, as an alternative to coding it into the renderer
213 * itself. The component should have the correct size and position at this point, if you call {@code getSize()} and
214 * {@code getPosition()}.
215 */
216 protected void onBeforeDrawing() {
217 //No operation by default
218 }
219
220 /**
221 * This method is called immediately after the component's renderer has finished the drawing operation. You can use
222 * this hook to do some post-processing if you need, as an alternative to coding it into the renderer. The
223 * {@code TextGUIGraphics} supplied is the same that was fed into the renderer.
224 * @param graphics Graphics object you can use to manipulate the appearance of the component
225 */
226 protected void onAfterDrawing(TextGUIGraphics graphics) {
227 //No operation by default
228 }
229
230 @Override
231 public synchronized T setLayoutData(LayoutData data) {
232 if(layoutData != data) {
233 layoutData = data;
234 invalidate();
235 }
236 return self();
237 }
238
239 @Override
240 public LayoutData getLayoutData() {
241 return layoutData;
242 }
243
244 @Override
245 public Container getParent() {
246 return parent;
247 }
248
249 @Override
250 public boolean hasParent(Container parent) {
251 if(this.parent == null) {
252 return false;
253 }
254 Container recursiveParent = this.parent;
255 while(recursiveParent != null) {
256 if(recursiveParent == parent) {
257 return true;
258 }
259 recursiveParent = recursiveParent.getParent();
260 }
261 return false;
262 }
263
264 @Override
265 public TextGUI getTextGUI() {
266 if(parent == null) {
267 return null;
268 }
269 return parent.getTextGUI();
270 }
271
272 @Override
273 public boolean isInside(Container container) {
274 Component test = this;
275 while(test.getParent() != null) {
276 if(test.getParent() == container) {
277 return true;
278 }
279 test = test.getParent();
280 }
281 return false;
282 }
283
284 @Override
285 public BasePane getBasePane() {
286 if(parent == null) {
287 return null;
288 }
289 return parent.getBasePane();
290 }
291
292 @Override
293 public TerminalPosition toBasePane(TerminalPosition position) {
294 Container parent = getParent();
295 if(parent == null) {
296 return null;
297 }
298 return parent.toBasePane(getPosition().withRelative(position));
299 }
300
301 @Override
302 public TerminalPosition toGlobal(TerminalPosition position) {
303 Container parent = getParent();
304 if(parent == null) {
305 return null;
306 }
307 return parent.toGlobal(getPosition().withRelative(position));
308 }
309
310 @Override
311 public synchronized Border withBorder(Border border) {
312 border.setComponent(this);
313 return border;
314 }
315
316 @Override
317 public synchronized T addTo(Panel panel) {
318 panel.addComponent(this);
319 return self();
320 }
321
322 @Override
323 public synchronized void onAdded(Container container) {
324 parent = container;
325 }
326
327 @Override
328 public synchronized void onRemoved(Container container) {
329 parent = null;
330 }
331
332 /**
333 * This is a little hack to avoid doing typecasts all over the place when having to return {@code T}. Credit to
334 * avl42 for this one!
335 * @return Itself, but as type T
336 */
337 @SuppressWarnings("unchecked")
338 protected T self() {
339 return (T)this;
340 }
341}