make Windows look like X11, why does this work
[fanfix.git] / src / jexer / TTreeView.java
CommitLineData
7668cb45
KL
1/**
2 * Jexer - Java Text User Interface
3 *
4 * License: LGPLv3 or later
5 *
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.
9 *
10 * Copyright (C) 2015 Kevin Lamonte
11 *
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.
16 *
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.
21 *
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
26 * 02110-1301 USA
27 *
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 * @version 1
30 */
31package jexer;
32
7668cb45
KL
33import jexer.event.TKeypressEvent;
34import jexer.event.TMouseEvent;
35import static jexer.TKeypress.*;
36
37/**
38 * TTreeView implements a simple tree view.
39 */
40public class TTreeView extends TWidget {
41
42 /**
43 * Vertical scrollbar.
44 */
45 private TVScroller vScroller;
46
47 /**
0d47c546 48 * Horizontal scrollbar.
7668cb45 49 */
0d47c546
KL
50 private THScroller hScroller;
51
52 /**
53 * Get the horizontal scrollbar. This is used by TTreeItem.draw(), and
54 * potentially subclasses.
55 */
56 public final THScroller getHScroller() {
57 return hScroller;
58 }
7668cb45
KL
59
60 /**
61 * Root of the tree.
62 */
63 private TTreeItem treeRoot;
64
65 /**
66 * Get the root of the tree.
67 *
68 * @return the root of the tree
69 */
70 public final TTreeItem getTreeRoot() {
71 return treeRoot;
72 }
73
74 /**
75 * Set the root of the tree.
76 *
77 * @param treeRoot the new root of the tree
78 */
79 public final void setTreeRoot(final TTreeItem treeRoot) {
80 this.treeRoot = treeRoot;
81 }
82
83 /**
84 * Maximum width of a single line.
85 */
86 private int maxLineWidth;
87
88 /**
89 * Only one of my children can be selected.
90 */
91 private TTreeItem selectedItem = null;
92
93 /**
94 * If true, move the window to put the selected item in view. This
95 * normally only happens once after setting treeRoot.
96 */
97 public boolean centerWindow = false;
98
99 /**
100 * The action to perform when the user selects an item.
101 */
102 private TAction action = null;
103
104 /**
105 * Set treeRoot.
106 *
107 * @param treeRoot ultimate root of tree
108 * @param centerWindow if true, move the window to put the root in view
109 */
110 public void setTreeRoot(final TTreeItem treeRoot, final boolean centerWindow) {
111 this.treeRoot = treeRoot;
112 this.centerWindow = centerWindow;
113 }
114
115 /**
116 * Public constructor.
117 *
118 * @param parent parent widget
119 * @param x column relative to parent
120 * @param y row relative to parent
121 * @param width width of tree view
122 * @param height height of tree view
123 */
124 public TTreeView(final TWidget parent, final int x, final int y,
125 final int width, final int height) {
126
127 this(parent, x, y, width, height, null);
128 }
129
130 /**
131 * Public constructor.
132 *
133 * @param parent parent widget
134 * @param x column relative to parent
135 * @param y row relative to parent
136 * @param width width of tree view
137 * @param height height of tree view
138 * @param action action to perform when an item is selected
139 */
140 public TTreeView(final TWidget parent, final int x, final int y,
141 final int width, final int height, final TAction action) {
142
143 super(parent, x, y, width, height);
144 this.action = action;
145 }
146
147 /**
148 * Get the tree view item that was selected.
149 *
150 * @return the selected item, or null if no item is selected
151 */
152 public final TTreeItem getSelected() {
153 return selectedItem;
154 }
155
156 /**
0d47c546 157 * Set the new selected tree view item.
7668cb45
KL
158 *
159 * @param item new item that became selected
160 */
0d47c546 161 public void setSelected(final TTreeItem item) {
7668cb45
KL
162 if (item != null) {
163 item.setSelected(true);
164 }
165 if ((selectedItem != null) && (selectedItem != item)) {
166 selectedItem.setSelected(false);
167 }
168 selectedItem = item;
169 }
170
171 /**
0d47c546 172 * Perform user selection action.
7668cb45 173 */
0d47c546 174 public void dispatch() {
7668cb45
KL
175 if (action != null) {
176 action.DO();
177 }
178 }
179
180 /**
181 * Update (or instantiate) vScroller and hScroller.
182 */
183 private void updateScrollers() {
184 // Setup vertical scroller
185 if (vScroller == null) {
186 vScroller = new TVScroller(this, getWidth() - 1, 0,
187 getHeight() - 1);
188 vScroller.setValue(0);
189 vScroller.setTopValue(0);
190 }
191 vScroller.setX(getWidth() - 1);
192 vScroller.setHeight(getHeight() - 1);
193 vScroller.setBigChange(getHeight() - 1);
194
195 // Setup horizontal scroller
196 if (hScroller == null) {
197 hScroller = new THScroller(this, 0, getHeight() - 1,
198 getWidth() - 1);
199 hScroller.setValue(0);
200 hScroller.setLeftValue(0);
201 }
202 hScroller.setY(getHeight() - 1);
203 hScroller.setWidth(getWidth() - 1);
204 hScroller.setBigChange(getWidth() - 1);
205 }
206
207 /**
208 * Resize text and scrollbars for a new width/height.
209 */
210 public void reflow() {
211 int selectedRow = 0;
212 boolean foundSelectedRow = false;
213
214 updateScrollers();
215 if (treeRoot == null) {
216 return;
217 }
218
219 // Make each child invisible/inactive to start, expandTree() will
220 // reactivate the visible ones.
221 for (TWidget widget: getChildren()) {
222 if (widget instanceof TTreeItem) {
223 TTreeItem item = (TTreeItem) widget;
224 item.setInvisible(true);
225 item.setEnabled(false);
0d47c546
KL
226 item.keyboardPrevious = null;
227 item.keyboardNext = null;
7668cb45
KL
228 }
229 }
230
231 // Expand the tree into a linear list
232 getChildren().clear();
233 getChildren().addAll(treeRoot.expandTree("", true));
0d47c546
KL
234
235 // Locate the selected row and maximum line width
7668cb45
KL
236 for (TWidget widget: getChildren()) {
237 TTreeItem item = (TTreeItem) widget;
238
239 if (item == selectedItem) {
240 foundSelectedRow = true;
241 }
242 if (foundSelectedRow == false) {
243 selectedRow++;
244 }
245
246 int lineWidth = item.getText().length()
247 + item.getPrefix().length() + 4;
248 if (lineWidth > maxLineWidth) {
249 maxLineWidth = lineWidth;
250 }
251 }
0d47c546 252
7668cb45
KL
253 if ((centerWindow) && (foundSelectedRow)) {
254 if ((selectedRow < vScroller.getValue())
255 || (selectedRow > vScroller.getValue() + getHeight() - 2)
256 ) {
257 vScroller.setValue(selectedRow);
258 centerWindow = false;
259 }
260 }
261 updatePositions();
262
263 // Rescale the scroll bars
264 vScroller.setBottomValue(getChildren().size() - getHeight() + 1);
265 if (vScroller.getBottomValue() < 0) {
266 vScroller.setBottomValue(0);
267 }
268 /*
269 if (vScroller.getValue() > vScroller.getBottomValue()) {
270 vScroller.setValue(vScroller.getBottomValue());
271 }
272 */
273 hScroller.setRightValue(maxLineWidth - getWidth() + 3);
274 if (hScroller.getRightValue() < 0) {
275 hScroller.setRightValue(0);
276 }
277 /*
278 if (hScroller.getValue() > hScroller.getRightValue()) {
279 hScroller.setValue(hScroller.getRightValue());
280 }
281 */
282 getChildren().add(hScroller);
283 getChildren().add(vScroller);
284 }
285
286 /**
287 * Update the Y positions of all the children items.
288 */
289 private void updatePositions() {
290 if (treeRoot == null) {
291 return;
292 }
293
294 int begin = vScroller.getValue();
295 int topY = 0;
0d47c546
KL
296
297 // As we walk the list we also adjust next/previous pointers,
298 // resulting in a doubly-linked list but only of the expanded items.
299 TTreeItem p = null;
300
7668cb45
KL
301 for (int i = 0; i < getChildren().size(); i++) {
302 if (!(getChildren().get(i) instanceof TTreeItem)) {
0d47c546 303 // Skip the scrollbars
7668cb45
KL
304 continue;
305 }
306 TTreeItem item = (TTreeItem) getChildren().get(i);
307
0d47c546
KL
308 if (p != null) {
309 item.keyboardPrevious = p;
310 p.keyboardNext = item;
311 }
312 p = item;
313
7668cb45
KL
314 if (i < begin) {
315 // Render invisible
316 item.setEnabled(false);
317 item.setInvisible(true);
318 continue;
319 }
320
321 if (topY >= getHeight() - 1) {
322 // Render invisible
323 item.setEnabled(false);
324 item.setInvisible(true);
325 continue;
326 }
327
328 item.setY(topY);
329 item.setEnabled(true);
330 item.setInvisible(false);
331 item.setWidth(getWidth() - 1);
332 topY++;
333 }
0d47c546 334
7668cb45
KL
335 }
336
337 /**
338 * Handle mouse press events.
339 *
340 * @param mouse mouse button press event
341 */
342 @Override
343 public void onMouseDown(final TMouseEvent mouse) {
344 if (mouse.isMouseWheelUp()) {
345 vScroller.decrement();
346 } else if (mouse.isMouseWheelDown()) {
347 vScroller.increment();
348 } else {
349 // Pass to children
350 super.onMouseDown(mouse);
351 }
352
353 // Update the screen after the scrollbars have moved
354 reflow();
355 }
356
357 /**
358 * Handle mouse release events.
359 *
360 * @param mouse mouse button release event
361 */
362 @Override
363 public void onMouseUp(TMouseEvent mouse) {
364 // Pass to children
365 super.onMouseDown(mouse);
366
367 // Update the screen after any thing has expanded/contracted
368 reflow();
369 }
370
371 /**
372 * Handle keystrokes.
373 *
374 * @param keypress keystroke event
375 */
376 @Override
377 public void onKeypress(final TKeypressEvent keypress) {
0d47c546
KL
378 if (keypress.equals(kbShiftLeft)
379 || keypress.equals(kbCtrlLeft)
380 || keypress.equals(kbAltLeft)
381 ) {
7668cb45 382 hScroller.decrement();
0d47c546
KL
383 } else if (keypress.equals(kbShiftRight)
384 || keypress.equals(kbCtrlRight)
385 || keypress.equals(kbAltRight)
386 ) {
7668cb45 387 hScroller.increment();
0d47c546
KL
388 } else if (keypress.equals(kbShiftUp)
389 || keypress.equals(kbCtrlUp)
390 || keypress.equals(kbAltUp)
391 ) {
7668cb45 392 vScroller.decrement();
0d47c546
KL
393 } else if (keypress.equals(kbShiftDown)
394 || keypress.equals(kbCtrlDown)
395 || keypress.equals(kbAltDown)
396 ) {
7668cb45 397 vScroller.increment();
0d47c546
KL
398 } else if (keypress.equals(kbShiftPgUp)
399 || keypress.equals(kbCtrlPgUp)
400 || keypress.equals(kbAltPgUp)
401 ) {
7668cb45 402 vScroller.bigDecrement();
0d47c546
KL
403 } else if (keypress.equals(kbShiftPgDn)
404 || keypress.equals(kbCtrlPgDn)
405 || keypress.equals(kbAltPgDn)
406 ) {
7668cb45
KL
407 vScroller.bigIncrement();
408 } else if (keypress.equals(kbHome)) {
409 vScroller.toTop();
410 } else if (keypress.equals(kbEnd)) {
411 vScroller.toBottom();
412 } else if (keypress.equals(kbEnter)) {
413 if (selectedItem != null) {
414 dispatch();
415 }
0d47c546
KL
416 } else if (keypress.equals(kbUp)) {
417 // Select the previous item
418 if (selectedItem != null) {
419 TTreeItem oldItem = selectedItem;
420 if (selectedItem.keyboardPrevious != null) {
421 setSelected(selectedItem.keyboardPrevious);
422 if (oldItem.getY() == 0) {
423 vScroller.decrement();
424 }
425 }
426 }
427 } else if (keypress.equals(kbDown)) {
428 // Select the next item
429 if (selectedItem != null) {
430 TTreeItem oldItem = selectedItem;
431 if (selectedItem.keyboardNext != null) {
432 setSelected(selectedItem.keyboardNext);
433 if (oldItem.getY() == getHeight() - 2) {
434 vScroller.increment();
435 }
436 }
437 }
a043164f
KL
438 } else if (keypress.equals(kbTab)) {
439 getParent().switchWidget(true);
440 return;
441 } else if (keypress.equals(kbShiftTab)
442 || keypress.equals(kbBackTab)) {
443 getParent().switchWidget(false);
444 return;
0d47c546
KL
445 } else if (selectedItem != null) {
446 // Give the TTreeItem a chance to handle arrow keys
447 selectedItem.onKeypress(keypress);
7668cb45 448 } else {
0d47c546 449 // Pass other keys (tab etc.) on to TWidget's handler.
7668cb45 450 super.onKeypress(keypress);
a043164f 451 return;
7668cb45
KL
452 }
453
454 // Update the screen after any thing has expanded/contracted
455 reflow();
456 }
457
458}