2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2016 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 TWidget
{
43 private TVScroller vScroller
;
46 * Horizontal scrollbar.
48 private THScroller hScroller
;
51 * Get the horizontal scrollbar. This is used by TTreeItem.draw(), and
52 * potentially subclasses.
54 * @return the horizontal scrollbar
56 public final THScroller
getHScroller() {
63 private TTreeItem treeRoot
;
66 * Get the root of the tree.
68 * @return the root of the tree
70 public final TTreeItem
getTreeRoot() {
75 * Set the root of the tree.
77 * @param treeRoot the new root of the tree
79 public final void setTreeRoot(final TTreeItem treeRoot
) {
80 this.treeRoot
= treeRoot
;
84 * Maximum width of a single line.
86 private int maxLineWidth
;
89 * Only one of my children can be selected.
91 private TTreeItem selectedItem
= null;
94 * If true, move the window to put the selected item in view. This
95 * normally only happens once after setting treeRoot.
97 private boolean centerWindow
= false;
100 * The action to perform when the user selects an item.
102 private TAction action
= null;
107 * @param treeRoot ultimate root of tree
108 * @param centerWindow if true, move the window to put the root in view
110 public void setTreeRoot(final TTreeItem treeRoot
,
111 final boolean centerWindow
) {
113 this.treeRoot
= treeRoot
;
114 this.centerWindow
= centerWindow
;
118 * Public constructor.
120 * @param parent parent widget
121 * @param x column relative to parent
122 * @param y row relative to parent
123 * @param width width of tree view
124 * @param height height of tree view
126 public TTreeView(final TWidget parent
, final int x
, final int y
,
127 final int width
, final int height
) {
129 this(parent
, x
, y
, width
, height
, null);
133 * Public constructor.
135 * @param parent parent widget
136 * @param x column relative to parent
137 * @param y row relative to parent
138 * @param width width of tree view
139 * @param height height of tree view
140 * @param action action to perform when an item is selected
142 public TTreeView(final TWidget parent
, final int x
, final int y
,
143 final int width
, final int height
, final TAction action
) {
145 super(parent
, x
, y
, width
, height
);
146 this.action
= action
;
150 * Get the tree view item that was selected.
152 * @return the selected item, or null if no item is selected
154 public final TTreeItem
getSelected() {
159 * Set the new selected tree view item.
161 * @param item new item that became selected
163 public void setSelected(final TTreeItem item
) {
165 item
.setSelected(true);
167 if ((selectedItem
!= null) && (selectedItem
!= item
)) {
168 selectedItem
.setSelected(false);
174 * Perform user selection action.
176 public void dispatch() {
177 if (action
!= null) {
183 * Update (or instantiate) vScroller and hScroller.
185 private void updateScrollers() {
186 // Setup vertical scroller
187 if (vScroller
== null) {
188 vScroller
= new TVScroller(this, getWidth() - 1, 0,
190 vScroller
.setValue(0);
191 vScroller
.setTopValue(0);
193 vScroller
.setX(getWidth() - 1);
194 vScroller
.setHeight(getHeight() - 1);
195 vScroller
.setBigChange(getHeight() - 1);
197 // Setup horizontal scroller
198 if (hScroller
== null) {
199 hScroller
= new THScroller(this, 0, getHeight() - 1,
201 hScroller
.setValue(0);
202 hScroller
.setLeftValue(0);
204 hScroller
.setY(getHeight() - 1);
205 hScroller
.setWidth(getWidth() - 1);
206 hScroller
.setBigChange(getWidth() - 1);
210 * Resize text and scrollbars for a new width/height.
212 public void reflow() {
214 boolean foundSelectedRow
= false;
217 if (treeRoot
== null) {
221 // Make each child invisible/inactive to start, expandTree() will
222 // reactivate the visible ones.
223 for (TWidget widget
: getChildren()) {
224 if (widget
instanceof TTreeItem
) {
225 TTreeItem item
= (TTreeItem
) widget
;
226 item
.setInvisible(true);
227 item
.setEnabled(false);
228 item
.keyboardPrevious
= null;
229 item
.keyboardNext
= null;
233 // Expand the tree into a linear list
234 getChildren().clear();
235 getChildren().addAll(treeRoot
.expandTree("", true));
237 // Locate the selected row and maximum line width
238 for (TWidget widget
: getChildren()) {
239 TTreeItem item
= (TTreeItem
) widget
;
241 if (item
== selectedItem
) {
242 foundSelectedRow
= true;
244 if (!foundSelectedRow
) {
248 int lineWidth
= item
.getText().length()
249 + item
.getPrefix().length() + 4;
250 if (lineWidth
> maxLineWidth
) {
251 maxLineWidth
= lineWidth
;
255 if ((centerWindow
) && (foundSelectedRow
)) {
256 if ((selectedRow
< vScroller
.getValue())
257 || (selectedRow
> vScroller
.getValue() + getHeight() - 2)
259 vScroller
.setValue(selectedRow
);
260 centerWindow
= false;
265 // Rescale the scroll bars
266 vScroller
.setBottomValue(getChildren().size() - getHeight() + 1);
267 if (vScroller
.getBottomValue() < 0) {
268 vScroller
.setBottomValue(0);
271 if (vScroller.getValue() > vScroller.getBottomValue()) {
272 vScroller.setValue(vScroller.getBottomValue());
275 hScroller
.setRightValue(maxLineWidth
- getWidth() + 3);
276 if (hScroller
.getRightValue() < 0) {
277 hScroller
.setRightValue(0);
280 if (hScroller.getValue() > hScroller.getRightValue()) {
281 hScroller.setValue(hScroller.getRightValue());
284 getChildren().add(hScroller
);
285 getChildren().add(vScroller
);
289 * Update the Y positions of all the children items.
291 private void updatePositions() {
292 if (treeRoot
== null) {
296 int begin
= vScroller
.getValue();
299 // As we walk the list we also adjust next/previous pointers,
300 // resulting in a doubly-linked list but only of the expanded items.
303 for (int i
= 0; i
< getChildren().size(); i
++) {
304 if (!(getChildren().get(i
) instanceof TTreeItem
)) {
305 // Skip the scrollbars
308 TTreeItem item
= (TTreeItem
) getChildren().get(i
);
311 item
.keyboardPrevious
= p
;
312 p
.keyboardNext
= item
;
318 item
.setEnabled(false);
319 item
.setInvisible(true);
323 if (topY
>= getHeight() - 1) {
325 item
.setEnabled(false);
326 item
.setInvisible(true);
331 item
.setEnabled(true);
332 item
.setInvisible(false);
333 item
.setWidth(getWidth() - 1);
340 * Handle mouse press events.
342 * @param mouse mouse button press event
345 public void onMouseDown(final TMouseEvent mouse
) {
346 if (mouse
.isMouseWheelUp()) {
347 vScroller
.decrement();
348 } else if (mouse
.isMouseWheelDown()) {
349 vScroller
.increment();
352 super.onMouseDown(mouse
);
355 // Update the screen after the scrollbars have moved
360 * Handle mouse release events.
362 * @param mouse mouse button release event
365 public void onMouseUp(final TMouseEvent mouse
) {
367 super.onMouseDown(mouse
);
369 // Update the screen after any thing has expanded/contracted
376 * @param keypress keystroke event
379 public void onKeypress(final TKeypressEvent keypress
) {
380 if (keypress
.equals(kbShiftLeft
)
381 || keypress
.equals(kbCtrlLeft
)
382 || keypress
.equals(kbAltLeft
)
384 hScroller
.decrement();
385 } else if (keypress
.equals(kbShiftRight
)
386 || keypress
.equals(kbCtrlRight
)
387 || keypress
.equals(kbAltRight
)
389 hScroller
.increment();
390 } else if (keypress
.equals(kbShiftUp
)
391 || keypress
.equals(kbCtrlUp
)
392 || keypress
.equals(kbAltUp
)
394 vScroller
.decrement();
395 } else if (keypress
.equals(kbShiftDown
)
396 || keypress
.equals(kbCtrlDown
)
397 || keypress
.equals(kbAltDown
)
399 vScroller
.increment();
400 } else if (keypress
.equals(kbShiftPgUp
)
401 || keypress
.equals(kbCtrlPgUp
)
402 || keypress
.equals(kbAltPgUp
)
404 vScroller
.bigDecrement();
405 } else if (keypress
.equals(kbShiftPgDn
)
406 || keypress
.equals(kbCtrlPgDn
)
407 || keypress
.equals(kbAltPgDn
)
409 vScroller
.bigIncrement();
410 } else if (keypress
.equals(kbHome
)) {
412 } else if (keypress
.equals(kbEnd
)) {
413 vScroller
.toBottom();
414 } else if (keypress
.equals(kbEnter
)) {
415 if (selectedItem
!= null) {
418 } else if (keypress
.equals(kbUp
)) {
419 // Select the previous item
420 if (selectedItem
!= null) {
421 TTreeItem oldItem
= selectedItem
;
422 if (selectedItem
.keyboardPrevious
!= null) {
423 setSelected(selectedItem
.keyboardPrevious
);
424 if (oldItem
.getY() == 0) {
425 vScroller
.decrement();
429 } else if (keypress
.equals(kbDown
)) {
430 // Select the next item
431 if (selectedItem
!= null) {
432 TTreeItem oldItem
= selectedItem
;
433 if (selectedItem
.keyboardNext
!= null) {
434 setSelected(selectedItem
.keyboardNext
);
435 if (oldItem
.getY() == getHeight() - 2) {
436 vScroller
.increment();
440 } else if (keypress
.equals(kbTab
)) {
441 getParent().switchWidget(true);
443 } else if (keypress
.equals(kbShiftTab
)
444 || keypress
.equals(kbBackTab
)) {
445 getParent().switchWidget(false);
447 } else if (selectedItem
!= null) {
448 // Give the TTreeItem a chance to handle arrow keys
449 selectedItem
.onKeypress(keypress
);
451 // Pass other keys (tab etc.) on to TWidget's handler.
452 super.onKeypress(keypress
);
456 // Update the screen after any thing has expanded/contracted