Common Scrollable interface
[fanfix.git] / src / jexer / TList.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2017 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;
30
31 import java.util.ArrayList;
32 import java.util.List;
33
34 import jexer.bits.CellAttributes;
35 import jexer.event.TKeypressEvent;
36 import jexer.event.TMouseEvent;
37 import static jexer.TKeypress.*;
38
39 /**
40 * TList shows a list of strings, and lets the user select one.
41 */
42 public class TList extends TScrollableWidget {
43
44 /**
45 * The list of strings to display.
46 */
47 private List<String> strings;
48
49 /**
50 * Selected string.
51 */
52 private int selectedString = -1;
53
54 /**
55 * Get the selection index.
56 *
57 * @return -1 if nothing is selected, otherwise the index into the list
58 */
59 public final int getSelectedIndex() {
60 return selectedString;
61 }
62
63 /**
64 * Set the selected string index.
65 *
66 * @param index -1 to unselect, otherwise the index into the list
67 */
68 public final void setSelectedIndex(final int index) {
69 selectedString = index;
70 }
71
72 /**
73 * Get the selected string.
74 *
75 * @return the selected string, or null of nothing is selected yet
76 */
77 public final String getSelected() {
78 if ((selectedString >= 0) && (selectedString <= strings.size() - 1)) {
79 return strings.get(selectedString);
80 }
81 return null;
82 }
83
84 /**
85 * Set the new list of strings to display.
86 *
87 * @param list new list of strings
88 */
89 public final void setList(final List<String> list) {
90 strings.clear();
91 strings.addAll(list);
92 reflowData();
93 }
94
95 /**
96 * Maximum width of a single line.
97 */
98 private int maxLineWidth;
99
100 /**
101 * The action to perform when the user selects an item (clicks or enter).
102 */
103 private TAction enterAction = null;
104
105 /**
106 * The action to perform when the user navigates with keyboard.
107 */
108 private TAction moveAction = null;
109
110 /**
111 * Perform user selection action.
112 */
113 public void dispatchEnter() {
114 assert (selectedString >= 0);
115 assert (selectedString < strings.size());
116 if (enterAction != null) {
117 enterAction.DO();
118 }
119 }
120
121 /**
122 * Perform list movement action.
123 */
124 public void dispatchMove() {
125 assert (selectedString >= 0);
126 assert (selectedString < strings.size());
127 if (moveAction != null) {
128 moveAction.DO();
129 }
130 }
131
132 /**
133 * Resize for a new width/height.
134 */
135 @Override
136 public void reflowData() {
137
138 // Reset the lines
139 selectedString = -1;
140 maxLineWidth = 0;
141
142 for (int i = 0; i < strings.size(); i++) {
143 String line = strings.get(i);
144 if (line.length() > maxLineWidth) {
145 maxLineWidth = line.length();
146 }
147 }
148
149 setBottomValue(strings.size() - getHeight() + 1);
150 if (getBottomValue() < 0) {
151 setBottomValue(0);
152 }
153
154 setRightValue(maxLineWidth - getWidth() + 1);
155 if (getRightValue() < 0) {
156 setRightValue(0);
157 }
158 }
159
160 /**
161 * Public constructor.
162 *
163 * @param parent parent widget
164 * @param strings list of strings to show
165 * @param x column relative to parent
166 * @param y row relative to parent
167 * @param width width of text area
168 * @param height height of text area
169 */
170 public TList(final TWidget parent, final List<String> strings, final int x,
171 final int y, final int width, final int height) {
172
173 this(parent, strings, x, y, width, height, null);
174 }
175
176 /**
177 * Public constructor.
178 *
179 * @param parent parent widget
180 * @param strings list of strings to show. This is allowed to be null
181 * and set later with setList() or by subclasses.
182 * @param x column relative to parent
183 * @param y row relative to parent
184 * @param width width of text area
185 * @param height height of text area
186 * @param enterAction action to perform when an item is selected
187 */
188 public TList(final TWidget parent, final List<String> strings, final int x,
189 final int y, final int width, final int height,
190 final TAction enterAction) {
191
192 super(parent, x, y, width, height);
193 this.enterAction = enterAction;
194 this.strings = new ArrayList<String>();
195 if (strings != null) {
196 this.strings.addAll(strings);
197 }
198
199 hScroller = new THScroller(this, 0, getHeight() - 1, getWidth() - 1);
200 vScroller = new TVScroller(this, getWidth() - 1, 0, getHeight() - 1);
201 reflowData();
202 }
203
204 /**
205 * Public constructor.
206 *
207 * @param parent parent widget
208 * @param strings list of strings to show. This is allowed to be null
209 * and set later with setList() or by subclasses.
210 * @param x column relative to parent
211 * @param y row relative to parent
212 * @param width width of text area
213 * @param height height of text area
214 * @param enterAction action to perform when an item is selected
215 * @param moveAction action to perform when the user navigates to a new
216 * item with arrow/page keys
217 */
218 public TList(final TWidget parent, final List<String> strings, final int x,
219 final int y, final int width, final int height,
220 final TAction enterAction, final TAction moveAction) {
221
222 super(parent, x, y, width, height);
223 this.enterAction = enterAction;
224 this.moveAction = moveAction;
225 this.strings = new ArrayList<String>();
226 if (strings != null) {
227 this.strings.addAll(strings);
228 }
229
230 hScroller = new THScroller(this, 0, getHeight() - 1, getWidth() - 1);
231 vScroller = new TVScroller(this, getWidth() - 1, 0, getHeight() - 1);
232 reflowData();
233 }
234
235 /**
236 * Draw the files list.
237 */
238 @Override
239 public void draw() {
240 CellAttributes color = null;
241 int begin = getVerticalValue();
242 int topY = 0;
243 for (int i = begin; i < strings.size(); i++) {
244 String line = strings.get(i);
245 if (getHorizontalValue() < line.length()) {
246 line = line.substring(getHorizontalValue());
247 } else {
248 line = "";
249 }
250 if (i == selectedString) {
251 color = getTheme().getColor("tlist.selected");
252 } else if (isAbsoluteActive()) {
253 color = getTheme().getColor("tlist");
254 } else {
255 color = getTheme().getColor("tlist.inactive");
256 }
257 String formatString = "%-" + Integer.toString(getWidth() - 1) + "s";
258 getScreen().putStringXY(0, topY, String.format(formatString, line),
259 color);
260 topY++;
261 if (topY >= getHeight() - 1) {
262 break;
263 }
264 }
265
266 if (isAbsoluteActive()) {
267 color = getTheme().getColor("tlist");
268 } else {
269 color = getTheme().getColor("tlist.inactive");
270 }
271
272 // Pad the rest with blank lines
273 for (int i = topY; i < getHeight() - 1; i++) {
274 getScreen().hLineXY(0, i, getWidth() - 1, ' ', color);
275 }
276 }
277
278 /**
279 * Handle mouse press events.
280 *
281 * @param mouse mouse button press event
282 */
283 @Override
284 public void onMouseDown(final TMouseEvent mouse) {
285 if (mouse.isMouseWheelUp()) {
286 verticalDecrement();
287 return;
288 }
289 if (mouse.isMouseWheelDown()) {
290 verticalIncrement();
291 return;
292 }
293
294 if ((mouse.getX() < getWidth() - 1)
295 && (mouse.getY() < getHeight() - 1)) {
296 if (getVerticalValue() + mouse.getY() < strings.size()) {
297 selectedString = getVerticalValue() + mouse.getY();
298 dispatchEnter();
299 }
300 return;
301 }
302
303 // Pass to children
304 super.onMouseDown(mouse);
305 }
306
307 /**
308 * Handle keystrokes.
309 *
310 * @param keypress keystroke event
311 */
312 @Override
313 public void onKeypress(final TKeypressEvent keypress) {
314 if (keypress.equals(kbLeft)) {
315 horizontalDecrement();
316 } else if (keypress.equals(kbRight)) {
317 horizontalIncrement();
318 } else if (keypress.equals(kbUp)) {
319 if (strings.size() > 0) {
320 if (selectedString >= 0) {
321 if (selectedString > 0) {
322 if (selectedString - getVerticalValue() == 0) {
323 verticalDecrement();
324 }
325 selectedString--;
326 }
327 } else {
328 selectedString = strings.size() - 1;
329 }
330 }
331 if (selectedString >= 0) {
332 dispatchMove();
333 }
334 } else if (keypress.equals(kbDown)) {
335 if (strings.size() > 0) {
336 if (selectedString >= 0) {
337 if (selectedString < strings.size() - 1) {
338 selectedString++;
339 if (selectedString - getVerticalValue() == getHeight() - 1) {
340 verticalIncrement();
341 }
342 }
343 } else {
344 selectedString = 0;
345 }
346 }
347 if (selectedString >= 0) {
348 dispatchMove();
349 }
350 } else if (keypress.equals(kbPgUp)) {
351 bigVerticalDecrement();
352 if (selectedString >= 0) {
353 selectedString -= getHeight() - 1;
354 if (selectedString < 0) {
355 selectedString = 0;
356 }
357 }
358 if (selectedString >= 0) {
359 dispatchMove();
360 }
361 } else if (keypress.equals(kbPgDn)) {
362 bigVerticalIncrement();
363 if (selectedString >= 0) {
364 selectedString += getHeight() - 1;
365 if (selectedString > strings.size() - 1) {
366 selectedString = strings.size() - 1;
367 }
368 }
369 if (selectedString >= 0) {
370 dispatchMove();
371 }
372 } else if (keypress.equals(kbHome)) {
373 toTop();
374 if (strings.size() > 0) {
375 selectedString = 0;
376 }
377 if (selectedString >= 0) {
378 dispatchMove();
379 }
380 } else if (keypress.equals(kbEnd)) {
381 toBottom();
382 if (strings.size() > 0) {
383 selectedString = strings.size() - 1;
384 }
385 if (selectedString >= 0) {
386 dispatchMove();
387 }
388 } else if (keypress.equals(kbTab)) {
389 getParent().switchWidget(true);
390 } else if (keypress.equals(kbShiftTab) || keypress.equals(kbBackTab)) {
391 getParent().switchWidget(false);
392 } else if (keypress.equals(kbEnter)) {
393 if (selectedString >= 0) {
394 dispatchEnter();
395 }
396 } else {
397 // Pass other keys (tab etc.) on
398 super.onKeypress(keypress);
399 }
400 }
401
402 }