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