oops
[fanfix.git] / src / jexer / ttree / TTreeItem.java
CommitLineData
daa4106c 1/*
7668cb45
KL
2 * Jexer - Java Text User Interface
3 *
e16dda65 4 * The MIT License (MIT)
7668cb45 5 *
a69ed767 6 * Copyright (C) 2019 Kevin Lamonte
7668cb45 7 *
e16dda65
KL
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:
7668cb45 14 *
e16dda65
KL
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
7668cb45 17 *
e16dda65
KL
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.
7668cb45
KL
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
d36057df 29package jexer.ttree;
7668cb45
KL
30
31import java.util.ArrayList;
32import java.util.List;
33
d36057df 34import jexer.TWidget;
7668cb45
KL
35import jexer.bits.CellAttributes;
36import jexer.bits.GraphicsChars;
37import jexer.event.TKeypressEvent;
38import jexer.event.TMouseEvent;
39import static jexer.TKeypress.*;
40
41/**
42 * TTreeItem is a single item in a tree view.
43 */
44public class TTreeItem extends TWidget {
45
d36057df
KL
46 // ------------------------------------------------------------------------
47 // Variables --------------------------------------------------------------
48 // ------------------------------------------------------------------------
49
7668cb45
KL
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
7668cb45
KL
56 /**
57 * Displayable text for this item.
58 */
59 private String text;
60
7668cb45
KL
61 /**
62 * If true, this item is expanded in the tree view.
63 */
64 private boolean expanded = true;
65
7668cb45
KL
66 /**
67 * If true, this item can be expanded in the tree view.
68 */
69 private boolean expandable = false;
70
7668cb45
KL
71 /**
72 * The vertical bars and such along the left side.
73 */
74 private String prefix = "";
75
76 /**
d36057df 77 * Tree level.
7668cb45 78 */
d36057df 79 protected int level = 0;
7668cb45
KL
80
81 /**
82 * True means selected.
83 */
84 private boolean selected = false;
85
7668cb45
KL
86 /**
87 * True means select-able.
88 */
89 private boolean selectable = true;
90
91 /**
d36057df 92 * Whether or not this item is last in its parent's list of children.
7668cb45 93 */
d36057df 94 private boolean last = false;
7668cb45 95
0d47c546
KL
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
d36057df
KL
108 // ------------------------------------------------------------------------
109 // Constructors -----------------------------------------------------------
110 // ------------------------------------------------------------------------
111
7668cb45
KL
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);
d36057df 123
7668cb45
KL
124 this.text = text;
125 this.expanded = expanded;
126 this.view = view;
127
128 if (view.getTreeRoot() == null) {
d36057df
KL
129 view.setTreeRoot(this);
130 } else {
131 view.alignTree();
7668cb45 132 }
7668cb45
KL
133 }
134
d36057df
KL
135 // ------------------------------------------------------------------------
136 // Event handlers ---------------------------------------------------------
137 // ------------------------------------------------------------------------
7668cb45
KL
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) {
d36057df 146 if ((mouse.getX() == (getExpanderX() - view.getLeftColumn()))
7668cb45
KL
147 && (mouse.getY() == 0)
148 ) {
d36057df
KL
149 if (level == 0) {
150 // Root node can't switch.
151 return;
152 }
7668cb45
KL
153 if (selectable) {
154 // Flip expanded flag
155 expanded = !expanded;
156 if (expanded == false) {
157 // Unselect children that became invisible
158 unselect();
159 }
d36057df 160 view.setSelected(this, false);
7668cb45
KL
161 }
162 // Let subclasses do something with this
163 onExpand();
d36057df
KL
164
165 // Update the screen after any thing has expanded/contracted
166 view.alignTree();
7668cb45 167 } else if (mouse.getY() == 0) {
d36057df
KL
168 // Do the action associated with this item.
169 view.setSelected(this, false);
7668cb45
KL
170 view.dispatch();
171 }
7668cb45
KL
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
0d47c546
KL
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 ) {
d36057df
KL
196 if (level == 0) {
197 // Root node can't switch.
198 return;
199 }
0d47c546
KL
200 if (selectable) {
201 // Flip expanded flag
202 expanded = !expanded;
203 if (expanded == false) {
204 // Unselect children that became invisible
205 unselect();
206 }
d36057df 207 view.setSelected(this, false);
0d47c546
KL
208 }
209 // Let subclasses do something with this
210 onExpand();
d36057df
KL
211 } else if (keypress.equals(kbEnter)) {
212 // Do the action associated with this item.
213 view.dispatch();
0d47c546
KL
214 } else {
215 // Pass other keys (tab etc.) on to TWidget's handler.
216 super.onKeypress(keypress);
217 }
218 }
219
d36057df
KL
220 // ------------------------------------------------------------------------
221 // TWidget ----------------------------------------------------------------
222 // ------------------------------------------------------------------------
223
7668cb45
KL
224 /**
225 * Draw this item to a window.
226 */
227 @Override
228 public void draw() {
d36057df 229 if ((getY() < 0) || (getY() > getParent().getHeight() - 1)) {
7668cb45
KL
230 return;
231 }
232
d36057df 233 int offset = -view.getLeftColumn();
7668cb45
KL
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");
d36057df 243 selectedColor = getTheme().getColor("ttreeview.selected.inactive");
7668cb45
KL
244 }
245
246 if (!selectable) {
247 textColor = getTheme().getColor("ttreeview.unreadable");
248 }
249
250 // Blank out the background
a69ed767 251 hLineXY(0, 0, getWidth(), ' ', color);
7668cb45 252
7668cb45
KL
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 += "[ ] ";
d36057df
KL
263 } else {
264 line += " ";
7668cb45
KL
265 }
266 }
a69ed767 267 putStringXY(offset, 0, line, color);
7668cb45 268 if (selected) {
a69ed767 269 putStringXY(offset + line.length(), 0, text, selectedColor);
7668cb45 270 } else {
a69ed767 271 putStringXY(offset + line.length(), 0, text, textColor);
7668cb45
KL
272 }
273 if ((level > 0) && (expandable)) {
274 if (expanded) {
a69ed767 275 putCharXY(offset + getExpanderX(), 0, '-', expanderColor);
7668cb45 276 } else {
a69ed767 277 putCharXY(offset + getExpanderX(), 0, '+', expanderColor);
7668cb45
KL
278 }
279 }
280 }
281
d36057df
KL
282 // ------------------------------------------------------------------------
283 // TTreeItem --------------------------------------------------------------
284 // ------------------------------------------------------------------------
285
286 /**
287 * Get the parent TTreeView.
288 *
289 * @return the parent TTreeView
290 */
291 public final TTreeView getTreeView() {
292 return view;
293 }
294
295 /**
296 * Get the displayable text for this item.
297 *
298 * @return the displayable text for this item
299 */
300 public final String getText() {
301 return text;
302 }
303
304 /**
305 * Set the displayable text for this item.
306 *
307 * @param text the displayable text for this item
308 */
309 public final void setText(final String text) {
310 this.text = text;
311 }
312
313 /**
314 * Get expanded value.
315 *
316 * @return if true, this item is expanded
317 */
318 public final boolean isExpanded() {
319 return expanded;
320 }
321
322 /**
323 * Set expanded value.
324 *
325 * @param expanded new value
326 */
327 public final void setExpanded(final boolean expanded) {
328 if (level == 0) {
329 // Root node can't be unexpanded, ever.
330 this.expanded = true;
331 return;
332 }
333 if (level > 0) {
334 this.expanded = expanded;
335 }
336 }
337
338 /**
339 * Get expandable value.
340 *
341 * @return if true, this item is expandable
342 */
343 public final boolean isExpandable() {
344 return expandable;
345 }
346
347 /**
348 * Set expandable value.
349 *
350 * @param expandable new value
351 */
352 public final void setExpandable(final boolean expandable) {
353 if (level == 0) {
354 // Root node can't be unexpanded, ever.
355 this.expandable = true;
356 return;
357 }
358 if (level > 0) {
359 this.expandable = expandable;
360 }
361 }
362
363 /**
364 * Get the vertical bars and such along the left side.
365 *
366 * @return the vertical bars and such along the left side
367 */
368 public final String getPrefix() {
369 return prefix;
370 }
371
372 /**
373 * Get selected value.
374 *
375 * @return if true, this item is selected
376 */
377 public final boolean isSelected() {
378 return selected;
379 }
380
381 /**
382 * Set selected value.
383 *
384 * @param selected new value
385 */
386 public final void setSelected(final boolean selected) {
387 this.selected = selected;
388 }
389
390 /**
391 * Set selectable value.
392 *
393 * @param selectable new value
394 */
395 public final void setSelectable(final boolean selectable) {
396 this.selectable = selectable;
397 }
398
399 /**
400 * Get the length of the widest item to display.
401 *
402 * @return the maximum number of columns for this item or its children
403 */
404 public int getMaximumColumn() {
405 int max = prefix.length() + 4 + text.length();
406 for (TWidget widget: getChildren()) {
407 TTreeItem item = (TTreeItem) widget;
408 int n = item.prefix.length() + 4 + item.text.length();
409 if (n > max) {
410 max = n;
411 }
412 }
413 return max;
414 }
415
416 /**
417 * Recursively expand the tree into a linear array of items.
418 *
419 * @param prefix vertical bar of parent levels and such that is set on
420 * each child
421 * @param last if true, this is the "last" leaf node of a tree
422 * @return additional items to add to the array
423 */
424 public List<TTreeItem> expandTree(final String prefix, final boolean last) {
425 List<TTreeItem> array = new ArrayList<TTreeItem>();
426 this.last = last;
427 this.prefix = prefix;
428 array.add(this);
429
430 if ((getChildren().size() == 0) || !expanded) {
431 return array;
432 }
433
434 String newPrefix = prefix;
435 if (level > 0) {
436 if (last) {
437 newPrefix += " ";
438 } else {
439 newPrefix += GraphicsChars.CP437[0xB3];
440 newPrefix += ' ';
441 }
442 }
443 for (int i = 0; i < getChildren().size(); i++) {
444 TTreeItem item = (TTreeItem) getChildren().get(i);
445 if (i == getChildren().size() - 1) {
446 array.addAll(item.expandTree(newPrefix, true));
447 } else {
448 array.addAll(item.expandTree(newPrefix, false));
449 }
450 }
451 return array;
452 }
453
454 /**
455 * Get the x spot for the + or - to expand/collapse.
456 *
457 * @return column of the expand/collapse button
458 */
459 private int getExpanderX() {
460 if ((level == 0) || (!expandable)) {
461 return 0;
462 }
463 return prefix.length() + 3;
464 }
465
466 /**
467 * Recursively unselect me and my children.
468 */
469 public void unselect() {
470 if (selected == true) {
471 selected = false;
472 view.setSelected(null, false);
473 }
474 for (TWidget widget: getChildren()) {
475 if (widget instanceof TTreeItem) {
476 TTreeItem item = (TTreeItem) widget;
477 item.unselect();
478 }
479 }
480 }
481
7668cb45 482}