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