2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2017 Kevin Lamonte
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:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
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.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
31 import jexer
.event
.TKeypressEvent
;
32 import jexer
.event
.TMouseEvent
;
33 import static jexer
.TKeypress
.*;
36 * TTreeView implements a simple tree view.
38 public class TTreeView
extends TScrollableWidget
{
43 private TTreeItem treeRoot
;
46 * Get the root of the tree.
48 * @return the root of the tree
50 public final TTreeItem
getTreeRoot() {
55 * Set the root of the tree.
57 * @param treeRoot the new root of the tree
59 public final void setTreeRoot(final TTreeItem treeRoot
) {
60 this.treeRoot
= treeRoot
;
64 * Maximum width of a single line.
66 private int maxLineWidth
;
69 * Only one of my children can be selected.
71 private TTreeItem selectedItem
= null;
74 * If true, move the window to put the selected item in view. This
75 * normally only happens once after setting treeRoot.
77 private boolean centerWindow
= false;
80 * The action to perform when the user selects an item.
82 private TAction action
= null;
87 * @param treeRoot ultimate root of tree
88 * @param centerWindow if true, move the window to put the root in view
90 public void setTreeRoot(final TTreeItem treeRoot
,
91 final boolean centerWindow
) {
93 this.treeRoot
= treeRoot
;
94 this.centerWindow
= centerWindow
;
100 * @param parent parent widget
101 * @param x column relative to parent
102 * @param y row relative to parent
103 * @param width width of tree view
104 * @param height height of tree view
106 public TTreeView(final TWidget parent
, final int x
, final int y
,
107 final int width
, final int height
) {
109 this(parent
, x
, y
, width
, height
, null);
113 * Public constructor.
115 * @param parent parent widget
116 * @param x column relative to parent
117 * @param y row relative to parent
118 * @param width width of tree view
119 * @param height height of tree view
120 * @param action action to perform when an item is selected
122 public TTreeView(final TWidget parent
, final int x
, final int y
,
123 final int width
, final int height
, final TAction action
) {
125 super(parent
, x
, y
, width
, height
);
126 this.action
= action
;
128 vScroller
= new TVScroller(this, getWidth() - 1, 0, getHeight() - 1);
129 hScroller
= new THScroller(this, 0, getHeight() - 1, getWidth() - 1);
133 * Get the tree view item that was selected.
135 * @return the selected item, or null if no item is selected
137 public final TTreeItem
getSelected() {
142 * Set the new selected tree view item.
144 * @param item new item that became selected
146 public void setSelected(final TTreeItem item
) {
148 item
.setSelected(true);
150 if ((selectedItem
!= null) && (selectedItem
!= item
)) {
151 selectedItem
.setSelected(false);
157 * Perform user selection action.
159 public void dispatch() {
160 if (action
!= null) {
166 * Resize text and scrollbars for a new width/height.
169 public void reflowData() {
171 boolean foundSelectedRow
= false;
173 if (treeRoot
== null) {
177 // Make each child invisible/inactive to start, expandTree() will
178 // reactivate the visible ones.
179 for (TWidget widget
: getChildren()) {
180 if (widget
instanceof TTreeItem
) {
181 TTreeItem item
= (TTreeItem
) widget
;
182 item
.setInvisible(true);
183 item
.setEnabled(false);
184 item
.keyboardPrevious
= null;
185 item
.keyboardNext
= null;
189 // Expand the tree into a linear list
190 getChildren().clear();
191 getChildren().addAll(treeRoot
.expandTree("", true));
193 // Locate the selected row and maximum line width
194 for (TWidget widget
: getChildren()) {
195 TTreeItem item
= (TTreeItem
) widget
;
197 if (item
== selectedItem
) {
198 foundSelectedRow
= true;
200 if (!foundSelectedRow
) {
204 int lineWidth
= item
.getText().length()
205 + item
.getPrefix().length() + 4;
206 if (lineWidth
> maxLineWidth
) {
207 maxLineWidth
= lineWidth
;
211 if ((centerWindow
) && (foundSelectedRow
)) {
212 if ((selectedRow
< getVerticalValue())
213 || (selectedRow
> getVerticalValue() + getHeight() - 2)
215 setVerticalValue(selectedRow
);
216 centerWindow
= false;
221 // Rescale the scroll bars
222 setBottomValue(getChildren().size() - getHeight() + 1);
223 if (getBottomValue() < 0) {
226 if (getVerticalValue() > getBottomValue()) {
227 setVerticalValue(getBottomValue());
229 setRightValue(maxLineWidth
- getWidth() + 3);
230 if (getRightValue() < 0) {
233 if (getHorizontalValue() > getRightValue()) {
234 setHorizontalValue(getRightValue());
236 getChildren().add(hScroller
);
237 getChildren().add(vScroller
);
241 * Update the Y positions of all the children items.
243 private void updatePositions() {
244 if (treeRoot
== null) {
248 int begin
= getVerticalValue();
251 // As we walk the list we also adjust next/previous pointers,
252 // resulting in a doubly-linked list but only of the expanded items.
255 for (int i
= 0; i
< getChildren().size(); i
++) {
256 if (!(getChildren().get(i
) instanceof TTreeItem
)) {
257 // Skip the scrollbars
260 TTreeItem item
= (TTreeItem
) getChildren().get(i
);
263 item
.keyboardPrevious
= p
;
264 p
.keyboardNext
= item
;
270 item
.setEnabled(false);
271 item
.setInvisible(true);
275 if (topY
>= getHeight() - 1) {
277 item
.setEnabled(false);
278 item
.setInvisible(true);
283 item
.setEnabled(true);
284 item
.setInvisible(false);
285 item
.setWidth(getWidth() - 1);
292 * Handle mouse press events.
294 * @param mouse mouse button press event
297 public void onMouseDown(final TMouseEvent mouse
) {
298 if (mouse
.isMouseWheelUp()) {
300 } else if (mouse
.isMouseWheelDown()) {
304 super.onMouseDown(mouse
);
307 // Update the screen after the scrollbars have moved
312 * Handle mouse release events.
314 * @param mouse mouse button release event
317 public void onMouseUp(final TMouseEvent mouse
) {
319 super.onMouseDown(mouse
);
321 // Update the screen after any thing has expanded/contracted
328 * @param keypress keystroke event
331 public void onKeypress(final TKeypressEvent keypress
) {
332 if (keypress
.equals(kbShiftLeft
)
333 || keypress
.equals(kbCtrlLeft
)
334 || keypress
.equals(kbAltLeft
)
336 horizontalDecrement();
337 } else if (keypress
.equals(kbShiftRight
)
338 || keypress
.equals(kbCtrlRight
)
339 || keypress
.equals(kbAltRight
)
341 horizontalIncrement();
342 } else if (keypress
.equals(kbShiftUp
)
343 || keypress
.equals(kbCtrlUp
)
344 || keypress
.equals(kbAltUp
)
347 } else if (keypress
.equals(kbShiftDown
)
348 || keypress
.equals(kbCtrlDown
)
349 || keypress
.equals(kbAltDown
)
352 } else if (keypress
.equals(kbShiftPgUp
)
353 || keypress
.equals(kbCtrlPgUp
)
354 || keypress
.equals(kbAltPgUp
)
356 bigVerticalDecrement();
357 } else if (keypress
.equals(kbShiftPgDn
)
358 || keypress
.equals(kbCtrlPgDn
)
359 || keypress
.equals(kbAltPgDn
)
361 bigVerticalIncrement();
362 } else if (keypress
.equals(kbHome
)) {
364 } else if (keypress
.equals(kbEnd
)) {
366 } else if (keypress
.equals(kbEnter
)) {
367 if (selectedItem
!= null) {
370 } else if (keypress
.equals(kbUp
)) {
371 // Select the previous item
372 if (selectedItem
!= null) {
373 TTreeItem oldItem
= selectedItem
;
374 if (selectedItem
.keyboardPrevious
!= null) {
375 setSelected(selectedItem
.keyboardPrevious
);
376 if (oldItem
.getY() == 0) {
381 } else if (keypress
.equals(kbDown
)) {
382 // Select the next item
383 if (selectedItem
!= null) {
384 TTreeItem oldItem
= selectedItem
;
385 if (selectedItem
.keyboardNext
!= null) {
386 setSelected(selectedItem
.keyboardNext
);
387 if (oldItem
.getY() == getHeight() - 2) {
392 } else if (keypress
.equals(kbTab
)) {
393 getParent().switchWidget(true);
395 } else if (keypress
.equals(kbShiftTab
)
396 || keypress
.equals(kbBackTab
)) {
397 getParent().switchWidget(false);
399 } else if (selectedItem
!= null) {
400 // Give the TTreeItem a chance to handle arrow keys
401 selectedItem
.onKeypress(keypress
);
403 // Pass other keys (tab etc.) on to TWidget's handler.
404 super.onKeypress(keypress
);
408 // Update the screen after any thing has expanded/contracted