Add 'src/jexer/' from commit 'cf01c92f5809a0732409e280fb0f32f27393618d'
[fanfix.git] / src / jexer / ttree / TTreeViewWindow.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.TApplication;
33 import jexer.THScroller;
34 import jexer.TScrollableWindow;
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 * TTreeViewWindow wraps a tree view with horizontal and vertical scrollbars
45 * in a standalone window.
46 */
47 public class TTreeViewWindow extends TScrollableWindow {
48
49 // ------------------------------------------------------------------------
50 // Variables --------------------------------------------------------------
51 // ------------------------------------------------------------------------
52
53 /**
54 * The TTreeView
55 */
56 private TTreeView treeView;
57
58 /**
59 * If true, move the window to put the selected item in view. This
60 * normally only happens once after setting treeRoot.
61 */
62 private boolean centerWindow = false;
63
64 /**
65 * Maximum width of a single line.
66 */
67 private int maxLineWidth;
68
69 // ------------------------------------------------------------------------
70 // Constructors -----------------------------------------------------------
71 // ------------------------------------------------------------------------
72
73 /**
74 * Public constructor.
75 *
76 * @param parent the main application
77 * @param title the window title
78 * @param x column relative to parent
79 * @param y row relative to parent
80 * @param width width of tree view
81 * @param flags bitmask of RESIZABLE, CENTERED, or MODAL
82 * @param height height of tree view
83 */
84 public TTreeViewWindow(final TApplication parent, final String title,
85 final int x, final int y, final int width, final int height,
86 final int flags) {
87
88 this(parent, title, x, y, width, height, flags, null);
89 }
90
91 /**
92 * Public constructor.
93 *
94 * @param parent the main application
95 * @param title the window title
96 * @param x column relative to parent
97 * @param y row relative to parent
98 * @param width width of tree view
99 * @param height height of tree view
100 * @param flags bitmask of RESIZABLE, CENTERED, or MODAL
101 * @param action action to perform when an item is selected
102 */
103 public TTreeViewWindow(final TApplication parent, final String title,
104 final int x, final int y, final int width, final int height,
105 final int flags, final TAction action) {
106
107 super(parent, title, x, y, width, height, flags);
108
109 treeView = new TTreeView(this, 0, 0, getWidth() - 2, getHeight() - 2,
110 action);
111
112 hScroller = new THScroller(this, 17, getHeight() - 2, getWidth() - 20);
113 vScroller = new TVScroller(this, getWidth() - 2, 0, getHeight() - 2);
114
115 /*
116 System.err.println("TTreeViewWindow()");
117 for (TWidget w: getChildren()) {
118 System.err.println(" " + w + " " + w.isActive());
119 }
120 */
121 }
122
123 // ------------------------------------------------------------------------
124 // Event handlers ---------------------------------------------------------
125 // ------------------------------------------------------------------------
126
127 /**
128 * Handle mouse press events.
129 *
130 * @param mouse mouse button press event
131 */
132 @Override
133 public void onMouseDown(final TMouseEvent mouse) {
134 if (mouse.isMouseWheelUp()) {
135 verticalDecrement();
136 } else if (mouse.isMouseWheelDown()) {
137 verticalIncrement();
138 } else {
139 // Pass to the TreeView or scrollbars
140 super.onMouseDown(mouse);
141 }
142
143 // Update the view to reflect the new scrollbar positions
144 treeView.setTopLine(getVerticalValue());
145 treeView.setLeftColumn(getHorizontalValue());
146 reflowData();
147 }
148
149 /**
150 * Handle mouse release events.
151 *
152 * @param mouse mouse button release event
153 */
154 @Override
155 public void onMouseUp(final TMouseEvent mouse) {
156 // Pass to the TreeView or scrollbars
157 super.onMouseUp(mouse);
158
159 // Update the view to reflect the new scrollbar positions
160 treeView.setTopLine(getVerticalValue());
161 treeView.setLeftColumn(getHorizontalValue());
162 reflowData();
163 }
164
165 /**
166 * Handle mouse motion events.
167 *
168 * @param mouse mouse motion event
169 */
170 @Override
171 public void onMouseMotion(final TMouseEvent mouse) {
172 // Pass to the TreeView or scrollbars
173 super.onMouseMotion(mouse);
174
175 // Update the view to reflect the new scrollbar positions
176 treeView.setTopLine(getVerticalValue());
177 treeView.setLeftColumn(getHorizontalValue());
178 reflowData();
179 }
180
181 /**
182 * Handle keystrokes.
183 *
184 * @param keypress keystroke event
185 */
186 @Override
187 public void onKeypress(final TKeypressEvent keypress) {
188 if (inKeyboardResize) {
189 // Let TWindow do its job.
190 super.onKeypress(keypress);
191 return;
192 }
193
194 // Give the shortcut bar a shot at this.
195 if (statusBar != null) {
196 if (statusBar.statusBarKeypress(keypress)) {
197 return;
198 }
199 }
200
201 if (keypress.equals(kbShiftLeft)
202 || keypress.equals(kbCtrlLeft)
203 || keypress.equals(kbAltLeft)
204 ) {
205 horizontalDecrement();
206 } else if (keypress.equals(kbShiftRight)
207 || keypress.equals(kbCtrlRight)
208 || keypress.equals(kbAltRight)
209 ) {
210 horizontalIncrement();
211 } else if (keypress.equals(kbShiftUp)
212 || keypress.equals(kbCtrlUp)
213 || keypress.equals(kbAltUp)
214 ) {
215 verticalDecrement();
216 } else if (keypress.equals(kbShiftDown)
217 || keypress.equals(kbCtrlDown)
218 || keypress.equals(kbAltDown)
219 ) {
220 verticalIncrement();
221 } else if (keypress.equals(kbShiftPgUp)
222 || keypress.equals(kbCtrlPgUp)
223 || keypress.equals(kbAltPgUp)
224 ) {
225 bigVerticalDecrement();
226 } else if (keypress.equals(kbShiftPgDn)
227 || keypress.equals(kbCtrlPgDn)
228 || keypress.equals(kbAltPgDn)
229 ) {
230 bigVerticalIncrement();
231 } else {
232 treeView.onKeypress(keypress);
233
234 // Update the scrollbars to reflect the new data position
235 reflowData();
236 return;
237 }
238
239 // Update the view to reflect the new scrollbar position
240 treeView.setTopLine(getVerticalValue());
241 treeView.setLeftColumn(getHorizontalValue());
242 reflowData();
243 }
244
245 // ------------------------------------------------------------------------
246 // TScrollableWindow ------------------------------------------------------
247 // ------------------------------------------------------------------------
248
249 /**
250 * Handle window/screen resize events.
251 *
252 * @param resize resize event
253 */
254 @Override
255 public void onResize(final TResizeEvent resize) {
256 if (resize.getType() == TResizeEvent.Type.WIDGET) {
257 // Resize the treeView field.
258 TResizeEvent treeSize = new TResizeEvent(TResizeEvent.Type.WIDGET,
259 resize.getWidth() - 2, resize.getHeight() - 2);
260 treeView.onResize(treeSize);
261
262 // Have TScrollableWindow handle the scrollbars.
263 super.onResize(resize);
264
265 // Now re-center the treeView field.
266 if (treeView.getSelected() != null) {
267 treeView.setSelected(treeView.getSelected(), true);
268 }
269 reflowData();
270 return;
271 }
272 }
273
274 /**
275 * Resize text and scrollbars for a new width/height.
276 */
277 @Override
278 public void reflowData() {
279 int selectedRow = 0;
280 boolean foundSelectedRow = false;
281
282 // Reset the keyboard list, expandTree() will recreate it.
283 for (TWidget widget: treeView.getChildren()) {
284 TTreeItem item = (TTreeItem) widget;
285 item.keyboardPrevious = null;
286 item.keyboardNext = null;
287 }
288
289 // Expand the tree into a linear list
290 treeView.getChildren().clear();
291 treeView.getChildren().addAll(treeView.getTreeRoot().expandTree("",
292 true));
293
294 // Locate the selected row and maximum line width
295 for (TWidget widget: treeView.getChildren()) {
296 TTreeItem item = (TTreeItem) widget;
297
298 if (item == treeView.getSelected()) {
299 foundSelectedRow = true;
300 }
301 if (!foundSelectedRow) {
302 selectedRow++;
303 }
304
305 int lineWidth = StringUtils.width(item.getText())
306 + item.getPrefix().length() + 4;
307 if (lineWidth > maxLineWidth) {
308 maxLineWidth = lineWidth;
309 }
310 }
311
312 if ((centerWindow) && (foundSelectedRow)) {
313 if ((selectedRow < getVerticalValue())
314 || (selectedRow > getVerticalValue() + getHeight() - 3)
315 ) {
316 treeView.setTopLine(selectedRow);
317 centerWindow = false;
318 }
319 }
320 treeView.alignTree();
321
322 // Rescale the scroll bars
323 setVerticalValue(treeView.getTopLine());
324 setBottomValue(treeView.getTotalLineCount() - (getHeight() - 2));
325 if (getBottomValue() < getTopValue()) {
326 setBottomValue(getTopValue());
327 }
328 if (getVerticalValue() > getBottomValue()) {
329 setVerticalValue(getBottomValue());
330 }
331 setRightValue(maxLineWidth - 4);
332 if (getHorizontalValue() > getRightValue()) {
333 setHorizontalValue(getRightValue());
334 }
335 }
336
337 // ------------------------------------------------------------------------
338 // TTreeView --------------------------------------------------------------
339 // ------------------------------------------------------------------------
340
341 /**
342 * Get the underlying TTreeView.
343 *
344 * @return the TTreeView
345 */
346 public TTreeView getTreeView() {
347 return treeView;
348 }
349
350 /**
351 * Get the root of the tree.
352 *
353 * @return the root of the tree
354 */
355 public final TTreeItem getTreeRoot() {
356 return treeView.getTreeRoot();
357 }
358
359 /**
360 * Set the root of the tree.
361 *
362 * @param treeRoot the new root of the tree
363 */
364 public final void setTreeRoot(final TTreeItem treeRoot) {
365 treeView.setTreeRoot(treeRoot);
366 }
367
368 /**
369 * Set treeRoot.
370 *
371 * @param treeRoot ultimate root of tree
372 * @param centerWindow if true, move the window to put the root in view
373 */
374 public void setTreeRoot(final TTreeItem treeRoot,
375 final boolean centerWindow) {
376
377 treeView.setTreeRoot(treeRoot);
378 this.centerWindow = centerWindow;
379 }
380
381 /**
382 * Get the tree view item that was selected.
383 *
384 * @return the selected item, or null if no item is selected
385 */
386 public final TTreeItem getSelected() {
387 return treeView.getSelected();
388 }
389
390 /**
391 * Set the new selected tree view item.
392 *
393 * @param item new item that became selected
394 * @param centerWindow if true, move the window to put the selected into
395 * view
396 */
397 public void setSelected(final TTreeItem item, final boolean centerWindow) {
398 treeView.setSelected(item, centerWindow);
399 }
400
401 /**
402 * Perform user selection action.
403 */
404 public void dispatch() {
405 treeView.dispatch();
406 }
407
408 }