Prep for 2019 release
[fanfix.git] / src / jexer / TMessageBox.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 import java.util.ResourceBundle;
34
35 import jexer.event.TKeypressEvent;
36 import static jexer.TKeypress.*;
37
38 /**
39 * TMessageBox is a system-modal dialog with buttons for OK, Cancel, Yes, or
40 * No. Call it like:
41 *
42 * <pre>
43 * {@code
44 * box = application.messageBox(title, caption,
45 * TMessageBox.Type.OK | TMessageBox.Type.CANCEL);
46 *
47 * if (box.getResult() == TMessageBox.OK) {
48 * ... the user pressed OK, do stuff ...
49 * }
50 * }
51 * </pre>
52 *
53 */
54 public class TMessageBox extends TWindow {
55
56 /**
57 * Translated strings.
58 */
59 private static final ResourceBundle i18n = ResourceBundle.getBundle(TMessageBox.class.getName());
60
61 // ------------------------------------------------------------------------
62 // Constants --------------------------------------------------------------
63 // ------------------------------------------------------------------------
64
65 /**
66 * Message boxes have these supported types.
67 */
68 public enum Type {
69 /**
70 * Show an OK button.
71 */
72 OK,
73
74 /**
75 * Show both OK and Cancel buttons.
76 */
77 OKCANCEL,
78
79 /**
80 * Show both Yes and No buttons.
81 */
82 YESNO,
83
84 /**
85 * Show Yes, No, and Cancel buttons.
86 */
87 YESNOCANCEL
88 };
89
90 /**
91 * Message boxes have these possible results.
92 */
93 public enum Result {
94 /**
95 * User clicked "OK".
96 */
97 OK,
98
99 /**
100 * User clicked "Cancel".
101 */
102 CANCEL,
103
104 /**
105 * User clicked "Yes".
106 */
107 YES,
108
109 /**
110 * User clicked "No".
111 */
112 NO
113 };
114
115 // ------------------------------------------------------------------------
116 // Variables --------------------------------------------------------------
117 // ------------------------------------------------------------------------
118
119 /**
120 * The type of this message box.
121 */
122 private Type type;
123
124 /**
125 * My buttons.
126 */
127 private List<TButton> buttons;
128
129 /**
130 * Which button was clicked: OK, CANCEL, YES, or NO.
131 */
132 private Result result = Result.OK;
133
134 // ------------------------------------------------------------------------
135 // Constructors -----------------------------------------------------------
136 // ------------------------------------------------------------------------
137
138 /**
139 * Public constructor. The message box will be centered on screen.
140 *
141 * @param application TApplication that manages this window
142 * @param title window title, will be centered along the top border
143 * @param caption message to display. Use embedded newlines to get a
144 * multi-line box.
145 */
146 public TMessageBox(final TApplication application, final String title,
147 final String caption) {
148
149 this(application, title, caption, Type.OK, true);
150 }
151
152 /**
153 * Public constructor. The message box will be centered on screen.
154 *
155 * @param application TApplication that manages this window
156 * @param title window title, will be centered along the top border
157 * @param caption message to display. Use embedded newlines to get a
158 * multi-line box.
159 * @param type one of the Type constants. Default is Type.OK.
160 */
161 public TMessageBox(final TApplication application, final String title,
162 final String caption, final Type type) {
163
164 this(application, title, caption, type, true);
165 }
166
167 /**
168 * Public constructor. The message box will be centered on screen.
169 *
170 * @param application TApplication that manages this window
171 * @param title window title, will be centered along the top border
172 * @param caption message to display. Use embedded newlines to get a
173 * multi-line box.
174 * @param type one of the Type constants. Default is Type.OK.
175 * @param yield if true, yield this Thread. Subclasses need to set this
176 * to false and yield at their end of their constructor intead.
177 */
178 protected TMessageBox(final TApplication application, final String title,
179 final String caption, final Type type, final boolean yield) {
180
181 // Start as 100x100 at (1, 1). These will be changed later.
182 super(application, title, 1, 1, 100, 100, CENTERED | MODAL);
183
184 // Hang onto type so that we can provide more convenience in
185 // onKeypress().
186 this.type = type;
187
188 // Determine width and height
189 String [] lines = caption.split("\n");
190 int width = title.length() + 12;
191 setHeight(6 + lines.length);
192 for (String line: lines) {
193 if (line.length() + 4 > width) {
194 width = line.length() + 4;
195 }
196 }
197 setWidth(width);
198 if (getWidth() > getScreen().getWidth()) {
199 setWidth(getScreen().getWidth());
200 }
201 // Re-center window to get an appropriate (x, y)
202 center();
203
204 // Now add my elements
205 int lineI = 1;
206 for (String line: lines) {
207 addLabel(line, 1, lineI, "twindow.background.modal");
208 lineI++;
209 }
210
211 // The button line
212 lineI++;
213 buttons = new ArrayList<TButton>();
214
215 int buttonX = 0;
216
217 // Setup button actions
218 switch (type) {
219
220 case OK:
221 result = Result.OK;
222 if (getWidth() < 15) {
223 setWidth(15);
224 }
225 buttonX = (getWidth() - 11) / 2;
226 buttons.add(addButton(i18n.getString("okButton"), buttonX, lineI,
227 new TAction() {
228 public void DO() {
229 result = Result.OK;
230 getApplication().closeWindow(TMessageBox.this);
231 }
232 }
233 )
234 );
235 break;
236
237 case OKCANCEL:
238 result = Result.CANCEL;
239 if (getWidth() < 26) {
240 setWidth(26);
241 }
242 buttonX = (getWidth() - 22) / 2;
243 buttons.add(addButton(i18n.getString("okButton"), buttonX, lineI,
244 new TAction() {
245 public void DO() {
246 result = Result.OK;
247 getApplication().closeWindow(TMessageBox.this);
248 }
249 }
250 )
251 );
252 buttonX += 8 + 4;
253 buttons.add(addButton(i18n.getString("cancelButton"), buttonX, lineI,
254 new TAction() {
255 public void DO() {
256 result = Result.CANCEL;
257 getApplication().closeWindow(TMessageBox.this);
258 }
259 }
260 )
261 );
262 break;
263
264 case YESNO:
265 result = Result.NO;
266 if (getWidth() < 20) {
267 setWidth(20);
268 }
269 buttonX = (getWidth() - 16) / 2;
270 buttons.add(addButton(i18n.getString("yesButton"), buttonX, lineI,
271 new TAction() {
272 public void DO() {
273 result = Result.YES;
274 getApplication().closeWindow(TMessageBox.this);
275 }
276 }
277 )
278 );
279 buttonX += 5 + 4;
280 buttons.add(addButton(i18n.getString("noButton"), buttonX, lineI,
281 new TAction() {
282 public void DO() {
283 result = Result.NO;
284 getApplication().closeWindow(TMessageBox.this);
285 }
286 }
287 )
288 );
289 break;
290
291 case YESNOCANCEL:
292 result = Result.CANCEL;
293 if (getWidth() < 31) {
294 setWidth(31);
295 }
296 buttonX = (getWidth() - 27) / 2;
297 buttons.add(addButton(i18n.getString("yesButton"), buttonX, lineI,
298 new TAction() {
299 public void DO() {
300 result = Result.YES;
301 getApplication().closeWindow(TMessageBox.this);
302 }
303 }
304 )
305 );
306 buttonX += 5 + 4;
307 buttons.add(addButton(i18n.getString("noButton"), buttonX, lineI,
308 new TAction() {
309 public void DO() {
310 result = Result.NO;
311 getApplication().closeWindow(TMessageBox.this);
312 }
313 }
314 )
315 );
316 buttonX += 4 + 4;
317 buttons.add(addButton(i18n.getString("cancelButton"), buttonX,
318 lineI,
319 new TAction() {
320 public void DO() {
321 result = Result.CANCEL;
322 getApplication().closeWindow(TMessageBox.this);
323 }
324 }
325 )
326 );
327 break;
328
329 default:
330 throw new IllegalArgumentException("Invalid message box type: " +
331 type);
332 }
333
334 if (yield) {
335 // Set the secondaryThread to run me
336 getApplication().enableSecondaryEventReceiver(this);
337
338 // Yield to the secondary thread. When I come back from the
339 // constructor response will already be set.
340 getApplication().yield();
341 }
342 }
343
344 // ------------------------------------------------------------------------
345 // TWindow ----------------------------------------------------------------
346 // ------------------------------------------------------------------------
347
348 /**
349 * Handle keystrokes.
350 *
351 * @param keypress keystroke event
352 */
353 @Override
354 public void onKeypress(final TKeypressEvent keypress) {
355
356 if (this instanceof TInputBox) {
357 super.onKeypress(keypress);
358 return;
359 }
360
361 // Some convenience for message boxes: Alt won't be needed for the
362 // buttons.
363 switch (type) {
364
365 case OK:
366 if (keypress.equals(kbO)) {
367 buttons.get(0).dispatch();
368 return;
369 }
370 break;
371
372 case OKCANCEL:
373 if (keypress.equals(kbO)) {
374 buttons.get(0).dispatch();
375 return;
376 } else if (keypress.equals(kbC)) {
377 buttons.get(1).dispatch();
378 return;
379 }
380 break;
381
382 case YESNO:
383 if (keypress.equals(kbY)) {
384 buttons.get(0).dispatch();
385 return;
386 } else if (keypress.equals(kbN)) {
387 buttons.get(1).dispatch();
388 return;
389 }
390 break;
391
392 case YESNOCANCEL:
393 if (keypress.equals(kbY)) {
394 buttons.get(0).dispatch();
395 return;
396 } else if (keypress.equals(kbN)) {
397 buttons.get(1).dispatch();
398 return;
399 } else if (keypress.equals(kbC)) {
400 buttons.get(2).dispatch();
401 return;
402 }
403 break;
404
405 default:
406 throw new IllegalArgumentException("Invalid message box type: " +
407 type);
408 }
409
410 super.onKeypress(keypress);
411 }
412
413 // ------------------------------------------------------------------------
414 // TMessageBox ------------------------------------------------------------
415 // ------------------------------------------------------------------------
416
417 /**
418 * Get the result.
419 *
420 * @return the result: OK, CANCEL, YES, or NO.
421 */
422 public final Result getResult() {
423 return result;
424 }
425
426 /**
427 * See if the user clicked YES.
428 *
429 * @return true if the user clicked YES
430 */
431 public final boolean isYes() {
432 return (result == Result.YES);
433 }
434
435 /**
436 * See if the user clicked NO.
437 *
438 * @return true if the user clicked NO
439 */
440 public final boolean isNo() {
441 return (result == Result.NO);
442 }
443
444 /**
445 * See if the user clicked OK.
446 *
447 * @return true if the user clicked OK
448 */
449 public final boolean isOk() {
450 return (result == Result.OK);
451 }
452
453 /**
454 * See if the user clicked CANCEL.
455 *
456 * @return true if the user clicked CANCEL
457 */
458 public final boolean isCancel() {
459 return (result == Result.CANCEL);
460 }
461
462 }