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