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