e6bf2790f8c2b584d89e8040ccba786c1a62579c
[nikiroo-utils.git] / src / jexer / TTreeItem.java
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 */
31 package jexer;
32
33 import java.util.ArrayList;
34 import java.util.List;
35
36 import jexer.bits.CellAttributes;
37 import jexer.bits.GraphicsChars;
38 import jexer.event.TKeypressEvent;
39 import jexer.event.TMouseEvent;
40 import static jexer.TKeypress.*;
41
42 /**
43 * TTreeItem is a single item in a tree view.
44 */
45 public 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 text 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(final 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(final 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(final 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(final 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(final boolean selectable) {
203 this.selectable = selectable;
204 }
205
206 /**
207 * Pointer to the previous keyboard-navigable item (kbUp). Note package
208 * private access.
209 */
210 TTreeItem keyboardPrevious = null;
211
212 /**
213 * Pointer to the next keyboard-navigable item (kbDown). Note package
214 * private access.
215 */
216 TTreeItem keyboardNext = null;
217
218 /**
219 * Public constructor.
220 *
221 * @param view root TTreeView
222 * @param text text for this item
223 * @param expanded if true, have it expanded immediately
224 */
225 public TTreeItem(final TTreeView view, final String text,
226 final boolean expanded) {
227
228 super(view, 0, 0, view.getWidth() - 3, 1);
229 this.text = text;
230 this.expanded = expanded;
231 this.view = view;
232
233 if (view.getTreeRoot() == null) {
234 view.setTreeRoot(this, true);
235 }
236
237 view.reflow();
238 }
239
240 /**
241 * Add a child item.
242 *
243 * @param text text for this item
244 * @return the new child item
245 */
246 public TTreeItem addChild(final String text) {
247 return addChild(text, true);
248 }
249
250 /**
251 * Add a child item.
252 *
253 * @param text text for this item
254 * @param expanded if true, have it expanded immediately
255 * @return the new child item
256 */
257 public TTreeItem addChild(final String text, final boolean expanded) {
258 TTreeItem item = new TTreeItem(view, text, expanded);
259 item.level = this.level + 1;
260 getChildren().add(item);
261 view.reflow();
262 return item;
263 }
264
265 /**
266 * Recursively expand the tree into a linear array of items.
267 *
268 * @param prefix vertical bar of parent levels and such that is set on
269 * each child
270 * @param last if true, this is the "last" leaf node of a tree
271 * @return additional items to add to the array
272 */
273 public List<TTreeItem> expandTree(final String prefix, final boolean last) {
274 List<TTreeItem> array = new ArrayList<TTreeItem>();
275 this.last = last;
276 this.prefix = prefix;
277 array.add(this);
278
279 if ((getChildren().size() == 0) || !expanded) {
280 return array;
281 }
282
283 String newPrefix = prefix;
284 if (level > 0) {
285 if (last) {
286 newPrefix += " ";
287 } else {
288 newPrefix += GraphicsChars.CP437[0xB3];
289 newPrefix += ' ';
290 }
291 }
292 for (int i = 0; i < getChildren().size(); i++) {
293 TTreeItem item = (TTreeItem) getChildren().get(i);
294 if (i == getChildren().size() - 1) {
295 array.addAll(item.expandTree(newPrefix, true));
296 } else {
297 array.addAll(item.expandTree(newPrefix, false));
298 }
299 }
300 return array;
301 }
302
303 /**
304 * Get the x spot for the + or - to expand/collapse.
305 *
306 * @return column of the expand/collapse button
307 */
308 private int getExpanderX() {
309 if ((level == 0) || (!expandable)) {
310 return 0;
311 }
312 return prefix.length() + 3;
313 }
314
315 /**
316 * Recursively unselect my or my children.
317 */
318 public void unselect() {
319 if (selected == true) {
320 selected = false;
321 view.setSelected(null);
322 }
323 for (TWidget widget: getChildren()) {
324 if (widget instanceof TTreeItem) {
325 TTreeItem item = (TTreeItem) widget;
326 item.unselect();
327 }
328 }
329 }
330
331 /**
332 * Handle mouse release events.
333 *
334 * @param mouse mouse button release event
335 */
336 @Override
337 public void onMouseUp(final TMouseEvent mouse) {
338 if ((mouse.getX() == (getExpanderX() - view.getHScroller().getValue()))
339 && (mouse.getY() == 0)
340 ) {
341 if (selectable) {
342 // Flip expanded flag
343 expanded = !expanded;
344 if (expanded == false) {
345 // Unselect children that became invisible
346 unselect();
347 }
348 }
349 // Let subclasses do something with this
350 onExpand();
351 } else if (mouse.getY() == 0) {
352 view.setSelected(this);
353 view.dispatch();
354 }
355
356 // Update the screen after any thing has expanded/contracted
357 view.reflow();
358 }
359
360 /**
361 * Called when this item is expanded or collapsed. this.expanded will be
362 * true if this item was just expanded from a mouse click or keypress.
363 */
364 public void onExpand() {
365 // Default: do nothing.
366 if (!expandable) {
367 return;
368 }
369 }
370
371 /**
372 * Handle keystrokes.
373 *
374 * @param keypress keystroke event
375 */
376 @Override
377 public void onKeypress(final TKeypressEvent keypress) {
378 if (keypress.equals(kbLeft)
379 || keypress.equals(kbRight)
380 || keypress.equals(kbSpace)
381 ) {
382 if (selectable) {
383 // Flip expanded flag
384 expanded = !expanded;
385 if (expanded == false) {
386 // Unselect children that became invisible
387 unselect();
388 }
389 view.setSelected(this);
390 }
391 // Let subclasses do something with this
392 onExpand();
393 } else {
394 // Pass other keys (tab etc.) on to TWidget's handler.
395 super.onKeypress(keypress);
396 }
397 }
398
399 /**
400 * Draw this item to a window.
401 */
402 @Override
403 public void draw() {
404 if (invisible) {
405 return;
406 }
407
408 int offset = -view.getHScroller().getValue();
409
410 CellAttributes color = getTheme().getColor("ttreeview");
411 CellAttributes textColor = getTheme().getColor("ttreeview");
412 CellAttributes expanderColor = getTheme().getColor("ttreeview.expandbutton");
413 CellAttributes selectedColor = getTheme().getColor("ttreeview.selected");
414
415 if (!getParent().isAbsoluteActive()) {
416 color = getTheme().getColor("ttreeview.inactive");
417 textColor = getTheme().getColor("ttreeview.inactive");
418 }
419
420 if (!selectable) {
421 textColor = getTheme().getColor("ttreeview.unreadable");
422 }
423
424 // Blank out the background
425 getScreen().hLineXY(0, 0, getWidth(), ' ', color);
426
427 String line = prefix;
428 if (level > 0) {
429 if (last) {
430 line += GraphicsChars.CP437[0xC0];
431 } else {
432 line += GraphicsChars.CP437[0xC3];
433 }
434 line += GraphicsChars.CP437[0xC4];
435 if (expandable) {
436 line += "[ ] ";
437 }
438 }
439 getScreen().putStringXY(offset, 0, line, color);
440 if (selected) {
441 getScreen().putStringXY(offset + line.length(), 0, text,
442 selectedColor);
443 } else {
444 getScreen().putStringXY(offset + line.length(), 0, text, textColor);
445 }
446 if ((level > 0) && (expandable)) {
447 if (expanded) {
448 getScreen().putCharXY(offset + getExpanderX(), 0, '-',
449 expanderColor);
450 } else {
451 getScreen().putCharXY(offset + getExpanderX(), 0, '+',
452 expanderColor);
453 }
454 }
455 }
456
457 }