2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
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 Kevin Lamonte [kevin.lamonte@gmail.com]
31 import java
.util
.ArrayList
;
32 import java
.util
.List
;
34 import jexer
.bits
.CellAttributes
;
35 import jexer
.bits
.GraphicsChars
;
36 import jexer
.event
.TKeypressEvent
;
37 import jexer
.event
.TMouseEvent
;
38 import jexer
.event
.TResizeEvent
;
39 import jexer
.event
.TResizeEvent
.Type
;
40 import static jexer
.TKeypress
.*;
43 * TComboBox implements a combobox containing a drop-down list and edit
44 * field. Alt-Down can be used to show the drop-down.
46 public class TComboBox
extends TWidget
{
48 // ------------------------------------------------------------------------
49 // Variables --------------------------------------------------------------
50 // ------------------------------------------------------------------------
53 * The list of items in the drop-down.
58 * The edit field containing the value to return.
63 * The action to perform when the user selects an item (clicks or enter).
65 private TAction updateAction
= null;
68 * If true, the field cannot be updated to a value not on the list.
70 private boolean limitToListValue
= true;
73 * The height of the list of values when it is shown, or -1 to use the
74 * number of values in the list as the height.
76 private int valuesHeight
= -1;
79 * The values shown by the drop-down list.
81 private List
<String
> values
= new ArrayList
<String
>();
84 * When looking for a link between the displayed text and the list
85 * of values, do a case sensitive search.
87 private boolean caseSensitive
= true;
90 * The maximum height of the values drop-down when it is visible.
92 private int maxValuesHeight
= 3;
94 // ------------------------------------------------------------------------
95 // Constructors -----------------------------------------------------------
96 // ------------------------------------------------------------------------
101 * @param parent parent widget
102 * @param x column relative to parent
103 * @param y row relative to parent
104 * @param width visible combobox width, including the down-arrow
105 * @param values the possible values for the box, shown in the drop-down
106 * @param valuesIndex the initial index in values, or -1 for no default
108 * @param valuesHeight the height of the values drop-down when it is
109 * visible, or -1 to use the number of values as the height of the list
110 * @param updateAction action to call when a new value is selected from
111 * the list or enter is pressed in the edit field
113 public TComboBox(final TWidget parent
, final int x
, final int y
,
114 final int width
, final List
<String
> values
, final int valuesIndex
,
115 final int valuesHeight
, final TAction updateAction
) {
117 // Set parent and window
118 super(parent
, x
, y
, width
, 1);
120 assert (values
!= null);
122 this.updateAction
= updateAction
;
123 this.values
= values
;
124 this.valuesHeight
= valuesHeight
;
126 field
= new TField(this, 0, 0, Math
.max(0, width
- 3), false, "",
128 if (valuesIndex
>= 0) {
129 field
.setText(values
.get(valuesIndex
));
133 if (limitToListValue
) {
134 field
.setEnabled(false);
140 // ------------------------------------------------------------------------
141 // Event handlers ---------------------------------------------------------
142 // ------------------------------------------------------------------------
145 * Returns true if the mouse is currently on the down arrow.
147 * @param mouse mouse event
148 * @return true if the mouse is currently on the down arrow
150 private boolean mouseOnArrow(final TMouseEvent mouse
) {
151 if ((mouse
.getY() == 0)
152 && (mouse
.getX() >= getWidth() - 3)
153 && (mouse
.getX() <= getWidth() - 1)
161 * Handle mouse down clicks.
163 * @param mouse mouse button down event
166 public void onMouseDown(final TMouseEvent mouse
) {
167 if ((mouseOnArrow(mouse
)) && (mouse
.isMouse1())) {
168 // Make the list visible or not.
176 // Pass to parent for the things we don't care about.
177 super.onMouseDown(mouse
);
183 * @param keypress keystroke event
186 public void onKeypress(final TKeypressEvent keypress
) {
187 if (keypress
.equals(kbEsc
)) {
194 if (keypress
.equals(kbAltDown
)) {
199 if (keypress
.equals(kbTab
)
200 || (keypress
.equals(kbShiftTab
))
201 || (keypress
.equals(kbBackTab
))
209 // Pass to parent for the things we don't care about.
210 super.onKeypress(keypress
);
213 // ------------------------------------------------------------------------
214 // TWidget ----------------------------------------------------------------
215 // ------------------------------------------------------------------------
218 * Override TWidget's width: we need to set child widget widths.
220 * @param width new widget width
223 public void setWidth(final int width
) {
225 field
.setWidth(width
- 3);
228 list
.setWidth(width
);
230 super.setWidth(width
);
234 * Override TWidget's height: we can only set height at construction
237 * @param height new widget height (ignored)
240 public void setHeight(final int height
) {
245 * Draw the combobox down arrow.
249 CellAttributes comboBoxColor
;
251 if (!isAbsoluteActive()) {
252 // We lost focus, turn off the list.
256 if (isAbsoluteActive()) {
257 comboBoxColor
= getTheme().getColor("tcombobox.active");
259 comboBoxColor
= getTheme().getColor("tcombobox.inactive");
262 putCharXY(getWidth() - 3, 0, GraphicsChars
.DOWNARROWLEFT
,
264 putCharXY(getWidth() - 2, 0, GraphicsChars
.DOWNARROW
,
266 putCharXY(getWidth() - 1, 0, GraphicsChars
.DOWNARROWRIGHT
,
270 // ------------------------------------------------------------------------
271 // TComboBox --------------------------------------------------------------
272 // ------------------------------------------------------------------------
275 * Hide the drop-down list.
277 public void hideList() {
278 list
.setEnabled(false);
279 list
.setVisible(false);
281 if (limitToListValue
== false) {
287 * Show the drop-down list.
289 public void showList() {
290 list
.setEnabled(true);
291 list
.setVisible(true);
292 super.setHeight(list
.getHeight() + 1);
297 * Get combobox text value.
299 * @return text in the edit field
301 public String
getText() {
302 return field
.getText();
306 * Set combobox text value.
308 * @param text the new text in the edit field
310 public void setText(final String text
) {
315 * Set combobox text value.
317 * @param text the new text in the edit field
318 * @param caseSensitive if true, perform a case-sensitive search for the
321 public void setText(final String text
, final boolean caseSensitive
) {
322 this.caseSensitive
= caseSensitive
;
330 * Set combobox text to one of the list values.
332 * @param index the index in the list
334 public void setIndex(final int index
) {
335 list
.setSelectedIndex(index
);
336 field
.setText(list
.getSelected());
340 * Get a copy of the list of strings to display.
342 * @return the list of strings
344 public final List
<String
> getList() {
345 return list
.getList();
349 * Set the new list of strings to display.
351 * @param list new list of strings
353 public final void setList(final List
<String
> list
) {
354 this.list
.setList(list
);
355 this.list
.setHeight(Math
.max(3, Math
.min(list
.size() + 1,
361 * Make sure the widget displays all its elements correctly according to
362 * the current size and content.
364 public void reflowData() {
365 // TODO: why setW/setH/reflow not enough for the scrollbars?
366 TList list
= this.list
;
368 int valuesHeight
= this.valuesHeight
;
369 if (valuesHeight
< 0) {
370 valuesHeight
= values
== null ?
0 : values
.size() + 1;
373 list
.onResize(new TResizeEvent(Type
.WIDGET
, getWidth(),
375 setHeight(valuesHeight
+ 1);
378 field
.onResize(new TResizeEvent(Type
.WIDGET
, getWidth(),
383 public void onResize(TResizeEvent resize
) {
384 super.onResize(resize
);
389 * Display the drop-down menu represented by {@link TComboBox#list}.
391 private void displayDropdown() {
392 if (this.list
!= null) {
396 int valuesHeight
= this.valuesHeight
;
397 if (valuesHeight
< 0) {
398 valuesHeight
= values
== null ?
0 : values
.size() + 1;
401 TList list
= new TList(this, values
, 0, 1, getWidth(), valuesHeight
,
405 TList list
= TComboBox
.this.list
;
410 field
.setText(list
.getSelected());
413 if (updateAction
!= null) {
421 if (values
!= null) {
422 String current
= field
.getText();
423 for (i
= 0 ; i
< values
.size() ; i
++) {
424 String value
= values
.get(i
);
425 if ((caseSensitive
&& current
.equals(value
))
426 || (!caseSensitive
&& current
.equalsIgnoreCase(value
))) {
431 if (i
>= values
.size()) {
435 list
.setSelectedIndex(i
);
437 list
.setEnabled(true);
438 list
.setVisible(true);
447 * Hide the drop-down menu represented by {@link TComboBox#list}.
449 private void hideDropdown() {
450 TList list
= this.list
;
453 list
.setEnabled(false);
454 list
.setVisible(false);
458 if (limitToListValue
== false) {