Expose width/height in TApplication constructor, attempt on ECMA48
[nikiroo-utils.git] / src / jexer / TTreeView.java
CommitLineData
daa4106c 1/*
7668cb45
KL
2 * Jexer - Java Text User Interface
3 *
e16dda65 4 * The MIT License (MIT)
7668cb45 5 *
a2018e99 6 * Copyright (C) 2017 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 */
29package jexer;
30
7668cb45
KL
31import jexer.event.TKeypressEvent;
32import jexer.event.TMouseEvent;
33import static jexer.TKeypress.*;
34
35/**
36 * TTreeView implements a simple tree view.
37 */
56661844 38public class TTreeView extends TScrollableWidget {
7668cb45
KL
39
40 /**
41 * Root of the tree.
42 */
43 private TTreeItem treeRoot;
44
45 /**
46 * Get the root of the tree.
47 *
48 * @return the root of the tree
49 */
50 public final TTreeItem getTreeRoot() {
51 return treeRoot;
52 }
53
54 /**
55 * Set the root of the tree.
56 *
57 * @param treeRoot the new root of the tree
58 */
59 public final void setTreeRoot(final TTreeItem treeRoot) {
60 this.treeRoot = treeRoot;
61 }
62
63 /**
64 * Maximum width of a single line.
65 */
66 private int maxLineWidth;
67
68 /**
69 * Only one of my children can be selected.
70 */
71 private TTreeItem selectedItem = null;
72
73 /**
74 * If true, move the window to put the selected item in view. This
75 * normally only happens once after setting treeRoot.
76 */
329fd62e 77 private boolean centerWindow = false;
7668cb45
KL
78
79 /**
80 * The action to perform when the user selects an item.
81 */
82 private TAction action = null;
83
84 /**
85 * Set treeRoot.
86 *
87 * @param treeRoot ultimate root of tree
88 * @param centerWindow if true, move the window to put the root in view
89 */
329fd62e
KL
90 public void setTreeRoot(final TTreeItem treeRoot,
91 final boolean centerWindow) {
92
7668cb45
KL
93 this.treeRoot = treeRoot;
94 this.centerWindow = centerWindow;
95 }
96
97 /**
98 * Public constructor.
99 *
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
105 */
106 public TTreeView(final TWidget parent, final int x, final int y,
107 final int width, final int height) {
108
109 this(parent, x, y, width, height, null);
110 }
111
112 /**
113 * Public constructor.
114 *
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
121 */
122 public TTreeView(final TWidget parent, final int x, final int y,
123 final int width, final int height, final TAction action) {
124
125 super(parent, x, y, width, height);
126 this.action = action;
56661844
KL
127
128 vScroller = new TVScroller(this, getWidth() - 1, 0, getHeight() - 1);
129 hScroller = new THScroller(this, 0, getHeight() - 1, getWidth() - 1);
7668cb45
KL
130 }
131
132 /**
133 * Get the tree view item that was selected.
134 *
135 * @return the selected item, or null if no item is selected
136 */
137 public final TTreeItem getSelected() {
138 return selectedItem;
139 }
140
141 /**
0d47c546 142 * Set the new selected tree view item.
7668cb45
KL
143 *
144 * @param item new item that became selected
145 */
0d47c546 146 public void setSelected(final TTreeItem item) {
7668cb45
KL
147 if (item != null) {
148 item.setSelected(true);
149 }
150 if ((selectedItem != null) && (selectedItem != item)) {
151 selectedItem.setSelected(false);
152 }
153 selectedItem = item;
154 }
155
156 /**
0d47c546 157 * Perform user selection action.
7668cb45 158 */
0d47c546 159 public void dispatch() {
7668cb45
KL
160 if (action != null) {
161 action.DO();
162 }
163 }
164
7668cb45
KL
165 /**
166 * Resize text and scrollbars for a new width/height.
167 */
56661844
KL
168 @Override
169 public void reflowData() {
7668cb45
KL
170 int selectedRow = 0;
171 boolean foundSelectedRow = false;
172
7668cb45
KL
173 if (treeRoot == null) {
174 return;
175 }
176
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);
0d47c546
KL
184 item.keyboardPrevious = null;
185 item.keyboardNext = null;
7668cb45
KL
186 }
187 }
188
189 // Expand the tree into a linear list
190 getChildren().clear();
191 getChildren().addAll(treeRoot.expandTree("", true));
0d47c546
KL
192
193 // Locate the selected row and maximum line width
7668cb45
KL
194 for (TWidget widget: getChildren()) {
195 TTreeItem item = (TTreeItem) widget;
196
197 if (item == selectedItem) {
198 foundSelectedRow = true;
199 }
329fd62e 200 if (!foundSelectedRow) {
7668cb45
KL
201 selectedRow++;
202 }
203
204 int lineWidth = item.getText().length()
329fd62e 205 + item.getPrefix().length() + 4;
7668cb45
KL
206 if (lineWidth > maxLineWidth) {
207 maxLineWidth = lineWidth;
208 }
209 }
0d47c546 210
7668cb45 211 if ((centerWindow) && (foundSelectedRow)) {
56661844
KL
212 if ((selectedRow < getVerticalValue())
213 || (selectedRow > getVerticalValue() + getHeight() - 2)
7668cb45 214 ) {
56661844 215 setVerticalValue(selectedRow);
7668cb45
KL
216 centerWindow = false;
217 }
218 }
219 updatePositions();
220
221 // Rescale the scroll bars
56661844
KL
222 setBottomValue(getChildren().size() - getHeight() + 1);
223 if (getBottomValue() < 0) {
224 setBottomValue(0);
7668cb45 225 }
56661844
KL
226 if (getVerticalValue() > getBottomValue()) {
227 setVerticalValue(getBottomValue());
7668cb45 228 }
56661844
KL
229 setRightValue(maxLineWidth - getWidth() + 3);
230 if (getRightValue() < 0) {
231 setRightValue(0);
7668cb45 232 }
56661844
KL
233 if (getHorizontalValue() > getRightValue()) {
234 setHorizontalValue(getRightValue());
7668cb45 235 }
7668cb45
KL
236 getChildren().add(hScroller);
237 getChildren().add(vScroller);
238 }
239
240 /**
241 * Update the Y positions of all the children items.
242 */
243 private void updatePositions() {
244 if (treeRoot == null) {
245 return;
246 }
247
56661844 248 int begin = getVerticalValue();
7668cb45 249 int topY = 0;
0d47c546
KL
250
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.
253 TTreeItem p = null;
254
7668cb45
KL
255 for (int i = 0; i < getChildren().size(); i++) {
256 if (!(getChildren().get(i) instanceof TTreeItem)) {
0d47c546 257 // Skip the scrollbars
7668cb45
KL
258 continue;
259 }
260 TTreeItem item = (TTreeItem) getChildren().get(i);
261
0d47c546
KL
262 if (p != null) {
263 item.keyboardPrevious = p;
264 p.keyboardNext = item;
265 }
266 p = item;
267
7668cb45
KL
268 if (i < begin) {
269 // Render invisible
270 item.setEnabled(false);
271 item.setInvisible(true);
272 continue;
273 }
274
275 if (topY >= getHeight() - 1) {
276 // Render invisible
277 item.setEnabled(false);
278 item.setInvisible(true);
279 continue;
280 }
281
282 item.setY(topY);
283 item.setEnabled(true);
284 item.setInvisible(false);
285 item.setWidth(getWidth() - 1);
286 topY++;
287 }
0d47c546 288
7668cb45
KL
289 }
290
291 /**
292 * Handle mouse press events.
293 *
294 * @param mouse mouse button press event
295 */
296 @Override
297 public void onMouseDown(final TMouseEvent mouse) {
298 if (mouse.isMouseWheelUp()) {
56661844 299 verticalDecrement();
7668cb45 300 } else if (mouse.isMouseWheelDown()) {
56661844 301 verticalIncrement();
7668cb45
KL
302 } else {
303 // Pass to children
304 super.onMouseDown(mouse);
305 }
306
307 // Update the screen after the scrollbars have moved
56661844 308 reflowData();
7668cb45
KL
309 }
310
311 /**
312 * Handle mouse release events.
313 *
314 * @param mouse mouse button release event
315 */
316 @Override
329fd62e 317 public void onMouseUp(final TMouseEvent mouse) {
7668cb45
KL
318 // Pass to children
319 super.onMouseDown(mouse);
320
321 // Update the screen after any thing has expanded/contracted
56661844 322 reflowData();
7668cb45
KL
323 }
324
325 /**
326 * Handle keystrokes.
327 *
328 * @param keypress keystroke event
329 */
330 @Override
331 public void onKeypress(final TKeypressEvent keypress) {
0d47c546
KL
332 if (keypress.equals(kbShiftLeft)
333 || keypress.equals(kbCtrlLeft)
334 || keypress.equals(kbAltLeft)
335 ) {
56661844 336 horizontalDecrement();
0d47c546
KL
337 } else if (keypress.equals(kbShiftRight)
338 || keypress.equals(kbCtrlRight)
339 || keypress.equals(kbAltRight)
340 ) {
56661844 341 horizontalIncrement();
0d47c546
KL
342 } else if (keypress.equals(kbShiftUp)
343 || keypress.equals(kbCtrlUp)
344 || keypress.equals(kbAltUp)
345 ) {
56661844 346 verticalDecrement();
0d47c546
KL
347 } else if (keypress.equals(kbShiftDown)
348 || keypress.equals(kbCtrlDown)
349 || keypress.equals(kbAltDown)
350 ) {
56661844 351 verticalIncrement();
0d47c546
KL
352 } else if (keypress.equals(kbShiftPgUp)
353 || keypress.equals(kbCtrlPgUp)
354 || keypress.equals(kbAltPgUp)
355 ) {
56661844 356 bigVerticalDecrement();
0d47c546
KL
357 } else if (keypress.equals(kbShiftPgDn)
358 || keypress.equals(kbCtrlPgDn)
359 || keypress.equals(kbAltPgDn)
360 ) {
56661844 361 bigVerticalIncrement();
7668cb45 362 } else if (keypress.equals(kbHome)) {
56661844 363 toTop();
7668cb45 364 } else if (keypress.equals(kbEnd)) {
56661844 365 toBottom();
7668cb45
KL
366 } else if (keypress.equals(kbEnter)) {
367 if (selectedItem != null) {
368 dispatch();
369 }
0d47c546
KL
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) {
56661844 377 verticalDecrement();
0d47c546
KL
378 }
379 }
380 }
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) {
56661844 388 verticalIncrement();
0d47c546
KL
389 }
390 }
391 }
a043164f
KL
392 } else if (keypress.equals(kbTab)) {
393 getParent().switchWidget(true);
394 return;
395 } else if (keypress.equals(kbShiftTab)
396 || keypress.equals(kbBackTab)) {
397 getParent().switchWidget(false);
398 return;
0d47c546
KL
399 } else if (selectedItem != null) {
400 // Give the TTreeItem a chance to handle arrow keys
401 selectedItem.onKeypress(keypress);
7668cb45 402 } else {
0d47c546 403 // Pass other keys (tab etc.) on to TWidget's handler.
7668cb45 404 super.onKeypress(keypress);
a043164f 405 return;
7668cb45
KL
406 }
407
408 // Update the screen after any thing has expanded/contracted
56661844 409 reflowData();
7668cb45
KL
410 }
411
412}