Add 'src/jexer/' from commit 'cf01c92f5809a0732409e280fb0f32f27393618d'
[fanfix.git] / src / jexer / 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 * Resize text and scrollbars for a new width/height.
273 */
274 @Override
275 public void reflowData() {
276 int selectedRow = 0;
277 boolean foundSelectedRow = false;
278
279 // Reset the keyboard list, expandTree() will recreate it.
280 for (TWidget widget: treeView.getChildren()) {
281 TTreeItem item = (TTreeItem) widget;
282 item.keyboardPrevious = null;
283 item.keyboardNext = null;
284 }
285
286 // Expand the tree into a linear list
287 treeView.getChildren().clear();
288 treeView.getChildren().addAll(treeView.getTreeRoot().expandTree("",
289 true));
290
291 // Locate the selected row and maximum line width
292 for (TWidget widget: treeView.getChildren()) {
293 TTreeItem item = (TTreeItem) widget;
294
295 if (item == treeView.getSelected()) {
296 foundSelectedRow = true;
297 }
298 if (!foundSelectedRow) {
299 selectedRow++;
300 }
301
302 int lineWidth = StringUtils.width(item.getText())
303 + item.getPrefix().length() + 4;
304 if (lineWidth > maxLineWidth) {
305 maxLineWidth = lineWidth;
306 }
307 }
308
309 if ((centerWindow) && (foundSelectedRow)) {
310 if ((selectedRow < getVerticalValue())
311 || (selectedRow > getVerticalValue() + getHeight() - 2)
312 ) {
313 treeView.setTopLine(selectedRow);
314 centerWindow = false;
315 }
316 }
317 treeView.alignTree();
318
319 // Rescale the scroll bars
320 setVerticalValue(treeView.getTopLine());
321 setBottomValue(treeView.getTotalLineCount() - (getHeight() - 1));
322 if (getBottomValue() < getTopValue()) {
323 setBottomValue(getTopValue());
324 }
325 if (getVerticalValue() > getBottomValue()) {
326 setVerticalValue(getBottomValue());
327 }
328 setRightValue(maxLineWidth - 2);
329 if (getHorizontalValue() > getRightValue()) {
330 setHorizontalValue(getRightValue());
331 }
332
333 }
334
335 // ------------------------------------------------------------------------
336 // TTreeView --------------------------------------------------------------
337 // ------------------------------------------------------------------------
338
339 /**
340 * Get the underlying TTreeView.
341 *
342 * @return the TTreeView
343 */
344 public TTreeView getTreeView() {
345 return treeView;
346 }
347
348 /**
349 * Get the root of the tree.
350 *
351 * @return the root of the tree
352 */
353 public final TTreeItem getTreeRoot() {
354 return treeView.getTreeRoot();
355 }
356
357 /**
358 * Set the root of the tree.
359 *
360 * @param treeRoot the new root of the tree
361 */
362 public final void setTreeRoot(final TTreeItem treeRoot) {
363 treeView.setTreeRoot(treeRoot);
364 }
365
366 /**
367 * Set treeRoot.
368 *
369 * @param treeRoot ultimate root of tree
370 * @param centerWindow if true, move the window to put the root in view
371 */
372 public void setTreeRoot(final TTreeItem treeRoot,
373 final boolean centerWindow) {
374
375 treeView.setTreeRoot(treeRoot);
376 this.centerWindow = centerWindow;
377 }
378
379 /**
380 * Get the tree view item that was selected.
381 *
382 * @return the selected item, or null if no item is selected
383 */
384 public final TTreeItem getSelected() {
385 return treeView.getSelected();
386 }
387
388 /**
389 * Set the new selected tree view item.
390 *
391 * @param item new item that became selected
392 * @param centerWindow if true, move the window to put the selected into
393 * view
394 */
395 public void setSelected(final TTreeItem item, final boolean centerWindow) {
396 treeView.setSelected(item, centerWindow);
397 }
398
399 /**
400 * Perform user selection action.
401 */
402 public void dispatch() {
403 treeView.dispatch();
404 }
405
406 }