2 * Jexer - Java Text User Interface
4 * License: LGPLv3 or later
6 * This module is licensed under the GNU Lesser General Public License
7 * Version 3. Please see the file "COPYING" in this directory for more
8 * information about the GNU Lesser General Public License Version 3.
10 * Copyright (C) 2015 Kevin Lamonte
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public License
14 * as published by the Free Software Foundation; either version 3 of
15 * the License, or (at your option) any later version.
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this program; if not, see
24 * http://www.gnu.org/licenses/, or write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
33 import jexer
.event
.TKeypressEvent
;
34 import jexer
.event
.TMouseEvent
;
35 import static jexer
.TKeypress
.*;
38 * TTreeView implements a simple tree view.
40 public class TTreeView
extends TWidget
{
45 private TVScroller vScroller
;
48 * Horizontal scrollbar.
50 private THScroller hScroller
;
53 * Get the horizontal scrollbar. This is used by TTreeItem.draw(), and
54 * potentially subclasses.
56 * @return the horizontal scrollbar
58 public final THScroller
getHScroller() {
65 private TTreeItem treeRoot
;
68 * Get the root of the tree.
70 * @return the root of the tree
72 public final TTreeItem
getTreeRoot() {
77 * Set the root of the tree.
79 * @param treeRoot the new root of the tree
81 public final void setTreeRoot(final TTreeItem treeRoot
) {
82 this.treeRoot
= treeRoot
;
86 * Maximum width of a single line.
88 private int maxLineWidth
;
91 * Only one of my children can be selected.
93 private TTreeItem selectedItem
= null;
96 * If true, move the window to put the selected item in view. This
97 * normally only happens once after setting treeRoot.
99 private boolean centerWindow
= false;
102 * The action to perform when the user selects an item.
104 private TAction action
= null;
109 * @param treeRoot ultimate root of tree
110 * @param centerWindow if true, move the window to put the root in view
112 public void setTreeRoot(final TTreeItem treeRoot
,
113 final boolean centerWindow
) {
115 this.treeRoot
= treeRoot
;
116 this.centerWindow
= centerWindow
;
120 * Public constructor.
122 * @param parent parent widget
123 * @param x column relative to parent
124 * @param y row relative to parent
125 * @param width width of tree view
126 * @param height height of tree view
128 public TTreeView(final TWidget parent
, final int x
, final int y
,
129 final int width
, final int height
) {
131 this(parent
, x
, y
, width
, height
, null);
135 * Public constructor.
137 * @param parent parent widget
138 * @param x column relative to parent
139 * @param y row relative to parent
140 * @param width width of tree view
141 * @param height height of tree view
142 * @param action action to perform when an item is selected
144 public TTreeView(final TWidget parent
, final int x
, final int y
,
145 final int width
, final int height
, final TAction action
) {
147 super(parent
, x
, y
, width
, height
);
148 this.action
= action
;
152 * Get the tree view item that was selected.
154 * @return the selected item, or null if no item is selected
156 public final TTreeItem
getSelected() {
161 * Set the new selected tree view item.
163 * @param item new item that became selected
165 public void setSelected(final TTreeItem item
) {
167 item
.setSelected(true);
169 if ((selectedItem
!= null) && (selectedItem
!= item
)) {
170 selectedItem
.setSelected(false);
176 * Perform user selection action.
178 public void dispatch() {
179 if (action
!= null) {
185 * Update (or instantiate) vScroller and hScroller.
187 private void updateScrollers() {
188 // Setup vertical scroller
189 if (vScroller
== null) {
190 vScroller
= new TVScroller(this, getWidth() - 1, 0,
192 vScroller
.setValue(0);
193 vScroller
.setTopValue(0);
195 vScroller
.setX(getWidth() - 1);
196 vScroller
.setHeight(getHeight() - 1);
197 vScroller
.setBigChange(getHeight() - 1);
199 // Setup horizontal scroller
200 if (hScroller
== null) {
201 hScroller
= new THScroller(this, 0, getHeight() - 1,
203 hScroller
.setValue(0);
204 hScroller
.setLeftValue(0);
206 hScroller
.setY(getHeight() - 1);
207 hScroller
.setWidth(getWidth() - 1);
208 hScroller
.setBigChange(getWidth() - 1);
212 * Resize text and scrollbars for a new width/height.
214 public void reflow() {
216 boolean foundSelectedRow
= false;
219 if (treeRoot
== null) {
223 // Make each child invisible/inactive to start, expandTree() will
224 // reactivate the visible ones.
225 for (TWidget widget
: getChildren()) {
226 if (widget
instanceof TTreeItem
) {
227 TTreeItem item
= (TTreeItem
) widget
;
228 item
.setInvisible(true);
229 item
.setEnabled(false);
230 item
.keyboardPrevious
= null;
231 item
.keyboardNext
= null;
235 // Expand the tree into a linear list
236 getChildren().clear();
237 getChildren().addAll(treeRoot
.expandTree("", true));
239 // Locate the selected row and maximum line width
240 for (TWidget widget
: getChildren()) {
241 TTreeItem item
= (TTreeItem
) widget
;
243 if (item
== selectedItem
) {
244 foundSelectedRow
= true;
246 if (!foundSelectedRow
) {
250 int lineWidth
= item
.getText().length()
251 + item
.getPrefix().length() + 4;
252 if (lineWidth
> maxLineWidth
) {
253 maxLineWidth
= lineWidth
;
257 if ((centerWindow
) && (foundSelectedRow
)) {
258 if ((selectedRow
< vScroller
.getValue())
259 || (selectedRow
> vScroller
.getValue() + getHeight() - 2)
261 vScroller
.setValue(selectedRow
);
262 centerWindow
= false;
267 // Rescale the scroll bars
268 vScroller
.setBottomValue(getChildren().size() - getHeight() + 1);
269 if (vScroller
.getBottomValue() < 0) {
270 vScroller
.setBottomValue(0);
273 if (vScroller.getValue() > vScroller.getBottomValue()) {
274 vScroller.setValue(vScroller.getBottomValue());
277 hScroller
.setRightValue(maxLineWidth
- getWidth() + 3);
278 if (hScroller
.getRightValue() < 0) {
279 hScroller
.setRightValue(0);
282 if (hScroller.getValue() > hScroller.getRightValue()) {
283 hScroller.setValue(hScroller.getRightValue());
286 getChildren().add(hScroller
);
287 getChildren().add(vScroller
);
291 * Update the Y positions of all the children items.
293 private void updatePositions() {
294 if (treeRoot
== null) {
298 int begin
= vScroller
.getValue();
301 // As we walk the list we also adjust next/previous pointers,
302 // resulting in a doubly-linked list but only of the expanded items.
305 for (int i
= 0; i
< getChildren().size(); i
++) {
306 if (!(getChildren().get(i
) instanceof TTreeItem
)) {
307 // Skip the scrollbars
310 TTreeItem item
= (TTreeItem
) getChildren().get(i
);
313 item
.keyboardPrevious
= p
;
314 p
.keyboardNext
= item
;
320 item
.setEnabled(false);
321 item
.setInvisible(true);
325 if (topY
>= getHeight() - 1) {
327 item
.setEnabled(false);
328 item
.setInvisible(true);
333 item
.setEnabled(true);
334 item
.setInvisible(false);
335 item
.setWidth(getWidth() - 1);
342 * Handle mouse press events.
344 * @param mouse mouse button press event
347 public void onMouseDown(final TMouseEvent mouse
) {
348 if (mouse
.isMouseWheelUp()) {
349 vScroller
.decrement();
350 } else if (mouse
.isMouseWheelDown()) {
351 vScroller
.increment();
354 super.onMouseDown(mouse
);
357 // Update the screen after the scrollbars have moved
362 * Handle mouse release events.
364 * @param mouse mouse button release event
367 public void onMouseUp(final TMouseEvent mouse
) {
369 super.onMouseDown(mouse
);
371 // Update the screen after any thing has expanded/contracted
378 * @param keypress keystroke event
381 public void onKeypress(final TKeypressEvent keypress
) {
382 if (keypress
.equals(kbShiftLeft
)
383 || keypress
.equals(kbCtrlLeft
)
384 || keypress
.equals(kbAltLeft
)
386 hScroller
.decrement();
387 } else if (keypress
.equals(kbShiftRight
)
388 || keypress
.equals(kbCtrlRight
)
389 || keypress
.equals(kbAltRight
)
391 hScroller
.increment();
392 } else if (keypress
.equals(kbShiftUp
)
393 || keypress
.equals(kbCtrlUp
)
394 || keypress
.equals(kbAltUp
)
396 vScroller
.decrement();
397 } else if (keypress
.equals(kbShiftDown
)
398 || keypress
.equals(kbCtrlDown
)
399 || keypress
.equals(kbAltDown
)
401 vScroller
.increment();
402 } else if (keypress
.equals(kbShiftPgUp
)
403 || keypress
.equals(kbCtrlPgUp
)
404 || keypress
.equals(kbAltPgUp
)
406 vScroller
.bigDecrement();
407 } else if (keypress
.equals(kbShiftPgDn
)
408 || keypress
.equals(kbCtrlPgDn
)
409 || keypress
.equals(kbAltPgDn
)
411 vScroller
.bigIncrement();
412 } else if (keypress
.equals(kbHome
)) {
414 } else if (keypress
.equals(kbEnd
)) {
415 vScroller
.toBottom();
416 } else if (keypress
.equals(kbEnter
)) {
417 if (selectedItem
!= null) {
420 } else if (keypress
.equals(kbUp
)) {
421 // Select the previous item
422 if (selectedItem
!= null) {
423 TTreeItem oldItem
= selectedItem
;
424 if (selectedItem
.keyboardPrevious
!= null) {
425 setSelected(selectedItem
.keyboardPrevious
);
426 if (oldItem
.getY() == 0) {
427 vScroller
.decrement();
431 } else if (keypress
.equals(kbDown
)) {
432 // Select the next item
433 if (selectedItem
!= null) {
434 TTreeItem oldItem
= selectedItem
;
435 if (selectedItem
.keyboardNext
!= null) {
436 setSelected(selectedItem
.keyboardNext
);
437 if (oldItem
.getY() == getHeight() - 2) {
438 vScroller
.increment();
442 } else if (keypress
.equals(kbTab
)) {
443 getParent().switchWidget(true);
445 } else if (keypress
.equals(kbShiftTab
)
446 || keypress
.equals(kbBackTab
)) {
447 getParent().switchWidget(false);
449 } else if (selectedItem
!= null) {
450 // Give the TTreeItem a chance to handle arrow keys
451 selectedItem
.onKeypress(keypress
);
453 // Pass other keys (tab etc.) on to TWidget's handler.
454 super.onKeypress(keypress
);
458 // Update the screen after any thing has expanded/contracted