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
.TerminalSize
;
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.
29 * The way you want to declare your new {@code Component} is to pass in itself as the generic parameter, like this:
32 * public class MyComponent extends AbstractComponent<MyComponent> {
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.
41 * @param <T> Should always be itself, this value will be used for the {@code ComponentRenderer} declaration
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
;
55 public AbstractComponent() {
56 size
= TerminalSize
.ZERO
;
57 position
= TerminalPosition
.TOP_LEFT_CORNER
;
58 explicitPreferredSize
= null;
62 renderer
= null; //Will be set on the first call to getRenderer()
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
71 protected abstract ComponentRenderer
<T
> createDefaultRenderer();
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.
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
82 @SuppressWarnings("unchecked")
83 protected ComponentRenderer
<T
> getRendererFromTheme(String className
) {
84 if(className
== null) {
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
);
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
103 protected void runOnGUIThreadIfExistsOtherwiseRunDirect(Runnable runnable
) {
104 if(getTextGUI() != null && getTextGUI().getGUIThread() != null) {
105 getTextGUI().getGUIThread().invokeLater(runnable
);
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
117 public T
setRenderer(ComponentRenderer
<T
> renderer
) {
118 this.renderer
= renderer
;
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");
134 public void invalidate() {
139 public synchronized T
setSize(TerminalSize size
) {
145 public TerminalSize
getSize() {
150 public final TerminalSize
getPreferredSize() {
151 if(explicitPreferredSize
!= null) {
152 return explicitPreferredSize
;
155 return calculatePreferredSize();
160 public final synchronized T
setPreferredSize(TerminalSize explicitPreferredSize
) {
161 this.explicitPreferredSize
= explicitPreferredSize
;
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
170 protected synchronized TerminalSize
calculatePreferredSize() {
171 return getRenderer().getPreferredSize(self());
175 public synchronized T
setPosition(TerminalPosition position
) {
176 this.position
= position
;
181 public TerminalPosition
getPosition() {
186 public boolean isInvalid() {
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");
200 setRenderer(renderer
);
202 //Delegate drawing the component to the renderer
203 setSize(graphics
.getSize());
205 getRenderer().drawComponent(graphics
, self());
206 onAfterDrawing(graphics
);
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()}.
216 protected void onBeforeDrawing() {
217 //No operation by default
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
226 protected void onAfterDrawing(TextGUIGraphics graphics
) {
227 //No operation by default
231 public synchronized T
setLayoutData(LayoutData data
) {
232 if(layoutData
!= data
) {
240 public LayoutData
getLayoutData() {
245 public Container
getParent() {
250 public boolean hasParent(Container parent
) {
251 if(this.parent
== null) {
254 Container recursiveParent
= this.parent
;
255 while(recursiveParent
!= null) {
256 if(recursiveParent
== parent
) {
259 recursiveParent
= recursiveParent
.getParent();
265 public TextGUI
getTextGUI() {
269 return parent
.getTextGUI();
273 public boolean isInside(Container container
) {
274 Component test
= this;
275 while(test
.getParent() != null) {
276 if(test
.getParent() == container
) {
279 test
= test
.getParent();
285 public BasePane
getBasePane() {
289 return parent
.getBasePane();
293 public TerminalPosition
toBasePane(TerminalPosition position
) {
294 Container parent
= getParent();
298 return parent
.toBasePane(getPosition().withRelative(position
));
302 public TerminalPosition
toGlobal(TerminalPosition position
) {
303 Container parent
= getParent();
307 return parent
.toGlobal(getPosition().withRelative(position
));
311 public synchronized Border
withBorder(Border border
) {
312 border
.setComponent(this);
317 public synchronized T
addTo(Panel panel
) {
318 panel
.addComponent(this);
323 public synchronized void onAdded(Container container
) {
328 public synchronized void onRemoved(Container container
) {
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
337 @SuppressWarnings("unchecked")