Merge commit '77d3a60869e7a780c6ae069e51530e1eacece5e2'
[fanfix.git] / src / jexer / ttree / TTreeViewWidget.java
CommitLineData
daa4106c 1/*
7668cb45
KL
2 * Jexer - Java Text User Interface
3 *
e16dda65 4 * The MIT License (MIT)
7668cb45 5 *
a69ed767 6 * Copyright (C) 2019 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 */
d36057df
KL
29package jexer.ttree;
30
31import jexer.TAction;
32import jexer.THScroller;
33import jexer.TKeypress;
34import jexer.TScrollableWidget;
35import jexer.TVScroller;
36import jexer.TWidget;
e820d5dd 37import jexer.bits.StringUtils;
7668cb45
KL
38import jexer.event.TKeypressEvent;
39import jexer.event.TMouseEvent;
8f62f06e 40import jexer.event.TResizeEvent;
7668cb45
KL
41import static jexer.TKeypress.*;
42
43/**
d36057df 44 * TTreeViewWidget wraps a tree view with horizontal and vertical scrollbars.
7668cb45 45 */
d36057df 46public class TTreeViewWidget extends TScrollableWidget {
7668cb45 47
d36057df
KL
48 // ------------------------------------------------------------------------
49 // Variables --------------------------------------------------------------
50 // ------------------------------------------------------------------------
7668cb45
KL
51
52 /**
d36057df 53 * The TTreeView
7668cb45 54 */
d36057df 55 private TTreeView treeView;
7668cb45
KL
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 */
329fd62e 61 private boolean centerWindow = false;
7668cb45
KL
62
63 /**
d36057df 64 * Maximum width of a single line.
7668cb45 65 */
d36057df 66 private int maxLineWidth;
329fd62e 67
d36057df
KL
68 // ------------------------------------------------------------------------
69 // Constructors -----------------------------------------------------------
70 // ------------------------------------------------------------------------
7668cb45
KL
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 */
d36057df 81 public TTreeViewWidget(final TWidget parent, final int x, final int y,
7668cb45
KL
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 */
d36057df 97 public TTreeViewWidget(final TWidget parent, final int x, final int y,
7668cb45
KL
98 final int width, final int height, final TAction action) {
99
100 super(parent, x, y, width, height);
d36057df
KL
101
102 treeView = new TTreeView(this, 0, 0, getWidth() - 1, getHeight() - 1,
103 action);
56661844
KL
104
105 vScroller = new TVScroller(this, getWidth() - 1, 0, getHeight() - 1);
106 hScroller = new THScroller(this, 0, getHeight() - 1, getWidth() - 1);
7668cb45 107
7668cb45
KL
108 }
109
d36057df
KL
110 // ------------------------------------------------------------------------
111 // Event handlers ---------------------------------------------------------
112 // ------------------------------------------------------------------------
7668cb45 113
8f62f06e
KL
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
7668cb45
KL
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()) {
56661844 140 verticalDecrement();
7668cb45 141 } else if (mouse.isMouseWheelDown()) {
56661844 142 verticalIncrement();
7668cb45 143 } else {
d36057df 144 // Pass to the TreeView or scrollbars
7668cb45
KL
145 super.onMouseDown(mouse);
146 }
147
d36057df
KL
148 // Update the view to reflect the new scrollbar positions
149 treeView.setTopLine(getVerticalValue());
150 treeView.setLeftColumn(getHorizontalValue());
56661844 151 reflowData();
7668cb45
KL
152 }
153
154 /**
155 * Handle mouse release events.
156 *
157 * @param mouse mouse button release event
158 */
159 @Override
329fd62e 160 public void onMouseUp(final TMouseEvent mouse) {
d36057df
KL
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);
7668cb45 179
d36057df
KL
180 // Update the view to reflect the new scrollbar positions
181 treeView.setTopLine(getVerticalValue());
182 treeView.setLeftColumn(getHorizontalValue());
56661844 183 reflowData();
7668cb45
KL
184 }
185
186 /**
187 * Handle keystrokes.
188 *
189 * @param keypress keystroke event
190 */
191 @Override
192 public void onKeypress(final TKeypressEvent keypress) {
0d47c546
KL
193 if (keypress.equals(kbShiftLeft)
194 || keypress.equals(kbCtrlLeft)
195 || keypress.equals(kbAltLeft)
196 ) {
56661844 197 horizontalDecrement();
0d47c546
KL
198 } else if (keypress.equals(kbShiftRight)
199 || keypress.equals(kbCtrlRight)
200 || keypress.equals(kbAltRight)
201 ) {
56661844 202 horizontalIncrement();
0d47c546
KL
203 } else if (keypress.equals(kbShiftUp)
204 || keypress.equals(kbCtrlUp)
205 || keypress.equals(kbAltUp)
206 ) {
56661844 207 verticalDecrement();
0d47c546
KL
208 } else if (keypress.equals(kbShiftDown)
209 || keypress.equals(kbCtrlDown)
210 || keypress.equals(kbAltDown)
211 ) {
56661844 212 verticalIncrement();
0d47c546
KL
213 } else if (keypress.equals(kbShiftPgUp)
214 || keypress.equals(kbCtrlPgUp)
215 || keypress.equals(kbAltPgUp)
216 ) {
56661844 217 bigVerticalDecrement();
0d47c546
KL
218 } else if (keypress.equals(kbShiftPgDn)
219 || keypress.equals(kbCtrlPgDn)
220 || keypress.equals(kbAltPgDn)
221 ) {
56661844 222 bigVerticalIncrement();
d36057df
KL
223 } else if (keypress.equals(kbPgDn)) {
224 for (int i = 0; i < getHeight() - 2; i++) {
225 treeView.onKeypress(new TKeypressEvent(TKeypress.kbDown));
7668cb45 226 }
d36057df
KL
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));
0d47c546 232 }
d36057df
KL
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;
a043164f
KL
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;
7668cb45 253 } else {
d36057df
KL
254 treeView.onKeypress(keypress);
255
256 // Update the scrollbars to reflect the new data position
257 reflowData();
a043164f 258 return;
7668cb45
KL
259 }
260
d36057df
KL
261 // Update the view to reflect the new scrollbar position
262 treeView.setTopLine(getVerticalValue());
263 treeView.setLeftColumn(getHorizontalValue());
56661844 264 reflowData();
7668cb45
KL
265 }
266
d36057df
KL
267 // ------------------------------------------------------------------------
268 // TScrollableWidget ------------------------------------------------------
269 // ------------------------------------------------------------------------
270
9240f032
KL
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
d36057df
KL
311 /**
312 * Resize text and scrollbars for a new width/height.
313 */
314 @Override
315 public void reflowData() {
9240f032
KL
316 if (treeView == null) {
317 return;
318 }
319
d36057df
KL
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
e820d5dd 346 int lineWidth = StringUtils.width(item.getText())
d36057df
KL
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
7668cb45 450}