Commit | Line | Data |
---|---|---|
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 |
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; | |
e820d5dd | 37 | import jexer.bits.StringUtils; |
7668cb45 KL |
38 | import jexer.event.TKeypressEvent; |
39 | import jexer.event.TMouseEvent; | |
8f62f06e | 40 | import jexer.event.TResizeEvent; |
7668cb45 KL |
41 | import static jexer.TKeypress.*; |
42 | ||
43 | /** | |
d36057df | 44 | * TTreeViewWidget wraps a tree view with horizontal and vertical scrollbars. |
7668cb45 | 45 | */ |
d36057df | 46 | public 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 | } |