Add 'src/jexer/' from commit 'cf01c92f5809a0732409e280fb0f32f27393618d'
[fanfix.git] / src / jexer / ttree / TTreeItem.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 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 */
29 package jexer.ttree;
30
31 import java.util.ArrayList;
32 import java.util.List;
33
34 import jexer.TWidget;
35 import jexer.bits.CellAttributes;
36 import jexer.bits.GraphicsChars;
37 import jexer.bits.StringUtils;
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 // Variables --------------------------------------------------------------
49 // ------------------------------------------------------------------------
50
51 /**
52 * Hang onto reference to my parent TTreeView so I can call its reflow()
53 * when I add a child node.
54 */
55 private TTreeView view;
56
57 /**
58 * Displayable text for this item.
59 */
60 private String text;
61
62 /**
63 * If true, this item is expanded in the tree view.
64 */
65 private boolean expanded = true;
66
67 /**
68 * If true, this item can be expanded in the tree view.
69 */
70 private boolean expandable = false;
71
72 /**
73 * The vertical bars and such along the left side.
74 */
75 private String prefix = "";
76
77 /**
78 * Tree level.
79 */
80 protected int level = 0;
81
82 /**
83 * True means selected.
84 */
85 private boolean selected = false;
86
87 /**
88 * True means select-able.
89 */
90 private boolean selectable = true;
91
92 /**
93 * Whether or not this item is last in its parent's list of children.
94 */
95 private boolean last = false;
96
97 /**
98 * Pointer to the previous keyboard-navigable item (kbUp). Note package
99 * private access.
100 */
101 TTreeItem keyboardPrevious = null;
102
103 /**
104 * Pointer to the next keyboard-navigable item (kbDown). Note package
105 * private access.
106 */
107 TTreeItem keyboardNext = null;
108
109 // ------------------------------------------------------------------------
110 // Constructors -----------------------------------------------------------
111 // ------------------------------------------------------------------------
112
113 /**
114 * Public constructor.
115 *
116 * @param view root TTreeView
117 * @param text text for this item
118 * @param expanded if true, have it expanded immediately
119 */
120 public TTreeItem(final TTreeView view, final String text,
121 final boolean expanded) {
122
123 super(view, 0, 0, view.getWidth() - 3, 1);
124
125 this.text = text;
126 this.expanded = expanded;
127 this.view = view;
128
129 if (view.getTreeRoot() == null) {
130 view.setTreeRoot(this);
131 } else {
132 view.alignTree();
133 }
134 }
135
136 // ------------------------------------------------------------------------
137 // Event handlers ---------------------------------------------------------
138 // ------------------------------------------------------------------------
139
140 /**
141 * Handle mouse release events.
142 *
143 * @param mouse mouse button release event
144 */
145 @Override
146 public void onMouseUp(final TMouseEvent mouse) {
147 if ((mouse.getX() == (getExpanderX() - view.getLeftColumn()))
148 && (mouse.getY() == 0)
149 ) {
150 if (level == 0) {
151 // Root node can't switch.
152 return;
153 }
154 if (selectable) {
155 // Flip expanded flag
156 expanded = !expanded;
157 if (expanded == false) {
158 // Unselect children that became invisible
159 unselect();
160 }
161 view.setSelected(this, false);
162 }
163 // Let subclasses do something with this
164 onExpand();
165
166 // Update the screen after any thing has expanded/contracted
167 view.alignTree();
168 } else if (mouse.getY() == 0) {
169 // Do the action associated with this item.
170 view.setSelected(this, false);
171 view.dispatch();
172 }
173 }
174
175 /**
176 * Called when this item is expanded or collapsed. this.expanded will be
177 * true if this item was just expanded from a mouse click or keypress.
178 */
179 public void onExpand() {
180 // Default: do nothing.
181 if (!expandable) {
182 return;
183 }
184 }
185
186 /**
187 * Handle keystrokes.
188 *
189 * @param keypress keystroke event
190 */
191 @Override
192 public void onKeypress(final TKeypressEvent keypress) {
193 if (keypress.equals(kbLeft)
194 || keypress.equals(kbRight)
195 || keypress.equals(kbSpace)
196 ) {
197 if (level == 0) {
198 // Root node can't switch.
199 return;
200 }
201 if (selectable) {
202 // Flip expanded flag
203 expanded = !expanded;
204 if (expanded == false) {
205 // Unselect children that became invisible
206 unselect();
207 }
208 view.setSelected(this, false);
209 }
210 // Let subclasses do something with this
211 onExpand();
212 } else if (keypress.equals(kbEnter)) {
213 // Do the action associated with this item.
214 view.dispatch();
215 } else {
216 // Pass other keys (tab etc.) on to TWidget's handler.
217 super.onKeypress(keypress);
218 }
219 }
220
221 // ------------------------------------------------------------------------
222 // TWidget ----------------------------------------------------------------
223 // ------------------------------------------------------------------------
224
225 /**
226 * Draw this item to a window.
227 */
228 @Override
229 public void draw() {
230 if ((getY() < 0) || (getY() > getParent().getHeight() - 1)) {
231 return;
232 }
233
234 int offset = -view.getLeftColumn();
235
236 CellAttributes color = getTheme().getColor("ttreeview");
237 CellAttributes textColor = getTheme().getColor("ttreeview");
238 CellAttributes expanderColor = getTheme().getColor("ttreeview.expandbutton");
239 CellAttributes selectedColor = getTheme().getColor("ttreeview.selected");
240
241 if (!getParent().isAbsoluteActive()) {
242 color = getTheme().getColor("ttreeview.inactive");
243 textColor = getTheme().getColor("ttreeview.inactive");
244 selectedColor = getTheme().getColor("ttreeview.selected.inactive");
245 }
246
247 if (!selectable) {
248 textColor = getTheme().getColor("ttreeview.unreadable");
249 }
250
251 // Blank out the background
252 hLineXY(0, 0, getWidth(), ' ', color);
253
254 String line = prefix;
255 if (level > 0) {
256 if (last) {
257 line += GraphicsChars.CP437[0xC0];
258 } else {
259 line += GraphicsChars.CP437[0xC3];
260 }
261 line += GraphicsChars.CP437[0xC4];
262 if (expandable) {
263 line += "[ ] ";
264 } else {
265 line += " ";
266 }
267 }
268 putStringXY(offset, 0, line, color);
269 if (selected) {
270 putStringXY(offset + StringUtils.width(line), 0, text, selectedColor);
271 } else {
272 putStringXY(offset + StringUtils.width(line), 0, text, textColor);
273 }
274 if ((level > 0) && (expandable)) {
275 if (expanded) {
276 putCharXY(offset + getExpanderX(), 0, '-', expanderColor);
277 } else {
278 putCharXY(offset + getExpanderX(), 0, '+', expanderColor);
279 }
280 }
281 }
282
283 // ------------------------------------------------------------------------
284 // TTreeItem --------------------------------------------------------------
285 // ------------------------------------------------------------------------
286
287 /**
288 * Get the parent TTreeView.
289 *
290 * @return the parent TTreeView
291 */
292 public final TTreeView getTreeView() {
293 return view;
294 }
295
296 /**
297 * Get the displayable text for this item.
298 *
299 * @return the displayable text for this item
300 */
301 public final String getText() {
302 return text;
303 }
304
305 /**
306 * Set the displayable text for this item.
307 *
308 * @param text the displayable text for this item
309 */
310 public final void setText(final String text) {
311 this.text = text;
312 }
313
314 /**
315 * Get expanded value.
316 *
317 * @return if true, this item is expanded
318 */
319 public final boolean isExpanded() {
320 return expanded;
321 }
322
323 /**
324 * Set expanded value.
325 *
326 * @param expanded new value
327 */
328 public final void setExpanded(final boolean expanded) {
329 if (level == 0) {
330 // Root node can't be unexpanded, ever.
331 this.expanded = true;
332 return;
333 }
334 if (level > 0) {
335 this.expanded = expanded;
336 }
337 }
338
339 /**
340 * Get expandable value.
341 *
342 * @return if true, this item is expandable
343 */
344 public final boolean isExpandable() {
345 return expandable;
346 }
347
348 /**
349 * Set expandable value.
350 *
351 * @param expandable new value
352 */
353 public final void setExpandable(final boolean expandable) {
354 if (level == 0) {
355 // Root node can't be unexpanded, ever.
356 this.expandable = true;
357 return;
358 }
359 if (level > 0) {
360 this.expandable = expandable;
361 }
362 }
363
364 /**
365 * Get the vertical bars and such along the left side.
366 *
367 * @return the vertical bars and such along the left side
368 */
369 public final String getPrefix() {
370 return prefix;
371 }
372
373 /**
374 * Get selected value.
375 *
376 * @return if true, this item is selected
377 */
378 public final boolean isSelected() {
379 return selected;
380 }
381
382 /**
383 * Set selected value.
384 *
385 * @param selected new value
386 */
387 public final void setSelected(final boolean selected) {
388 this.selected = selected;
389 }
390
391 /**
392 * Set selectable value.
393 *
394 * @param selectable new value
395 */
396 public final void setSelectable(final boolean selectable) {
397 this.selectable = selectable;
398 }
399
400 /**
401 * Get the length of the widest item to display.
402 *
403 * @return the maximum number of columns for this item or its children
404 */
405 public int getMaximumColumn() {
406 int max = prefix.length() + 4 + StringUtils.width(text);
407 for (TWidget widget: getChildren()) {
408 TTreeItem item = (TTreeItem) widget;
409 int n = item.prefix.length() + 4 + StringUtils.width(item.text);
410 if (n > max) {
411 max = n;
412 }
413 }
414 return max;
415 }
416
417 /**
418 * Recursively expand the tree into a linear array of items.
419 *
420 * @param prefix vertical bar of parent levels and such that is set on
421 * each child
422 * @param last if true, this is the "last" leaf node of a tree
423 * @return additional items to add to the array
424 */
425 public List<TTreeItem> expandTree(final String prefix, final boolean last) {
426 List<TTreeItem> array = new ArrayList<TTreeItem>();
427 this.last = last;
428 this.prefix = prefix;
429 array.add(this);
430
431 if ((getChildren().size() == 0) || !expanded) {
432 return array;
433 }
434
435 String newPrefix = prefix;
436 if (level > 0) {
437 if (last) {
438 newPrefix += " ";
439 } else {
440 newPrefix += GraphicsChars.CP437[0xB3];
441 newPrefix += ' ';
442 }
443 }
444 for (int i = 0; i < getChildren().size(); i++) {
445 TTreeItem item = (TTreeItem) getChildren().get(i);
446 if (i == getChildren().size() - 1) {
447 array.addAll(item.expandTree(newPrefix, true));
448 } else {
449 array.addAll(item.expandTree(newPrefix, false));
450 }
451 }
452 return array;
453 }
454
455 /**
456 * Get the x spot for the + or - to expand/collapse.
457 *
458 * @return column of the expand/collapse button
459 */
460 private int getExpanderX() {
461 if ((level == 0) || (!expandable)) {
462 return 0;
463 }
464 return prefix.length() + 3;
465 }
466
467 /**
468 * Recursively unselect me and my children.
469 */
470 public void unselect() {
471 if (selected == true) {
472 selected = false;
473 view.setSelected(null, false);
474 }
475 for (TWidget widget: getChildren()) {
476 if (widget instanceof TTreeItem) {
477 TTreeItem item = (TTreeItem) widget;
478 item.unselect();
479 }
480 }
481 }
482
483 }