Commit | Line | Data |
---|---|---|
a3b510ab NR |
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 | */ | |
19 | package com.googlecode.lanterna.gui2; | |
20 | ||
21 | import com.googlecode.lanterna.TerminalPosition; | |
22 | import 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 | */ | |
43 | public 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 | } |