Commit | Line | Data |
---|---|---|
051e2913 KL |
1 | /* |
2 | * Jexer - Java Text User Interface | |
3 | * | |
4 | * The MIT License (MIT) | |
5 | * | |
a69ed767 | 6 | * Copyright (C) 2019 Kevin Lamonte |
051e2913 KL |
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.List; | |
32 | ||
33 | import jexer.bits.CellAttributes; | |
34 | import jexer.bits.GraphicsChars; | |
35 | import jexer.event.TKeypressEvent; | |
36 | import jexer.event.TMouseEvent; | |
37 | import static jexer.TKeypress.*; | |
38 | ||
39 | /** | |
40 | * TComboBox implements a combobox containing a drop-down list and edit | |
41 | * field. Alt-Down can be used to show the drop-down. | |
42 | */ | |
43 | public class TComboBox extends TWidget { | |
44 | ||
45 | // ------------------------------------------------------------------------ | |
46 | // Variables -------------------------------------------------------------- | |
47 | // ------------------------------------------------------------------------ | |
48 | ||
49 | /** | |
50 | * The list of items in the drop-down. | |
51 | */ | |
52 | private TList list; | |
53 | ||
54 | /** | |
55 | * The edit field containing the value to return. | |
56 | */ | |
57 | private TField field; | |
58 | ||
59 | /** | |
60 | * The action to perform when the user selects an item (clicks or enter). | |
61 | */ | |
62 | private TAction updateAction = null; | |
63 | ||
a69ed767 KL |
64 | /** |
65 | * If true, the field cannot be updated to a value not on the list. | |
66 | */ | |
67 | private boolean limitToListValue = true; | |
68 | ||
8ab60a33 KL |
69 | /** |
70 | * The maximum height of the values drop-down when it is visible. | |
71 | */ | |
72 | private int maxValuesHeight = 3; | |
73 | ||
051e2913 KL |
74 | // ------------------------------------------------------------------------ |
75 | // Constructors ----------------------------------------------------------- | |
76 | // ------------------------------------------------------------------------ | |
77 | ||
78 | /** | |
79 | * Public constructor. | |
80 | * | |
81 | * @param parent parent widget | |
82 | * @param x column relative to parent | |
83 | * @param y row relative to parent | |
84 | * @param width visible combobox width, including the down-arrow | |
85 | * @param values the possible values for the box, shown in the drop-down | |
86 | * @param valuesIndex the initial index in values, or -1 for no default | |
87 | * value | |
8ab60a33 KL |
88 | * @param maxValuesHeight the maximum height of the values drop-down when |
89 | * it is visible | |
051e2913 KL |
90 | * @param updateAction action to call when a new value is selected from |
91 | * the list or enter is pressed in the edit field | |
92 | */ | |
93 | public TComboBox(final TWidget parent, final int x, final int y, | |
94 | final int width, final List<String> values, final int valuesIndex, | |
8ab60a33 | 95 | final int maxValuesHeight, final TAction updateAction) { |
051e2913 KL |
96 | |
97 | // Set parent and window | |
98 | super(parent, x, y, width, 1); | |
99 | ||
a69ed767 KL |
100 | assert (values != null); |
101 | ||
051e2913 | 102 | this.updateAction = updateAction; |
8ab60a33 | 103 | this.maxValuesHeight = maxValuesHeight; |
051e2913 | 104 | |
382bc294 | 105 | field = addField(0, 0, width - 3, false, "", updateAction, null); |
8ab60a33 | 106 | if ((valuesIndex >= 0) && (valuesIndex < values.size())) { |
051e2913 KL |
107 | field.setText(values.get(valuesIndex)); |
108 | } | |
109 | ||
8ab60a33 KL |
110 | list = addList(values, 0, 1, width, |
111 | Math.max(3, Math.min(values.size() + 1, maxValuesHeight)), | |
051e2913 KL |
112 | new TAction() { |
113 | public void DO() { | |
114 | field.setText(list.getSelected()); | |
115 | list.setEnabled(false); | |
116 | list.setVisible(false); | |
d8dc8aea | 117 | TComboBox.super.setHeight(1); |
a69ed767 KL |
118 | if (TComboBox.this.limitToListValue == false) { |
119 | TComboBox.this.activate(field); | |
120 | } | |
051e2913 | 121 | if (updateAction != null) { |
a524aa2e | 122 | updateAction.DO(TComboBox.this); |
051e2913 KL |
123 | } |
124 | } | |
125 | } | |
126 | ); | |
a69ed767 KL |
127 | if (valuesIndex >= 0) { |
128 | list.setSelectedIndex(valuesIndex); | |
129 | } | |
051e2913 KL |
130 | |
131 | list.setEnabled(false); | |
132 | list.setVisible(false); | |
d8dc8aea | 133 | super.setHeight(1); |
a69ed767 KL |
134 | if (limitToListValue) { |
135 | field.setEnabled(false); | |
136 | } else { | |
137 | activate(field); | |
138 | } | |
051e2913 KL |
139 | } |
140 | ||
141 | // ------------------------------------------------------------------------ | |
142 | // Event handlers --------------------------------------------------------- | |
143 | // ------------------------------------------------------------------------ | |
144 | ||
145 | /** | |
146 | * Returns true if the mouse is currently on the down arrow. | |
147 | * | |
148 | * @param mouse mouse event | |
149 | * @return true if the mouse is currently on the down arrow | |
150 | */ | |
151 | private boolean mouseOnArrow(final TMouseEvent mouse) { | |
152 | if ((mouse.getY() == 0) | |
a69ed767 KL |
153 | && (mouse.getX() >= getWidth() - 3) |
154 | && (mouse.getX() <= getWidth() - 1) | |
051e2913 KL |
155 | ) { |
156 | return true; | |
157 | } | |
158 | return false; | |
159 | } | |
160 | ||
161 | /** | |
162 | * Handle mouse down clicks. | |
163 | * | |
164 | * @param mouse mouse button down event | |
165 | */ | |
166 | @Override | |
167 | public void onMouseDown(final TMouseEvent mouse) { | |
168 | if ((mouseOnArrow(mouse)) && (mouse.isMouse1())) { | |
169 | // Make the list visible or not. | |
170 | if (list.isActive()) { | |
8ab60a33 | 171 | hideList(); |
051e2913 | 172 | } else { |
8ab60a33 | 173 | showList(); |
051e2913 KL |
174 | } |
175 | } | |
a69ed767 KL |
176 | |
177 | // Pass to parent for the things we don't care about. | |
178 | super.onMouseDown(mouse); | |
051e2913 KL |
179 | } |
180 | ||
181 | /** | |
182 | * Handle keystrokes. | |
183 | * | |
184 | * @param keypress keystroke event | |
185 | */ | |
186 | @Override | |
187 | public void onKeypress(final TKeypressEvent keypress) { | |
a69ed767 KL |
188 | if (keypress.equals(kbEsc)) { |
189 | if (list.isActive()) { | |
8ab60a33 | 190 | hideList(); |
a69ed767 KL |
191 | return; |
192 | } | |
193 | } | |
194 | ||
051e2913 | 195 | if (keypress.equals(kbAltDown)) { |
8ab60a33 | 196 | showList(); |
051e2913 KL |
197 | return; |
198 | } | |
199 | ||
200 | if (keypress.equals(kbTab) | |
201 | || (keypress.equals(kbShiftTab)) | |
202 | || (keypress.equals(kbBackTab)) | |
203 | ) { | |
204 | if (list.isActive()) { | |
8ab60a33 | 205 | hideList(); |
051e2913 KL |
206 | return; |
207 | } | |
208 | } | |
209 | ||
210 | // Pass to parent for the things we don't care about. | |
211 | super.onKeypress(keypress); | |
212 | } | |
213 | ||
214 | // ------------------------------------------------------------------------ | |
215 | // TWidget ---------------------------------------------------------------- | |
216 | // ------------------------------------------------------------------------ | |
217 | ||
d8dc8aea | 218 | /** |
8f62f06e | 219 | * Override TWidget's width: we need to set child widget widths. |
d8dc8aea | 220 | * |
8f62f06e | 221 | * @param width new widget width |
d8dc8aea KL |
222 | */ |
223 | @Override | |
224 | public void setWidth(final int width) { | |
fc2af494 KL |
225 | if (field != null) { |
226 | field.setWidth(width - 3); | |
227 | } | |
228 | if (list != null) { | |
229 | list.setWidth(width); | |
230 | } | |
8f62f06e | 231 | super.setWidth(width); |
d8dc8aea KL |
232 | } |
233 | ||
234 | /** | |
235 | * Override TWidget's height: we can only set height at construction | |
236 | * time. | |
237 | * | |
238 | * @param height new widget height (ignored) | |
239 | */ | |
240 | @Override | |
241 | public void setHeight(final int height) { | |
242 | // Do nothing | |
243 | } | |
244 | ||
051e2913 KL |
245 | /** |
246 | * Draw the combobox down arrow. | |
247 | */ | |
248 | @Override | |
249 | public void draw() { | |
250 | CellAttributes comboBoxColor; | |
251 | ||
a69ed767 KL |
252 | if (!isAbsoluteActive()) { |
253 | // We lost focus, turn off the list. | |
254 | if (list.isActive()) { | |
8ab60a33 | 255 | hideList(); |
a69ed767 KL |
256 | } |
257 | } | |
258 | ||
e23ea538 | 259 | if (isAbsoluteActive()) { |
051e2913 KL |
260 | comboBoxColor = getTheme().getColor("tcombobox.active"); |
261 | } else { | |
262 | comboBoxColor = getTheme().getColor("tcombobox.inactive"); | |
263 | } | |
264 | ||
a69ed767 KL |
265 | putCharXY(getWidth() - 3, 0, GraphicsChars.DOWNARROWLEFT, |
266 | comboBoxColor); | |
267 | putCharXY(getWidth() - 2, 0, GraphicsChars.DOWNARROW, | |
268 | comboBoxColor); | |
269 | putCharXY(getWidth() - 1, 0, GraphicsChars.DOWNARROWRIGHT, | |
051e2913 KL |
270 | comboBoxColor); |
271 | } | |
272 | ||
273 | // ------------------------------------------------------------------------ | |
274 | // TComboBox -------------------------------------------------------------- | |
275 | // ------------------------------------------------------------------------ | |
276 | ||
8ab60a33 KL |
277 | /** |
278 | * Hide the drop-down list. | |
279 | */ | |
280 | public void hideList() { | |
281 | list.setEnabled(false); | |
282 | list.setVisible(false); | |
d8dc8aea | 283 | super.setHeight(1); |
8ab60a33 KL |
284 | if (limitToListValue == false) { |
285 | activate(field); | |
286 | } | |
287 | } | |
288 | ||
289 | /** | |
290 | * Show the drop-down list. | |
291 | */ | |
292 | public void showList() { | |
293 | list.setEnabled(true); | |
294 | list.setVisible(true); | |
d8dc8aea | 295 | super.setHeight(list.getHeight() + 1); |
8ab60a33 KL |
296 | activate(list); |
297 | } | |
298 | ||
051e2913 KL |
299 | /** |
300 | * Get combobox text value. | |
301 | * | |
302 | * @return text in the edit field | |
303 | */ | |
304 | public String getText() { | |
305 | return field.getText(); | |
306 | } | |
307 | ||
308 | /** | |
309 | * Set combobox text value. | |
310 | * | |
311 | * @param text the new text in the edit field | |
312 | */ | |
313 | public void setText(final String text) { | |
af56159c KL |
314 | setText(text, true); |
315 | } | |
316 | ||
317 | /** | |
318 | * Set combobox text value. | |
319 | * | |
320 | * @param text the new text in the edit field | |
321 | * @param caseSensitive if true, perform a case-sensitive search for the | |
322 | * list item | |
323 | */ | |
324 | public void setText(final String text, final boolean caseSensitive) { | |
051e2913 KL |
325 | field.setText(text); |
326 | for (int i = 0; i < list.getMaxSelectedIndex(); i++) { | |
af56159c KL |
327 | if (caseSensitive == true) { |
328 | if (list.getListItem(i).equals(text)) { | |
329 | list.setSelectedIndex(i); | |
330 | return; | |
331 | } | |
332 | } else { | |
333 | if (list.getListItem(i).toLowerCase().equals(text.toLowerCase())) { | |
334 | list.setSelectedIndex(i); | |
335 | return; | |
336 | } | |
051e2913 KL |
337 | } |
338 | } | |
339 | list.setSelectedIndex(-1); | |
340 | } | |
341 | ||
a69ed767 KL |
342 | /** |
343 | * Set combobox text to one of the list values. | |
344 | * | |
345 | * @param index the index in the list | |
346 | */ | |
347 | public void setIndex(final int index) { | |
348 | list.setSelectedIndex(index); | |
349 | field.setText(list.getSelected()); | |
350 | } | |
351 | ||
352 | /** | |
353 | * Get a copy of the list of strings to display. | |
354 | * | |
355 | * @return the list of strings | |
356 | */ | |
357 | public final List<String> getList() { | |
358 | return list.getList(); | |
359 | } | |
360 | ||
361 | /** | |
362 | * Set the new list of strings to display. | |
363 | * | |
364 | * @param list new list of strings | |
365 | */ | |
366 | public final void setList(final List<String> list) { | |
367 | this.list.setList(list); | |
8ab60a33 KL |
368 | this.list.setHeight(Math.max(3, Math.min(list.size() + 1, |
369 | maxValuesHeight))); | |
a69ed767 KL |
370 | field.setText(""); |
371 | } | |
372 | ||
051e2913 | 373 | } |