2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 David "Niki" ROULET
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:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
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.
26 * @author David ROULET [niki@nikiroo.be]
29 package be
.nikiroo
.jexer
;
31 import static jexer
.TKeypress
.kbBackTab
;
32 import static jexer
.TKeypress
.kbDown
;
33 import static jexer
.TKeypress
.kbEnd
;
34 import static jexer
.TKeypress
.kbEnter
;
35 import static jexer
.TKeypress
.kbHome
;
36 import static jexer
.TKeypress
.kbLeft
;
37 import static jexer
.TKeypress
.kbPgDn
;
38 import static jexer
.TKeypress
.kbPgUp
;
39 import static jexer
.TKeypress
.kbRight
;
40 import static jexer
.TKeypress
.kbShiftTab
;
41 import static jexer
.TKeypress
.kbTab
;
42 import static jexer
.TKeypress
.kbUp
;
43 import jexer
.THScroller
;
44 import jexer
.TScrollableWidget
;
45 import jexer
.TVScroller
;
47 import jexer
.event
.TKeypressEvent
;
48 import jexer
.event
.TMouseEvent
;
49 import jexer
.event
.TResizeEvent
;
52 * This class represents a browsable {@link TWidget}, that is, a {@link TWidget}
53 * where you can use the keyboard or mouse to browse to one line to the next, or
58 abstract public class TBrowsableWidget
extends TScrollableWidget
{
59 private int selectedRow
;
60 private int selectedColumn
;
64 * The number of rows in this {@link TWidget}.
66 * @return the number of rows
68 abstract protected int getRowCount();
71 * The number of columns in this {@link TWidget}.
73 * @return the number of columns
75 abstract protected int getColumnCount();
78 * The virtual width of this {@link TWidget}, that is, the total width it
79 * can take to display all the data.
83 abstract int getVirtualWidth();
86 * The virtual height of this {@link TWidget}, that is, the total width it
87 * can take to display all the data.
91 abstract int getVirtualHeight();
94 * Basic setup of this class (called by all constructors)
96 private void setup() {
97 vScroller
= new TVScroller(this, 0, 0, 1);
98 hScroller
= new THScroller(this, 0, 0, 1);
103 * Create a new {@link TBrowsableWidget} linked to the given {@link TWidget}
109 protected TBrowsableWidget(final TWidget parent
) {
115 * Create a new {@link TBrowsableWidget} linked to the given {@link TWidget}
121 * column relative to parent
123 * row relative to parent
129 protected TBrowsableWidget(final TWidget parent
, final int x
, final int y
,
130 final int width
, final int height
) {
131 super(parent
, x
, y
, width
, height
);
136 * Create a new {@link TBrowsableWidget} linked to the given {@link TWidget}
142 * if true assume enabled
144 protected TBrowsableWidget(final TWidget parent
, final boolean enabled
) {
145 super(parent
, enabled
);
150 * Create a new {@link TBrowsableWidget} linked to the given {@link TWidget}
156 * if true assume enabled
158 * column relative to parent
160 * row relative to parent
166 protected TBrowsableWidget(final TWidget parent
, final boolean enabled
,
167 final int x
, final int y
, final int width
, final int height
) {
168 super(parent
, enabled
, x
, y
, width
, height
);
173 * The currently selected row (or -1 if no row is selected).
175 * @return the selected row
177 public int getSelectedRow() {
182 * The currently selected row (or -1 if no row is selected).
184 * You may want to call {@link TBrowsableWidget#reflowData()} when done to
188 * the new selected row
190 * @throws IndexOutOfBoundsException
191 * when the index is out of bounds
193 public void setSelectedRow(int selectedRow
) {
194 if (selectedRow
< -1 || selectedRow
>= getRowCount()) {
195 throw new IndexOutOfBoundsException(String
.format(
196 "Cannot set row %d on a table with %d rows", selectedRow
,
200 this.selectedRow
= selectedRow
;
204 * The currently selected column (or -1 if no column is selected).
206 * @return the new selected column
208 public int getSelectedColumn() {
209 return selectedColumn
;
213 * The currently selected column (or -1 if no column is selected).
215 * You may want to call {@link TBrowsableWidget#reflowData()} when done to
218 * @param selectedColumn
219 * the new selected column
221 * @throws IndexOutOfBoundsException
222 * when the index is out of bounds
224 public void setSelectedColumn(int selectedColumn
) {
225 if (selectedColumn
< -1 || selectedColumn
>= getColumnCount()) {
226 throw new IndexOutOfBoundsException(String
.format(
227 "Cannot set column %d on a table with %d columns",
228 selectedColumn
, getColumnCount()));
231 this.selectedColumn
= selectedColumn
;
235 * An offset on the Y position of the table, i.e., the number of rows to
236 * skip so the control can draw that many rows always on top.
240 public int getYOffset() {
245 * An offset on the Y position of the table, i.e., the number of rows that
246 * should always stay on top.
251 public void setYOffset(int yOffset
) {
252 this.yOffset
= yOffset
;
255 @SuppressWarnings("unused")
256 public void dispatchMove(int fromRow
, int toRow
) {
260 @SuppressWarnings("unused")
261 public void dispatchEnter(int selectedRow
) {
266 public void onMouseDown(final TMouseEvent mouse
) {
267 if (mouse
.isMouseWheelUp()) {
268 vScroller
.decrement();
271 if (mouse
.isMouseWheelDown()) {
272 vScroller
.increment();
276 if ((mouse
.getX() < getWidth() - 1) && (mouse
.getY() < getHeight() - 1)) {
277 if (vScroller
.getValue() + mouse
.getY() < getRowCount()) {
278 selectedRow
= vScroller
.getValue() + mouse
.getY()
281 dispatchEnter(selectedRow
);
286 super.onMouseDown(mouse
);
290 public void onKeypress(final TKeypressEvent keypress
) {
291 int maxX
= getRowCount();
292 int prevSelectedRow
= selectedRow
;
294 int firstLineIndex
= vScroller
.getValue() - getYOffset() + 2;
295 int lastLineIndex
= firstLineIndex
- hScroller
.getHeight()
296 + getHeight() - 2 - 2;
298 if (keypress
.equals(kbLeft
)) {
299 hScroller
.decrement();
300 } else if (keypress
.equals(kbRight
)) {
301 hScroller
.increment();
302 } else if (keypress
.equals(kbUp
)) {
303 if (maxX
> 0 && selectedRow
< maxX
) {
304 if (selectedRow
> 0) {
305 if (selectedRow
<= firstLineIndex
) {
306 vScroller
.decrement();
313 dispatchMove(prevSelectedRow
, selectedRow
);
315 } else if (keypress
.equals(kbDown
)) {
317 if (selectedRow
>= 0) {
318 if (selectedRow
< maxX
- 1) {
320 if (selectedRow
>= lastLineIndex
) {
321 vScroller
.increment();
328 dispatchMove(prevSelectedRow
, selectedRow
);
330 } else if (keypress
.equals(kbPgUp
)) {
331 if (selectedRow
>= 0) {
332 vScroller
.bigDecrement();
333 selectedRow
-= getHeight() - 1;
334 if (selectedRow
< 0) {
338 dispatchMove(prevSelectedRow
, selectedRow
);
340 } else if (keypress
.equals(kbPgDn
)) {
341 if (selectedRow
>= 0) {
342 vScroller
.bigIncrement();
343 selectedRow
+= getHeight() - 1;
344 if (selectedRow
> getRowCount() - 1) {
345 selectedRow
= getRowCount() - 1;
348 dispatchMove(prevSelectedRow
, selectedRow
);
350 } else if (keypress
.equals(kbHome
)) {
351 if (getRowCount() > 0) {
354 dispatchMove(prevSelectedRow
, selectedRow
);
356 } else if (keypress
.equals(kbEnd
)) {
357 if (getRowCount() > 0) {
358 vScroller
.toBottom();
359 selectedRow
= getRowCount() - 1;
360 dispatchMove(prevSelectedRow
, selectedRow
);
362 } else if (keypress
.equals(kbTab
)) {
363 getParent().switchWidget(true);
364 } else if (keypress
.equals(kbShiftTab
) || keypress
.equals(kbBackTab
)) {
365 getParent().switchWidget(false);
366 } else if (keypress
.equals(kbEnter
)) {
367 if (selectedRow
>= 0) {
368 dispatchEnter(selectedRow
);
371 // Pass other keys (tab etc.) on
372 super.onKeypress(keypress
);
377 public void onResize(TResizeEvent event
) {
378 super.onResize(event
);
383 public void reflowData() {
388 private void fixScrollers() {
389 int width
= getWidth() - 1; // vertical prio
390 int height
= getHeight();
392 // TODO: why did we do that before?
398 int x
= Math
.max(0, width
);
399 int y
= Math
.max(0, height
- 1);
402 vScroller
.setHeight(height
);
404 hScroller
.setWidth(width
);
406 // TODO why did we use to add 2?
407 // + 2 (for the border of the window)
410 // + the other scroll bar size
411 vScroller
.setTopValue(0);
412 vScroller
.setBottomValue(Math
.max(0, getVirtualHeight() - getHeight()
413 + hScroller
.getHeight()));
414 hScroller
.setLeftValue(0);
415 hScroller
.setRightValue(Math
.max(0, getVirtualWidth() - getWidth()
416 + vScroller
.getWidth()));