Finish code sweep
[fanfix.git] / src / jexer / TMessageBox.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2017 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 * Get the result.
136 *
137 * @return the result: OK, CANCEL, YES, or NO.
138 */
139 public final Result getResult() {
140 return result;
141 }
142
143 // ------------------------------------------------------------------------
144 // Constructors -----------------------------------------------------------
145 // ------------------------------------------------------------------------
146
147 /**
148 * Public constructor. The message box will be centered on screen.
149 *
150 * @param application TApplication that manages this window
151 * @param title window title, will be centered along the top border
152 * @param caption message to display. Use embedded newlines to get a
153 * multi-line box.
154 */
155 public TMessageBox(final TApplication application, final String title,
156 final String caption) {
157
158 this(application, title, caption, Type.OK, 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 */
170 public TMessageBox(final TApplication application, final String title,
171 final String caption, final Type type) {
172
173 this(application, title, caption, type, true);
174 }
175
176 /**
177 * Public constructor. The message box will be centered on screen.
178 *
179 * @param application TApplication that manages this window
180 * @param title window title, will be centered along the top border
181 * @param caption message to display. Use embedded newlines to get a
182 * multi-line box.
183 * @param type one of the Type constants. Default is Type.OK.
184 * @param yield if true, yield this Thread. Subclasses need to set this
185 * to false and yield at their end of their constructor intead.
186 */
187 protected TMessageBox(final TApplication application, final String title,
188 final String caption, final Type type, final boolean yield) {
189
190 // Start as 50x50 at (1, 1). These will be changed later.
191 super(application, title, 1, 1, 100, 100, CENTERED | MODAL);
192
193 // Hang onto type so that we can provide more convenience in
194 // onKeypress().
195 this.type = type;
196
197 // Determine width and height
198 String [] lines = caption.split("\n");
199 int width = title.length() + 12;
200 setHeight(6 + lines.length);
201 for (String line: lines) {
202 if (line.length() + 4 > width) {
203 width = line.length() + 4;
204 }
205 }
206 setWidth(width);
207 if (getWidth() > getScreen().getWidth()) {
208 setWidth(getScreen().getWidth());
209 }
210 // Re-center window to get an appropriate (x, y)
211 center();
212
213 // Now add my elements
214 int lineI = 1;
215 for (String line: lines) {
216 addLabel(line, 1, lineI, "twindow.background.modal");
217 lineI++;
218 }
219
220 // The button line
221 lineI++;
222 buttons = new ArrayList<TButton>();
223
224 int buttonX = 0;
225
226 // Setup button actions
227 switch (type) {
228
229 case OK:
230 result = Result.OK;
231 if (getWidth() < 15) {
232 setWidth(15);
233 }
234 buttonX = (getWidth() - 11) / 2;
235 buttons.add(addButton(i18n.getString("okButton"), buttonX, lineI,
236 new TAction() {
237 public void DO() {
238 result = Result.OK;
239 getApplication().closeWindow(TMessageBox.this);
240 }
241 }
242 )
243 );
244 break;
245
246 case OKCANCEL:
247 result = Result.CANCEL;
248 if (getWidth() < 26) {
249 setWidth(26);
250 }
251 buttonX = (getWidth() - 22) / 2;
252 buttons.add(addButton(i18n.getString("okButton"), buttonX, lineI,
253 new TAction() {
254 public void DO() {
255 result = Result.OK;
256 getApplication().closeWindow(TMessageBox.this);
257 }
258 }
259 )
260 );
261 buttonX += 8 + 4;
262 buttons.add(addButton(i18n.getString("cancelButton"), buttonX, lineI,
263 new TAction() {
264 public void DO() {
265 result = Result.CANCEL;
266 getApplication().closeWindow(TMessageBox.this);
267 }
268 }
269 )
270 );
271 break;
272
273 case YESNO:
274 result = Result.NO;
275 if (getWidth() < 20) {
276 setWidth(20);
277 }
278 buttonX = (getWidth() - 16) / 2;
279 buttons.add(addButton(i18n.getString("yesButton"), buttonX, lineI,
280 new TAction() {
281 public void DO() {
282 result = Result.YES;
283 getApplication().closeWindow(TMessageBox.this);
284 }
285 }
286 )
287 );
288 buttonX += 5 + 4;
289 buttons.add(addButton(i18n.getString("noButton"), buttonX, lineI,
290 new TAction() {
291 public void DO() {
292 result = Result.NO;
293 getApplication().closeWindow(TMessageBox.this);
294 }
295 }
296 )
297 );
298 break;
299
300 case YESNOCANCEL:
301 result = Result.CANCEL;
302 if (getWidth() < 31) {
303 setWidth(31);
304 }
305 buttonX = (getWidth() - 27) / 2;
306 buttons.add(addButton(i18n.getString("yesButton"), buttonX, lineI,
307 new TAction() {
308 public void DO() {
309 result = Result.YES;
310 getApplication().closeWindow(TMessageBox.this);
311 }
312 }
313 )
314 );
315 buttonX += 5 + 4;
316 buttons.add(addButton(i18n.getString("noButton"), buttonX, lineI,
317 new TAction() {
318 public void DO() {
319 result = Result.NO;
320 getApplication().closeWindow(TMessageBox.this);
321 }
322 }
323 )
324 );
325 buttonX += 4 + 4;
326 buttons.add(addButton(i18n.getString("cancelButton"), buttonX,
327 lineI,
328 new TAction() {
329 public void DO() {
330 result = Result.CANCEL;
331 getApplication().closeWindow(TMessageBox.this);
332 }
333 }
334 )
335 );
336 break;
337
338 default:
339 throw new IllegalArgumentException("Invalid message box type: " + type);
340 }
341
342 // Set the secondaryThread to run me
343 getApplication().enableSecondaryEventReceiver(this);
344
345 if (yield) {
346 // Yield to the secondary thread. When I come back from the
347 // constructor response will already be set.
348 getApplication().yield();
349 }
350 }
351
352 // ------------------------------------------------------------------------
353 // TWindow ----------------------------------------------------------------
354 // ------------------------------------------------------------------------
355
356 /**
357 * Handle keystrokes.
358 *
359 * @param keypress keystroke event
360 */
361 @Override
362 public void onKeypress(final TKeypressEvent keypress) {
363
364 if (this instanceof TInputBox) {
365 super.onKeypress(keypress);
366 return;
367 }
368
369 // Some convenience for message boxes: Alt won't be needed for the
370 // buttons.
371 switch (type) {
372
373 case OK:
374 if (keypress.equals(kbO)) {
375 buttons.get(0).dispatch();
376 return;
377 }
378 break;
379
380 case OKCANCEL:
381 if (keypress.equals(kbO)) {
382 buttons.get(0).dispatch();
383 return;
384 } else if (keypress.equals(kbC)) {
385 buttons.get(1).dispatch();
386 return;
387 }
388 break;
389
390 case YESNO:
391 if (keypress.equals(kbY)) {
392 buttons.get(0).dispatch();
393 return;
394 } else if (keypress.equals(kbN)) {
395 buttons.get(1).dispatch();
396 return;
397 }
398 break;
399
400 case YESNOCANCEL:
401 if (keypress.equals(kbY)) {
402 buttons.get(0).dispatch();
403 return;
404 } else if (keypress.equals(kbN)) {
405 buttons.get(1).dispatch();
406 return;
407 } else if (keypress.equals(kbC)) {
408 buttons.get(2).dispatch();
409 return;
410 }
411 break;
412
413 default:
414 throw new IllegalArgumentException("Invalid message box type: " +
415 type);
416 }
417
418 super.onKeypress(keypress);
419 }
420
421 }