Merge branch 'master' of https://github.com/klamonte/jexer
[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
33import jexer.bits.CellAttributes;
34import jexer.bits.GraphicsChars;
35import jexer.event.TKeypressEvent;
36import jexer.event.TMouseEvent;
37import static jexer.TKeypress.*;
38
39/**
40 * TTreeView implements a simple tree view.
41 */
42public class TTreeView extends TWidget {
43
44 /**
45 * Vertical scrollbar.
46 */
47 private TVScroller vScroller;
48
49 /**
50 * Horizontal scrollbar. Note package private access.
51 */
52 THScroller hScroller;
53
54 /**
55 * Root of the tree.
56 */
57 private TTreeItem treeRoot;
58
59 /**
60 * Get the root of the tree.
61 *
62 * @return the root of the tree
63 */
64 public final TTreeItem getTreeRoot() {
65 return treeRoot;
66 }
67
68 /**
69 * Set the root of the tree.
70 *
71 * @param treeRoot the new root of the tree
72 */
73 public final void setTreeRoot(final TTreeItem treeRoot) {
74 this.treeRoot = treeRoot;
75 }
76
77 /**
78 * Maximum width of a single line.
79 */
80 private int maxLineWidth;
81
82 /**
83 * Only one of my children can be selected.
84 */
85 private TTreeItem selectedItem = null;
86
87 /**
88 * If true, move the window to put the selected item in view. This
89 * normally only happens once after setting treeRoot.
90 */
91 public boolean centerWindow = false;
92
93 /**
94 * The action to perform when the user selects an item.
95 */
96 private TAction action = null;
97
98 /**
99 * Set treeRoot.
100 *
101 * @param treeRoot ultimate root of tree
102 * @param centerWindow if true, move the window to put the root in view
103 */
104 public void setTreeRoot(final TTreeItem treeRoot, final boolean centerWindow) {
105 this.treeRoot = treeRoot;
106 this.centerWindow = centerWindow;
107 }
108
109 /**
110 * Public constructor.
111 *
112 * @param parent parent widget
113 * @param x column relative to parent
114 * @param y row relative to parent
115 * @param width width of tree view
116 * @param height height of tree view
117 */
118 public TTreeView(final TWidget parent, final int x, final int y,
119 final int width, final int height) {
120
121 this(parent, x, y, width, height, null);
122 }
123
124 /**
125 * Public constructor.
126 *
127 * @param parent parent widget
128 * @param x column relative to parent
129 * @param y row relative to parent
130 * @param width width of tree view
131 * @param height height of tree view
132 * @param action action to perform when an item is selected
133 */
134 public TTreeView(final TWidget parent, final int x, final int y,
135 final int width, final int height, final TAction action) {
136
137 super(parent, x, y, width, height);
138 this.action = action;
139 }
140
141 /**
142 * Get the tree view item that was selected.
143 *
144 * @return the selected item, or null if no item is selected
145 */
146 public final TTreeItem getSelected() {
147 return selectedItem;
148 }
149
150 /**
151 * Set the new selected tree view item. Note package private access.
152 *
153 * @param item new item that became selected
154 */
155 void setSelected(final TTreeItem item) {
156 if (item != null) {
157 item.setSelected(true);
158 }
159 if ((selectedItem != null) && (selectedItem != item)) {
160 selectedItem.setSelected(false);
161 }
162 selectedItem = item;
163 }
164
165 /**
166 * Perform user selection action. Note package private access.
167 */
168 void dispatch() {
169 if (action != null) {
170 action.DO();
171 }
172 }
173
174 /**
175 * Update (or instantiate) vScroller and hScroller.
176 */
177 private void updateScrollers() {
178 // Setup vertical scroller
179 if (vScroller == null) {
180 vScroller = new TVScroller(this, getWidth() - 1, 0,
181 getHeight() - 1);
182 vScroller.setValue(0);
183 vScroller.setTopValue(0);
184 }
185 vScroller.setX(getWidth() - 1);
186 vScroller.setHeight(getHeight() - 1);
187 vScroller.setBigChange(getHeight() - 1);
188
189 // Setup horizontal scroller
190 if (hScroller == null) {
191 hScroller = new THScroller(this, 0, getHeight() - 1,
192 getWidth() - 1);
193 hScroller.setValue(0);
194 hScroller.setLeftValue(0);
195 }
196 hScroller.setY(getHeight() - 1);
197 hScroller.setWidth(getWidth() - 1);
198 hScroller.setBigChange(getWidth() - 1);
199 }
200
201 /**
202 * Resize text and scrollbars for a new width/height.
203 */
204 public void reflow() {
205 int selectedRow = 0;
206 boolean foundSelectedRow = false;
207
208 updateScrollers();
209 if (treeRoot == null) {
210 return;
211 }
212
213 // Make each child invisible/inactive to start, expandTree() will
214 // reactivate the visible ones.
215 for (TWidget widget: getChildren()) {
216 if (widget instanceof TTreeItem) {
217 TTreeItem item = (TTreeItem) widget;
218 item.setInvisible(true);
219 item.setEnabled(false);
220 }
221 }
222
223 // Expand the tree into a linear list
224 getChildren().clear();
225 getChildren().addAll(treeRoot.expandTree("", true));
226 for (TWidget widget: getChildren()) {
227 TTreeItem item = (TTreeItem) widget;
228
229 if (item == selectedItem) {
230 foundSelectedRow = true;
231 }
232 if (foundSelectedRow == false) {
233 selectedRow++;
234 }
235
236 int lineWidth = item.getText().length()
237 + item.getPrefix().length() + 4;
238 if (lineWidth > maxLineWidth) {
239 maxLineWidth = lineWidth;
240 }
241 }
242 if ((centerWindow) && (foundSelectedRow)) {
243 if ((selectedRow < vScroller.getValue())
244 || (selectedRow > vScroller.getValue() + getHeight() - 2)
245 ) {
246 vScroller.setValue(selectedRow);
247 centerWindow = false;
248 }
249 }
250 updatePositions();
251
252 // Rescale the scroll bars
253 vScroller.setBottomValue(getChildren().size() - getHeight() + 1);
254 if (vScroller.getBottomValue() < 0) {
255 vScroller.setBottomValue(0);
256 }
257 /*
258 if (vScroller.getValue() > vScroller.getBottomValue()) {
259 vScroller.setValue(vScroller.getBottomValue());
260 }
261 */
262 hScroller.setRightValue(maxLineWidth - getWidth() + 3);
263 if (hScroller.getRightValue() < 0) {
264 hScroller.setRightValue(0);
265 }
266 /*
267 if (hScroller.getValue() > hScroller.getRightValue()) {
268 hScroller.setValue(hScroller.getRightValue());
269 }
270 */
271 getChildren().add(hScroller);
272 getChildren().add(vScroller);
273 }
274
275 /**
276 * Update the Y positions of all the children items.
277 */
278 private void updatePositions() {
279 if (treeRoot == null) {
280 return;
281 }
282
283 int begin = vScroller.getValue();
284 int topY = 0;
285 for (int i = 0; i < getChildren().size(); i++) {
286 if (!(getChildren().get(i) instanceof TTreeItem)) {
287 // Skip
288 continue;
289 }
290 TTreeItem item = (TTreeItem) getChildren().get(i);
291
292 if (i < begin) {
293 // Render invisible
294 item.setEnabled(false);
295 item.setInvisible(true);
296 continue;
297 }
298
299 if (topY >= getHeight() - 1) {
300 // Render invisible
301 item.setEnabled(false);
302 item.setInvisible(true);
303 continue;
304 }
305
306 item.setY(topY);
307 item.setEnabled(true);
308 item.setInvisible(false);
309 item.setWidth(getWidth() - 1);
310 topY++;
311 }
312 }
313
314 /**
315 * Handle mouse press events.
316 *
317 * @param mouse mouse button press event
318 */
319 @Override
320 public void onMouseDown(final TMouseEvent mouse) {
321 if (mouse.isMouseWheelUp()) {
322 vScroller.decrement();
323 } else if (mouse.isMouseWheelDown()) {
324 vScroller.increment();
325 } else {
326 // Pass to children
327 super.onMouseDown(mouse);
328 }
329
330 // Update the screen after the scrollbars have moved
331 reflow();
332 }
333
334 /**
335 * Handle mouse release events.
336 *
337 * @param mouse mouse button release event
338 */
339 @Override
340 public void onMouseUp(TMouseEvent mouse) {
341 // Pass to children
342 super.onMouseDown(mouse);
343
344 // Update the screen after any thing has expanded/contracted
345 reflow();
346 }
347
348 /**
349 * Handle keystrokes.
350 *
351 * @param keypress keystroke event
352 */
353 @Override
354 public void onKeypress(final TKeypressEvent keypress) {
355 if (keypress.equals(kbLeft)) {
356 hScroller.decrement();
357 } else if (keypress.equals(kbRight)) {
358 hScroller.increment();
359 } else if (keypress.equals(kbUp)) {
360 vScroller.decrement();
361 } else if (keypress.equals(kbDown)) {
362 vScroller.increment();
363 } else if (keypress.equals(kbPgUp)) {
364 vScroller.bigDecrement();
365 } else if (keypress.equals(kbPgDn)) {
366 vScroller.bigIncrement();
367 } else if (keypress.equals(kbHome)) {
368 vScroller.toTop();
369 } else if (keypress.equals(kbEnd)) {
370 vScroller.toBottom();
371 } else if (keypress.equals(kbEnter)) {
372 if (selectedItem != null) {
373 dispatch();
374 }
375 } else {
376 // Pass other keys (tab etc.) on
377 super.onKeypress(keypress);
378 }
379
380 // Update the screen after any thing has expanded/contracted
381 reflow();
382 }
383
384}