bugfixes
[fanfix.git] / src / jexer / TMessageBox.java
1 /**
2 * Jexer - Java Text User Interface
3 *
4 * License: LGPLv3 or later
5 *
6 * This module is licensed under the GNU Lesser General Public License
7 * Version 3. Please see the file "COPYING" in this directory for more
8 * information about the GNU Lesser General Public License Version 3.
9 *
10 * Copyright (C) 2015 Kevin Lamonte
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public License
14 * as published by the Free Software Foundation; either version 3 of
15 * the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this program; if not, see
24 * http://www.gnu.org/licenses/, or write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
26 * 02110-1301 USA
27 *
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 * @version 1
30 */
31 package jexer;
32
33 import java.util.ArrayList;
34 import java.util.List;
35
36 import jexer.event.TKeypressEvent;
37 import static jexer.TKeypress.*;
38
39 /**
40 * TMessageBox is a system-modal dialog with buttons for OK, Cancel, Yes, or
41 * No. Call it like:
42 *
43 * <p>
44 * <pre>
45 * {@code
46 * box = application.messageBox(title, caption,
47 * TMessageBox.Type.OK | TMessageBox.Type.CANCEL);
48 *
49 * if (box.getResult() == TMessageBox.OK) {
50 * ... the user pressed OK, do stuff ...
51 * }
52 * }
53 * </pre>
54 *
55 */
56 public class TMessageBox extends TWindow {
57
58 /**
59 * Message boxes have these supported types.
60 */
61 public enum Type {
62 /**
63 * Show an OK button.
64 */
65 OK,
66
67 /**
68 * Show both OK and Cancel buttons.
69 */
70 OKCANCEL,
71
72 /**
73 * Show both Yes and No buttons.
74 */
75 YESNO,
76
77 /**
78 * Show Yes, No, and Cancel buttons.
79 */
80 YESNOCANCEL
81 };
82
83 /**
84 * The type of this message box.
85 */
86 private Type type;
87
88 /**
89 * My buttons.
90 */
91 List<TButton> buttons;
92
93 /**
94 * Message boxes have these possible results.
95 */
96 public enum Result {
97 /**
98 * User clicked "OK".
99 */
100 OK,
101
102 /**
103 * User clicked "Cancel".
104 */
105 CANCEL,
106
107 /**
108 * User clicked "Yes".
109 */
110 YES,
111
112 /**
113 * User clicked "No".
114 */
115 NO
116 };
117
118 /**
119 * Which button was clicked: OK, CANCEL, YES, or NO.
120 */
121 private Result result = Result.OK;
122
123 /**
124 * Get the result.
125 *
126 * @return the result: OK, CANCEL, YES, or NO.
127 */
128 public final Result getResult() {
129 return result;
130 }
131
132 /**
133 * Public constructor. The message box will be centered on screen.
134 *
135 * @param application TApplication that manages this window
136 * @param title window title, will be centered along the top border
137 * @param caption message to display. Use embedded newlines to get a
138 * multi-line box.
139 */
140 public TMessageBox(final TApplication application, final String title,
141 final String caption) {
142
143 this(application, title, caption, Type.OK, true);
144 }
145
146 /**
147 * Public constructor. The message box will be centered on screen.
148 *
149 * @param application TApplication that manages this window
150 * @param title window title, will be centered along the top border
151 * @param caption message to display. Use embedded newlines to get a
152 * multi-line box.
153 * @param type one of the Type constants. Default is Type.OK.
154 */
155 public TMessageBox(final TApplication application, final String title,
156 final String caption, final Type type) {
157
158 this(application, title, caption, type, true);
159 }
160
161 /**
162 * Public constructor. The message box will be centered on screen.
163 *
164 * @param application TApplication that manages this window
165 * @param title window title, will be centered along the top border
166 * @param caption message to display. Use embedded newlines to get a
167 * multi-line box.
168 * @param type one of the Type constants. Default is Type.OK.
169 * @param yield if true, yield this Thread. Subclasses need to set this
170 * to false and yield at their end of their constructor intead.
171 */
172 protected TMessageBox(final TApplication application, final String title,
173 final String caption, final Type type, final boolean yield) {
174
175 // Start as 50x50 at (1, 1). These will be changed later.
176 super(application, title, 1, 1, 100, 100, CENTERED | MODAL);
177
178 // Hang onto type so that we can provide more convenience in
179 // onKeypress().
180 this.type = type;
181
182 // Determine width and height
183 String [] lines = caption.split("\n");
184 int width = title.length() + 12;
185 setHeight(6 + lines.length);
186 for (String line: lines) {
187 if (line.length() + 4 > width) {
188 width = line.length() + 4;
189 }
190 }
191 setWidth(width);
192 if (getWidth() > getScreen().getWidth()) {
193 setWidth(getScreen().getWidth());
194 }
195 // Re-center window to get an appropriate (x, y)
196 center();
197
198 // Now add my elements
199 int lineI = 1;
200 for (String line: lines) {
201 addLabel(line, 1, lineI, "twindow.background.modal");
202 lineI++;
203 }
204
205 // The button line
206 lineI++;
207 buttons = new ArrayList<TButton>();
208
209 int buttonX = 0;
210
211 // Setup button actions
212 switch (type) {
213
214 case OK:
215 result = Result.OK;
216 if (getWidth() < 15) {
217 setWidth(15);
218 }
219 buttonX = (getWidth() - 11) / 2;
220 buttons.add(addButton(" &OK ", buttonX, lineI,
221 new TAction() {
222 public void DO() {
223 result = Result.OK;
224 getApplication().closeWindow(TMessageBox.this);
225 }
226 }
227 )
228 );
229 break;
230
231 case OKCANCEL:
232 result = Result.CANCEL;
233 if (getWidth() < 26) {
234 setWidth(26);
235 }
236 buttonX = (getWidth() - 22) / 2;
237 buttons.add(addButton(" &OK ", buttonX, lineI,
238 new TAction() {
239 public void DO() {
240 result = Result.OK;
241 getApplication().closeWindow(TMessageBox.this);
242 }
243 }
244 )
245 );
246 buttonX += 8 + 4;
247 buttons.add(addButton("&Cancel", buttonX, lineI,
248 new TAction() {
249 public void DO() {
250 result = Result.CANCEL;
251 getApplication().closeWindow(TMessageBox.this);
252 }
253 }
254 )
255 );
256 break;
257
258 case YESNO:
259 result = Result.NO;
260 if (getWidth() < 20) {
261 setWidth(20);
262 }
263 buttonX = (getWidth() - 16) / 2;
264 buttons.add(addButton("&Yes", buttonX, lineI,
265 new TAction() {
266 public void DO() {
267 result = Result.YES;
268 getApplication().closeWindow(TMessageBox.this);
269 }
270 }
271 )
272 );
273 buttonX += 5 + 4;
274 buttons.add(addButton("&No", buttonX, lineI,
275 new TAction() {
276 public void DO() {
277 result = Result.NO;
278 getApplication().closeWindow(TMessageBox.this);
279 }
280 }
281 )
282 );
283 break;
284
285 case YESNOCANCEL:
286 result = Result.CANCEL;
287 if (getWidth() < 31) {
288 setWidth(31);
289 }
290 buttonX = (getWidth() - 27) / 2;
291 buttons.add(addButton("&Yes", buttonX, lineI,
292 new TAction() {
293 public void DO() {
294 result = Result.YES;
295 getApplication().closeWindow(TMessageBox.this);
296 }
297 }
298 )
299 );
300 buttonX += 5 + 4;
301 buttons.add(addButton("&No", buttonX, lineI,
302 new TAction() {
303 public void DO() {
304 result = Result.NO;
305 getApplication().closeWindow(TMessageBox.this);
306 }
307 }
308 )
309 );
310 buttonX += 4 + 4;
311 buttons.add(addButton("&Cancel", buttonX, lineI,
312 new TAction() {
313 public void DO() {
314 result = Result.CANCEL;
315 getApplication().closeWindow(TMessageBox.this);
316 }
317 }
318 )
319 );
320 break;
321
322 default:
323 throw new IllegalArgumentException("Invalid message box type: " + type);
324 }
325
326 // Set the secondaryThread to run me
327 getApplication().enableSecondaryEventReceiver(this);
328
329 if (yield) {
330 // Yield to the secondary thread. When I come back from the
331 // constructor response will already be set.
332 getApplication().yield();
333 }
334 }
335
336 /**
337 * Handle keystrokes.
338 *
339 * @param keypress keystroke event
340 */
341 @Override
342 public void onKeypress(final TKeypressEvent keypress) {
343
344 if (this instanceof TInputBox) {
345 super.onKeypress(keypress);
346 return;
347 }
348
349 // Some convenience for message boxes: Alt won't be needed for the
350 // buttons.
351 switch (type) {
352
353 case OK:
354 if (keypress.equals(kbO)) {
355 buttons.get(0).dispatch();
356 return;
357 }
358 break;
359
360 case OKCANCEL:
361 if (keypress.equals(kbO)) {
362 buttons.get(0).dispatch();
363 return;
364 } else if (keypress.equals(kbC)) {
365 buttons.get(1).dispatch();
366 return;
367 }
368 break;
369
370 case YESNO:
371 if (keypress.equals(kbY)) {
372 buttons.get(0).dispatch();
373 return;
374 } else if (keypress.equals(kbN)) {
375 buttons.get(1).dispatch();
376 return;
377 }
378 break;
379
380 case YESNOCANCEL:
381 if (keypress.equals(kbY)) {
382 buttons.get(0).dispatch();
383 return;
384 } else if (keypress.equals(kbN)) {
385 buttons.get(1).dispatch();
386 return;
387 } else if (keypress.equals(kbC)) {
388 buttons.get(2).dispatch();
389 return;
390 }
391 break;
392
393 default:
394 throw new IllegalArgumentException("Invalid message box type: " + type);
395 }
396
397 super.onKeypress(keypress);
398 }
399
400 }