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