Merge branch 'master' of https://github.com/klamonte/jexer
[fanfix.git] / src / jexer / TTreeItem.java
CommitLineData
7668cb45
KL
1/**
2 * Jexer - Java Text User Interface
3 *
4 * License: LGPLv3 or later
5 *
6 * This module is licensed under the GNU Lesser General Public License
7 * Version 3. Please see the file "COPYING" in this directory for more
8 * information about the GNU Lesser General Public License Version 3.
9 *
10 * Copyright (C) 2015 Kevin Lamonte
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public License
14 * as published by the Free Software Foundation; either version 3 of
15 * the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this program; if not, see
24 * http://www.gnu.org/licenses/, or write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
26 * 02110-1301 USA
27 *
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 * @version 1
30 */
31package jexer;
32
33import java.util.ArrayList;
34import java.util.List;
35
36import jexer.bits.CellAttributes;
37import jexer.bits.GraphicsChars;
38import jexer.event.TKeypressEvent;
39import jexer.event.TMouseEvent;
40import static jexer.TKeypress.*;
41
42/**
43 * TTreeItem is a single item in a tree view.
44 */
45public class TTreeItem extends TWidget {
46
47 /**
48 * Hang onto reference to my parent TTreeView so I can call its reflow()
49 * when I add a child node.
50 */
51 private TTreeView view;
52
53 /**
54 * Get the parent TTreeView.
55 *
56 * @return the parent TTreeView
57 */
58 public final TTreeView getTreeView() {
59 return view;
60 }
61
62 /**
63 * Displayable text for this item.
64 */
65 private String text;
66
67 /**
68 * Get the displayable text for this item.
69 *
70 * @return the displayable text for this item
71 */
72 public final String getText() {
73 return text;
74 }
75
76 /**
77 * Set the displayable text for this item.
78 *
79 * @param the displayable text for this item
80 */
81 public final void setText(final String text) {
82 this.text = text;
83 }
84
85 /**
86 * If true, this item is expanded in the tree view.
87 */
88 private boolean expanded = true;
89
90 /**
91 * Get expanded value.
92 *
93 * @return if true, this item is expanded
94 */
95 public final boolean isExpanded() {
96 return expanded;
97 }
98
99 /**
100 * Set expanded value.
101 *
102 * @param expanded new value
103 */
104 public final void setExpanded(boolean expanded) {
105 this.expanded = expanded;
106 }
107
108 /**
109 * If true, this item can be expanded in the tree view.
110 */
111 private boolean expandable = false;
112
113 /**
114 * Get expandable value.
115 *
116 * @return if true, this item is expandable
117 */
118 public final boolean isExpandable() {
119 return expandable;
120 }
121
122 /**
123 * Set expandable value.
124 *
125 * @param expandable new value
126 */
127 public final void setExpandable(boolean expandable) {
128 this.expandable = expandable;
129 }
130
131 /**
132 * The vertical bars and such along the left side.
133 */
134 private String prefix = "";
135
136 /**
137 * Get the vertical bars and such along the left side.
138 *
139 * @return the vertical bars and such along the left side
140 */
141 public final String getPrefix() {
142 return prefix;
143 }
144
145 /**
146 * Whether or not this item is last in its parent's list of children.
147 */
148 private boolean last = false;
149
150 /**
151 * Tree level. Note package private access.
152 */
153 int level = 0;
154
155 /**
156 * If true, this item will not be drawn.
157 */
158 private boolean invisible = false;
159
160 /**
161 * Set invisible value.
162 *
163 * @param invisible new value
164 */
165 public final void setInvisible(boolean invisible) {
166 this.invisible = invisible;
167 }
168
169 /**
170 * True means selected.
171 */
172 private boolean selected = false;
173
174 /**
175 * Get selected value.
176 *
177 * @return if true, this item is selected
178 */
179 public final boolean isSelected() {
180 return selected;
181 }
182
183 /**
184 * Set selected value.
185 *
186 * @param selected new value
187 */
188 public final void setSelected(boolean selected) {
189 this.selected = selected;
190 }
191
192 /**
193 * True means select-able.
194 */
195 private boolean selectable = true;
196
197 /**
198 * Set selectable value.
199 *
200 * @param selectable new value
201 */
202 public final void setSelectable(boolean selectable) {
203 this.selectable = selectable;
204 }
205
206 /**
207 * Public constructor.
208 *
209 * @param view root TTreeView
210 * @param text text for this item
211 * @param expanded if true, have it expanded immediately
212 */
213 public TTreeItem(final TTreeView view, final String text,
214 final boolean expanded) {
215
216 super(view, 0, 0, view.getWidth() - 3, 1);
217 this.text = text;
218 this.expanded = expanded;
219 this.view = view;
220
221 if (view.getTreeRoot() == null) {
222 view.setTreeRoot(this, true);
223 }
224
225 view.reflow();
226 }
227
228 /**
229 * Add a child item.
230 *
231 * @param text text for this item
232 * @return the new child item
233 */
234 public TTreeItem addChild(final String text) {
235 return addChild(text, true);
236 }
237
238 /**
239 * Add a child item.
240 *
241 * @param text text for this item
242 * @param expanded if true, have it expanded immediately
243 * @return the new child item
244 */
245 public TTreeItem addChild(final String text, final boolean expanded) {
246 TTreeItem item = new TTreeItem(view, text, expanded);
247 item.level = this.level + 1;
248 getChildren().add(item);
249 view.reflow();
250 return item;
251 }
252
253 /**
254 * Recursively expand the tree into a linear array of items.
255 *
256 * @param prefix = vertical bar of parent levels and such that is set on
257 * each child
258 * @param last = if true, this is the "last" leaf node of a tree
259 * @param additional items to add to the array
260 */
261 public List<TTreeItem> expandTree(final String prefix, final boolean last) {
262 List<TTreeItem> array = new ArrayList<TTreeItem>();
263 this.last = last;
264 this.prefix = prefix;
265 array.add(this);
266
267 if ((getChildren().size() == 0) || (expanded == false)) {
268 return array;
269 }
270
271 String newPrefix = prefix;
272 if (level > 0) {
273 if (last) {
274 newPrefix += " ";
275 } else {
276 newPrefix += GraphicsChars.CP437[0xB3];
277 newPrefix += ' ';
278 }
279 }
280 for (int i = 0; i < getChildren().size(); i++) {
281 TTreeItem item = (TTreeItem) getChildren().get(i);
282 if (i == getChildren().size() - 1) {
283 array.addAll(item.expandTree(newPrefix, true));
284 } else {
285 array.addAll(item.expandTree(newPrefix, false));
286 }
287 }
288 return array;
289 }
290
291 /**
292 * Get the x spot for the + or - to expand/collapse.
293 *
294 * @return column of the expand/collapse button
295 */
296 private int getExpanderX() {
297 if ((level == 0) || (!expandable)) {
298 return 0;
299 }
300 return prefix.length() + 3;
301 }
302
303 /**
304 * Recursively unselect my or my children.
305 */
306 public void unselect() {
307 if (selected == true) {
308 selected = false;
309 view.setSelected(null);
310 }
311 for (TWidget widget: getChildren()) {
312 if (widget instanceof TTreeItem) {
313 TTreeItem item = (TTreeItem) widget;
314 item.unselect();
315 }
316 }
317 }
318
319 /**
320 * Handle mouse release events.
321 *
322 * @param mouse mouse button release event
323 */
324 @Override
325 public void onMouseUp(final TMouseEvent mouse) {
326 if ((mouse.getX() == (getExpanderX() - view.hScroller.getValue()))
327 && (mouse.getY() == 0)
328 ) {
329 if (selectable) {
330 // Flip expanded flag
331 expanded = !expanded;
332 if (expanded == false) {
333 // Unselect children that became invisible
334 unselect();
335 }
336 }
337 // Let subclasses do something with this
338 onExpand();
339 } else if (mouse.getY() == 0) {
340 view.setSelected(this);
341 view.dispatch();
342 }
343
344 // Update the screen after any thing has expanded/contracted
345 view.reflow();
346 }
347
348 /**
349 * Called when this item is expanded or collapsed. this.expanded will be
350 * true if this item was just expanded from a mouse click or keypress.
351 */
352 public void onExpand() {
353 // Default: do nothing.
354 if (!expandable) {
355 return;
356 }
357 }
358
359 /**
360 * Draw this item to a window.
361 */
362 @Override
363 public void draw() {
364 if (invisible) {
365 return;
366 }
367
368 int offset = -view.hScroller.getValue();
369
370 CellAttributes color = getTheme().getColor("ttreeview");
371 CellAttributes textColor = getTheme().getColor("ttreeview");
372 CellAttributes expanderColor = getTheme().getColor("ttreeview.expandbutton");
373 CellAttributes selectedColor = getTheme().getColor("ttreeview.selected");
374
375 if (!getParent().isAbsoluteActive()) {
376 color = getTheme().getColor("ttreeview.inactive");
377 textColor = getTheme().getColor("ttreeview.inactive");
378 }
379
380 if (!selectable) {
381 textColor = getTheme().getColor("ttreeview.unreadable");
382 }
383
384 // Blank out the background
385 getScreen().hLineXY(0, 0, getWidth(), ' ', color);
386
387 int expandX = 0;
388 String line = prefix;
389 if (level > 0) {
390 if (last) {
391 line += GraphicsChars.CP437[0xC0];
392 } else {
393 line += GraphicsChars.CP437[0xC3];
394 }
395 line += GraphicsChars.CP437[0xC4];
396 if (expandable) {
397 line += "[ ] ";
398 }
399 }
400 getScreen().putStrXY(offset, 0, line, color);
401 if (selected) {
402 getScreen().putStrXY(offset + line.length(), 0, text,
403 selectedColor);
404 } else {
405 getScreen().putStrXY(offset + line.length(), 0, text, textColor);
406 }
407 if ((level > 0) && (expandable)) {
408 if (expanded) {
409 getScreen().putCharXY(offset + getExpanderX(), 0, '-',
410 expanderColor);
411 } else {
412 getScreen().putCharXY(offset + getExpanderX(), 0, '+',
413 expanderColor);
414 }
415 }
416 }
417
418}