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