Fix NPE
[nikiroo-utils.git] / src / jexer / TTreeItem.java
... / ...
CommitLineData
1/*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2017 Kevin Lamonte
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29package jexer;
30
31import java.util.ArrayList;
32import java.util.List;
33
34import jexer.bits.CellAttributes;
35import jexer.bits.GraphicsChars;
36import jexer.event.TKeypressEvent;
37import jexer.event.TMouseEvent;
38import static jexer.TKeypress.*;
39
40/**
41 * TTreeItem is a single item in a tree view.
42 */
43public class TTreeItem extends TWidget {
44
45 /**
46 * Hang onto reference to my parent TTreeView so I can call its reflow()
47 * when I add a child node.
48 */
49 private TTreeView view;
50
51 /**
52 * Get the parent TTreeView.
53 *
54 * @return the parent TTreeView
55 */
56 public final TTreeView getTreeView() {
57 return view;
58 }
59
60 /**
61 * Displayable text for this item.
62 */
63 private String text;
64
65 /**
66 * Get the displayable text for this item.
67 *
68 * @return the displayable text for this item
69 */
70 public final String getText() {
71 return text;
72 }
73
74 /**
75 * Set the displayable text for this item.
76 *
77 * @param text the displayable text for this item
78 */
79 public final void setText(final String text) {
80 this.text = text;
81 }
82
83 /**
84 * If true, this item is expanded in the tree view.
85 */
86 private boolean expanded = true;
87
88 /**
89 * Get expanded value.
90 *
91 * @return if true, this item is expanded
92 */
93 public final boolean isExpanded() {
94 return expanded;
95 }
96
97 /**
98 * Set expanded value.
99 *
100 * @param expanded new value
101 */
102 public final void setExpanded(final boolean expanded) {
103 this.expanded = expanded;
104 }
105
106 /**
107 * If true, this item can be expanded in the tree view.
108 */
109 private boolean expandable = false;
110
111 /**
112 * Get expandable value.
113 *
114 * @return if true, this item is expandable
115 */
116 public final boolean isExpandable() {
117 return expandable;
118 }
119
120 /**
121 * Set expandable value.
122 *
123 * @param expandable new value
124 */
125 public final void setExpandable(final boolean expandable) {
126 this.expandable = expandable;
127 }
128
129 /**
130 * The vertical bars and such along the left side.
131 */
132 private String prefix = "";
133
134 /**
135 * Get the vertical bars and such along the left side.
136 *
137 * @return the vertical bars and such along the left side
138 */
139 public final String getPrefix() {
140 return prefix;
141 }
142
143 /**
144 * Whether or not this item is last in its parent's list of children.
145 */
146 private boolean last = false;
147
148 /**
149 * Tree level. Note package private access.
150 */
151 int level = 0;
152
153 /**
154 * If true, this item will not be drawn.
155 */
156 private boolean invisible = false;
157
158 /**
159 * Set invisible value.
160 *
161 * @param invisible new value
162 */
163 public final void setInvisible(final boolean invisible) {
164 this.invisible = invisible;
165 }
166
167 /**
168 * True means selected.
169 */
170 private boolean selected = false;
171
172 /**
173 * Get selected value.
174 *
175 * @return if true, this item is selected
176 */
177 public final boolean isSelected() {
178 return selected;
179 }
180
181 /**
182 * Set selected value.
183 *
184 * @param selected new value
185 */
186 public final void setSelected(final boolean selected) {
187 this.selected = selected;
188 }
189
190 /**
191 * True means select-able.
192 */
193 private boolean selectable = true;
194
195 /**
196 * Set selectable value.
197 *
198 * @param selectable new value
199 */
200 public final void setSelectable(final boolean selectable) {
201 this.selectable = selectable;
202 }
203
204 /**
205 * Pointer to the previous keyboard-navigable item (kbUp). Note package
206 * private access.
207 */
208 TTreeItem keyboardPrevious = null;
209
210 /**
211 * Pointer to the next keyboard-navigable item (kbDown). Note package
212 * private access.
213 */
214 TTreeItem keyboardNext = null;
215
216 /**
217 * Public constructor.
218 *
219 * @param view root TTreeView
220 * @param text text for this item
221 * @param expanded if true, have it expanded immediately
222 */
223 public TTreeItem(final TTreeView view, final String text,
224 final boolean expanded) {
225
226 super(view, 0, 0, view.getWidth() - 3, 1);
227 this.text = text;
228 this.expanded = expanded;
229 this.view = view;
230
231 if (view.getTreeRoot() == null) {
232 view.setTreeRoot(this, true);
233 }
234
235 view.reflow();
236 }
237
238 /**
239 * Add a child item.
240 *
241 * @param text text for this item
242 * @return the new child item
243 */
244 public TTreeItem addChild(final String text) {
245 return addChild(text, true);
246 }
247
248 /**
249 * Add a child item.
250 *
251 * @param text text for this item
252 * @param expanded if true, have it expanded immediately
253 * @return the new child item
254 */
255 public TTreeItem addChild(final String text, final boolean expanded) {
256 TTreeItem item = new TTreeItem(view, text, expanded);
257 item.level = this.level + 1;
258 getChildren().add(item);
259 view.reflow();
260 return item;
261 }
262
263 /**
264 * Recursively expand the tree into a linear array of items.
265 *
266 * @param prefix vertical bar of parent levels and such that is set on
267 * each child
268 * @param last if true, this is the "last" leaf node of a tree
269 * @return additional items to add to the array
270 */
271 public List<TTreeItem> expandTree(final String prefix, final boolean last) {
272 List<TTreeItem> array = new ArrayList<TTreeItem>();
273 this.last = last;
274 this.prefix = prefix;
275 array.add(this);
276
277 if ((getChildren().size() == 0) || !expanded) {
278 return array;
279 }
280
281 String newPrefix = prefix;
282 if (level > 0) {
283 if (last) {
284 newPrefix += " ";
285 } else {
286 newPrefix += GraphicsChars.CP437[0xB3];
287 newPrefix += ' ';
288 }
289 }
290 for (int i = 0; i < getChildren().size(); i++) {
291 TTreeItem item = (TTreeItem) getChildren().get(i);
292 if (i == getChildren().size() - 1) {
293 array.addAll(item.expandTree(newPrefix, true));
294 } else {
295 array.addAll(item.expandTree(newPrefix, false));
296 }
297 }
298 return array;
299 }
300
301 /**
302 * Get the x spot for the + or - to expand/collapse.
303 *
304 * @return column of the expand/collapse button
305 */
306 private int getExpanderX() {
307 if ((level == 0) || (!expandable)) {
308 return 0;
309 }
310 return prefix.length() + 3;
311 }
312
313 /**
314 * Recursively unselect my or my children.
315 */
316 public void unselect() {
317 if (selected == true) {
318 selected = false;
319 view.setSelected(null);
320 }
321 for (TWidget widget: getChildren()) {
322 if (widget instanceof TTreeItem) {
323 TTreeItem item = (TTreeItem) widget;
324 item.unselect();
325 }
326 }
327 }
328
329 /**
330 * Handle mouse release events.
331 *
332 * @param mouse mouse button release event
333 */
334 @Override
335 public void onMouseUp(final TMouseEvent mouse) {
336 if ((mouse.getX() == (getExpanderX() - view.getHScroller().getValue()))
337 && (mouse.getY() == 0)
338 ) {
339 if (selectable) {
340 // Flip expanded flag
341 expanded = !expanded;
342 if (expanded == false) {
343 // Unselect children that became invisible
344 unselect();
345 }
346 }
347 // Let subclasses do something with this
348 onExpand();
349 } else if (mouse.getY() == 0) {
350 view.setSelected(this);
351 view.dispatch();
352 }
353
354 // Update the screen after any thing has expanded/contracted
355 view.reflow();
356 }
357
358 /**
359 * Called when this item is expanded or collapsed. this.expanded will be
360 * true if this item was just expanded from a mouse click or keypress.
361 */
362 public void onExpand() {
363 // Default: do nothing.
364 if (!expandable) {
365 return;
366 }
367 }
368
369 /**
370 * Handle keystrokes.
371 *
372 * @param keypress keystroke event
373 */
374 @Override
375 public void onKeypress(final TKeypressEvent keypress) {
376 if (keypress.equals(kbLeft)
377 || keypress.equals(kbRight)
378 || keypress.equals(kbSpace)
379 ) {
380 if (selectable) {
381 // Flip expanded flag
382 expanded = !expanded;
383 if (expanded == false) {
384 // Unselect children that became invisible
385 unselect();
386 }
387 view.setSelected(this);
388 }
389 // Let subclasses do something with this
390 onExpand();
391 } else {
392 // Pass other keys (tab etc.) on to TWidget's handler.
393 super.onKeypress(keypress);
394 }
395 }
396
397 /**
398 * Draw this item to a window.
399 */
400 @Override
401 public void draw() {
402 if (invisible) {
403 return;
404 }
405
406 int offset = -view.getHScroller().getValue();
407
408 CellAttributes color = getTheme().getColor("ttreeview");
409 CellAttributes textColor = getTheme().getColor("ttreeview");
410 CellAttributes expanderColor = getTheme().getColor("ttreeview.expandbutton");
411 CellAttributes selectedColor = getTheme().getColor("ttreeview.selected");
412
413 if (!getParent().isAbsoluteActive()) {
414 color = getTheme().getColor("ttreeview.inactive");
415 textColor = getTheme().getColor("ttreeview.inactive");
416 }
417
418 if (!selectable) {
419 textColor = getTheme().getColor("ttreeview.unreadable");
420 }
421
422 // Blank out the background
423 getScreen().hLineXY(0, 0, getWidth(), ' ', color);
424
425 String line = prefix;
426 if (level > 0) {
427 if (last) {
428 line += GraphicsChars.CP437[0xC0];
429 } else {
430 line += GraphicsChars.CP437[0xC3];
431 }
432 line += GraphicsChars.CP437[0xC4];
433 if (expandable) {
434 line += "[ ] ";
435 }
436 }
437 getScreen().putStringXY(offset, 0, line, color);
438 if (selected) {
439 getScreen().putStringXY(offset + line.length(), 0, text,
440 selectedColor);
441 } else {
442 getScreen().putStringXY(offset + line.length(), 0, text, textColor);
443 }
444 if ((level > 0) && (expandable)) {
445 if (expanded) {
446 getScreen().putCharXY(offset + getExpanderX(), 0, '-',
447 expanderColor);
448 } else {
449 getScreen().putCharXY(offset + getExpanderX(), 0, '+',
450 expanderColor);
451 }
452 }
453 }
454
455}