Merge branch 'upstream' into subtree
[fanfix.git] / ttree / TTreeViewWidget.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 Kevin Lamonte
7 *
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:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
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.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29 package jexer.ttree;
30
31 import jexer.TAction;
32 import jexer.THScroller;
33 import jexer.TKeypress;
34 import jexer.TScrollableWidget;
35 import jexer.TVScroller;
36 import jexer.TWidget;
37 import jexer.bits.StringUtils;
38 import jexer.event.TKeypressEvent;
39 import jexer.event.TMouseEvent;
40 import jexer.event.TResizeEvent;
41 import static jexer.TKeypress.*;
42
43 /**
44 * TTreeViewWidget wraps a tree view with horizontal and vertical scrollbars.
45 */
46 public class TTreeViewWidget extends TScrollableWidget {
47
48 // ------------------------------------------------------------------------
49 // Variables --------------------------------------------------------------
50 // ------------------------------------------------------------------------
51
52 /**
53 * The TTreeView
54 */
55 private TTreeView treeView;
56
57 /**
58 * If true, move the window to put the selected item in view. This
59 * normally only happens once after setting treeRoot.
60 */
61 private boolean centerWindow = false;
62
63 /**
64 * Maximum width of a single line.
65 */
66 private int maxLineWidth;
67
68 // ------------------------------------------------------------------------
69 // Constructors -----------------------------------------------------------
70 // ------------------------------------------------------------------------
71
72 /**
73 * Public constructor.
74 *
75 * @param parent parent widget
76 * @param x column relative to parent
77 * @param y row relative to parent
78 * @param width width of tree view
79 * @param height height of tree view
80 */
81 public TTreeViewWidget(final TWidget parent, final int x, final int y,
82 final int width, final int height) {
83
84 this(parent, x, y, width, height, null);
85 }
86
87 /**
88 * Public constructor.
89 *
90 * @param parent parent widget
91 * @param x column relative to parent
92 * @param y row relative to parent
93 * @param width width of tree view
94 * @param height height of tree view
95 * @param action action to perform when an item is selected
96 */
97 public TTreeViewWidget(final TWidget parent, final int x, final int y,
98 final int width, final int height, final TAction action) {
99
100 super(parent, x, y, width, height);
101
102 treeView = new TTreeView(this, 0, 0, getWidth() - 1, getHeight() - 1,
103 action);
104
105 vScroller = new TVScroller(this, getWidth() - 1, 0, getHeight() - 1);
106 hScroller = new THScroller(this, 0, getHeight() - 1, getWidth() - 1);
107
108 }
109
110 // ------------------------------------------------------------------------
111 // Event handlers ---------------------------------------------------------
112 // ------------------------------------------------------------------------
113
114 /**
115 * Handle window/screen resize events.
116 *
117 * @param event resize event
118 */
119 @Override
120 public void onResize(final TResizeEvent event) {
121 super.onResize(event);
122
123 if (event.getType() == TResizeEvent.Type.WIDGET) {
124 treeView.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
125 getWidth() - 1, getHeight() - 1));
126 return;
127 } else {
128 super.onResize(event);
129 }
130 }
131
132 /**
133 * Handle mouse press events.
134 *
135 * @param mouse mouse button press event
136 */
137 @Override
138 public void onMouseDown(final TMouseEvent mouse) {
139 if (mouse.isMouseWheelUp()) {
140 verticalDecrement();
141 } else if (mouse.isMouseWheelDown()) {
142 verticalIncrement();
143 } else {
144 // Pass to the TreeView or scrollbars
145 super.onMouseDown(mouse);
146 }
147
148 // Update the view to reflect the new scrollbar positions
149 treeView.setTopLine(getVerticalValue());
150 treeView.setLeftColumn(getHorizontalValue());
151 reflowData();
152 }
153
154 /**
155 * Handle mouse release events.
156 *
157 * @param mouse mouse button release event
158 */
159 @Override
160 public void onMouseUp(final TMouseEvent mouse) {
161 // Pass to the TreeView or scrollbars
162 super.onMouseUp(mouse);
163
164 // Update the view to reflect the new scrollbar positions
165 treeView.setTopLine(getVerticalValue());
166 treeView.setLeftColumn(getHorizontalValue());
167 reflowData();
168 }
169
170 /**
171 * Handle mouse motion events.
172 *
173 * @param mouse mouse motion event
174 */
175 @Override
176 public void onMouseMotion(final TMouseEvent mouse) {
177 // Pass to the TreeView or scrollbars
178 super.onMouseMotion(mouse);
179
180 // Update the view to reflect the new scrollbar positions
181 treeView.setTopLine(getVerticalValue());
182 treeView.setLeftColumn(getHorizontalValue());
183 reflowData();
184 }
185
186 /**
187 * Handle keystrokes.
188 *
189 * @param keypress keystroke event
190 */
191 @Override
192 public void onKeypress(final TKeypressEvent keypress) {
193 if (keypress.equals(kbShiftLeft)
194 || keypress.equals(kbCtrlLeft)
195 || keypress.equals(kbAltLeft)
196 ) {
197 horizontalDecrement();
198 } else if (keypress.equals(kbShiftRight)
199 || keypress.equals(kbCtrlRight)
200 || keypress.equals(kbAltRight)
201 ) {
202 horizontalIncrement();
203 } else if (keypress.equals(kbShiftUp)
204 || keypress.equals(kbCtrlUp)
205 || keypress.equals(kbAltUp)
206 ) {
207 verticalDecrement();
208 } else if (keypress.equals(kbShiftDown)
209 || keypress.equals(kbCtrlDown)
210 || keypress.equals(kbAltDown)
211 ) {
212 verticalIncrement();
213 } else if (keypress.equals(kbShiftPgUp)
214 || keypress.equals(kbCtrlPgUp)
215 || keypress.equals(kbAltPgUp)
216 ) {
217 bigVerticalDecrement();
218 } else if (keypress.equals(kbShiftPgDn)
219 || keypress.equals(kbCtrlPgDn)
220 || keypress.equals(kbAltPgDn)
221 ) {
222 bigVerticalIncrement();
223 } else if (keypress.equals(kbPgDn)) {
224 for (int i = 0; i < getHeight() - 2; i++) {
225 treeView.onKeypress(new TKeypressEvent(TKeypress.kbDown));
226 }
227 reflowData();
228 return;
229 } else if (keypress.equals(kbPgUp)) {
230 for (int i = 0; i < getHeight() - 2; i++) {
231 treeView.onKeypress(new TKeypressEvent(TKeypress.kbUp));
232 }
233 reflowData();
234 return;
235 } else if (keypress.equals(kbHome)) {
236 treeView.setSelected((TTreeItem) treeView.getChildren().get(0),
237 false);
238 treeView.setTopLine(0);
239 reflowData();
240 return;
241 } else if (keypress.equals(kbEnd)) {
242 treeView.setSelected((TTreeItem) treeView.getChildren().get(
243 treeView.getChildren().size() - 1), true);
244 reflowData();
245 return;
246 } else if (keypress.equals(kbTab)) {
247 getParent().switchWidget(true);
248 return;
249 } else if (keypress.equals(kbShiftTab)
250 || keypress.equals(kbBackTab)) {
251 getParent().switchWidget(false);
252 return;
253 } else {
254 treeView.onKeypress(keypress);
255
256 // Update the scrollbars to reflect the new data position
257 reflowData();
258 return;
259 }
260
261 // Update the view to reflect the new scrollbar position
262 treeView.setTopLine(getVerticalValue());
263 treeView.setLeftColumn(getHorizontalValue());
264 reflowData();
265 }
266
267 // ------------------------------------------------------------------------
268 // TScrollableWidget ------------------------------------------------------
269 // ------------------------------------------------------------------------
270
271 /**
272 * Override TWidget's width: we need to set child widget widths.
273 *
274 * @param width new widget width
275 */
276 @Override
277 public void setWidth(final int width) {
278 super.setWidth(width);
279 if (hScroller != null) {
280 hScroller.setWidth(getWidth() - 1);
281 }
282 if (vScroller != null) {
283 vScroller.setX(getWidth() - 1);
284 }
285 if (treeView != null) {
286 treeView.setWidth(getWidth() - 1);
287 }
288 reflowData();
289 }
290
291 /**
292 * Override TWidget's height: we need to set child widget heights.
293 *
294 * @param height new widget height
295 */
296 @Override
297 public void setHeight(final int height) {
298 super.setHeight(height);
299 if (hScroller != null) {
300 hScroller.setY(getHeight() - 1);
301 }
302 if (vScroller != null) {
303 vScroller.setHeight(getHeight() - 1);
304 }
305 if (treeView != null) {
306 treeView.setHeight(getHeight() - 1);
307 }
308 reflowData();
309 }
310
311 /**
312 * Resize text and scrollbars for a new width/height.
313 */
314 @Override
315 public void reflowData() {
316 if (treeView == null) {
317 return;
318 }
319
320 int selectedRow = 0;
321 boolean foundSelectedRow = false;
322
323 // Reset the keyboard list, expandTree() will recreate it.
324 for (TWidget widget: treeView.getChildren()) {
325 TTreeItem item = (TTreeItem) widget;
326 item.keyboardPrevious = null;
327 item.keyboardNext = null;
328 }
329
330 // Expand the tree into a linear list
331 treeView.getChildren().clear();
332 treeView.getChildren().addAll(treeView.getTreeRoot().expandTree("",
333 true));
334
335 // Locate the selected row and maximum line width
336 for (TWidget widget: treeView.getChildren()) {
337 TTreeItem item = (TTreeItem) widget;
338
339 if (item == treeView.getSelected()) {
340 foundSelectedRow = true;
341 }
342 if (!foundSelectedRow) {
343 selectedRow++;
344 }
345
346 int lineWidth = StringUtils.width(item.getText())
347 + item.getPrefix().length() + 4;
348 if (lineWidth > maxLineWidth) {
349 maxLineWidth = lineWidth;
350 }
351 }
352
353 if ((centerWindow) && (foundSelectedRow)) {
354 if ((selectedRow < getVerticalValue())
355 || (selectedRow > getVerticalValue() + getHeight() - 2)
356 ) {
357 treeView.setTopLine(selectedRow);
358 centerWindow = false;
359 }
360 }
361 treeView.alignTree();
362
363 // Rescale the scroll bars
364 setVerticalValue(treeView.getTopLine());
365 setBottomValue(treeView.getTotalLineCount() - (getHeight() - 1));
366 if (getBottomValue() < getTopValue()) {
367 setBottomValue(getTopValue());
368 }
369 if (getVerticalValue() > getBottomValue()) {
370 setVerticalValue(getBottomValue());
371 }
372 setRightValue(maxLineWidth - 2);
373 if (getHorizontalValue() > getRightValue()) {
374 setHorizontalValue(getRightValue());
375 }
376
377 }
378
379 // ------------------------------------------------------------------------
380 // TTreeView --------------------------------------------------------------
381 // ------------------------------------------------------------------------
382
383 /**
384 * Get the underlying TTreeView.
385 *
386 * @return the TTreeView
387 */
388 public TTreeView getTreeView() {
389 return treeView;
390 }
391
392 /**
393 * Get the root of the tree.
394 *
395 * @return the root of the tree
396 */
397 public final TTreeItem getTreeRoot() {
398 return treeView.getTreeRoot();
399 }
400
401 /**
402 * Set the root of the tree.
403 *
404 * @param treeRoot the new root of the tree
405 */
406 public final void setTreeRoot(final TTreeItem treeRoot) {
407 treeView.setTreeRoot(treeRoot);
408 }
409
410 /**
411 * Set treeRoot.
412 *
413 * @param treeRoot ultimate root of tree
414 * @param centerWindow if true, move the window to put the root in view
415 */
416 public void setTreeRoot(final TTreeItem treeRoot,
417 final boolean centerWindow) {
418
419 treeView.setTreeRoot(treeRoot);
420 this.centerWindow = centerWindow;
421 }
422
423 /**
424 * Get the tree view item that was selected.
425 *
426 * @return the selected item, or null if no item is selected
427 */
428 public final TTreeItem getSelected() {
429 return treeView.getSelected();
430 }
431
432 /**
433 * Set the new selected tree view item.
434 *
435 * @param item new item that became selected
436 * @param centerWindow if true, move the window to put the selected into
437 * view
438 */
439 public void setSelected(final TTreeItem item, final boolean centerWindow) {
440 treeView.setSelected(item, centerWindow);
441 }
442
443 /**
444 * Perform user selection action.
445 */
446 public void dispatch() {
447 treeView.dispatch();
448 }
449
450 }