/*
* Jexer - Java Text User Interface
*
* The MIT License (MIT)
*
* Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* @author Kevin Lamonte [kevin.lamonte@gmail.com]
* @version 1
*/
package jexer;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import jexer.bits.StringUtils;
import jexer.event.TKeypressEvent;
import static jexer.TKeypress.*;
/**
* TMessageBox is a system-modal dialog with buttons for OK, Cancel, Yes, or
* No. Call it like:
*
*
* {@code
* box = messageBox(title, caption,
* TMessageBox.Type.OK | TMessageBox.Type.CANCEL);
*
* if (box.getResult() == TMessageBox.OK) {
* ... the user pressed OK, do stuff ...
* }
* }
*
*
*/
public class TMessageBox extends TWindow {
/**
* Translated strings.
*/
private static final ResourceBundle i18n = ResourceBundle.getBundle(TMessageBox.class.getName());
// ------------------------------------------------------------------------
// Constants --------------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Message boxes have these supported types.
*/
public enum Type {
/**
* Show an OK button.
*/
OK,
/**
* Show both OK and Cancel buttons.
*/
OKCANCEL,
/**
* Show both Yes and No buttons.
*/
YESNO,
/**
* Show Yes, No, and Cancel buttons.
*/
YESNOCANCEL
};
/**
* Message boxes have these possible results.
*/
public enum Result {
/**
* User clicked "OK".
*/
OK,
/**
* User clicked "Cancel".
*/
CANCEL,
/**
* User clicked "Yes".
*/
YES,
/**
* User clicked "No".
*/
NO
};
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
// ------------------------------------------------------------------------
/**
* The type of this message box.
*/
private Type type;
/**
* My buttons.
*/
private List buttons;
/**
* Which button was clicked: OK, CANCEL, YES, or NO.
*/
private Result result = Result.OK;
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Public constructor. The message box will be centered on screen.
*
* @param application TApplication that manages this window
* @param title window title, will be centered along the top border
* @param caption message to display. Use embedded newlines to get a
* multi-line box.
*/
public TMessageBox(final TApplication application, final String title,
final String caption) {
this(application, title, caption, Type.OK, true);
}
/**
* Public constructor. The message box will be centered on screen.
*
* @param application TApplication that manages this window
* @param title window title, will be centered along the top border
* @param caption message to display. Use embedded newlines to get a
* multi-line box.
* @param type one of the Type constants. Default is Type.OK.
*/
public TMessageBox(final TApplication application, final String title,
final String caption, final Type type) {
this(application, title, caption, type, true);
}
/**
* Public constructor. The message box will be centered on screen.
*
* @param application TApplication that manages this window
* @param title window title, will be centered along the top border
* @param caption message to display. Use embedded newlines to get a
* multi-line box.
* @param type one of the Type constants. Default is Type.OK.
* @param yield if true, yield this Thread. Subclasses need to set this
* to false and yield at their end of their constructor intead.
*/
protected TMessageBox(final TApplication application, final String title,
final String caption, final Type type, final boolean yield) {
// Start as 100x100 at (1, 1). These will be changed later.
super(application, title, 1, 1, 100, 100, CENTERED | MODAL);
// Hang onto type so that we can provide more convenience in
// onKeypress().
this.type = type;
// Determine width and height
String [] lines = caption.split("\n");
int width = StringUtils.width(title) + 12;
setHeight(6 + lines.length);
for (String line: lines) {
if (StringUtils.width(line) + 4 > width) {
width = StringUtils.width(line) + 4;
}
}
setWidth(width);
if (getWidth() > getScreen().getWidth()) {
setWidth(getScreen().getWidth());
}
// Re-center window to get an appropriate (x, y)
center();
// Now add my elements
int lineI = 1;
for (String line: lines) {
addLabel(line, 1, lineI, "twindow.background.modal");
lineI++;
}
// The button line
lineI++;
buttons = new ArrayList();
int buttonX = 0;
// Setup button actions
switch (type) {
case OK:
result = Result.OK;
if (getWidth() < 15) {
setWidth(15);
}
buttonX = (getWidth() - 11) / 2;
buttons.add(addButton(i18n.getString("okButton"), buttonX, lineI,
new TAction() {
public void DO() {
result = Result.OK;
getApplication().closeWindow(TMessageBox.this);
}
}
)
);
break;
case OKCANCEL:
result = Result.CANCEL;
if (getWidth() < 26) {
setWidth(26);
}
buttonX = (getWidth() - 22) / 2;
buttons.add(addButton(i18n.getString("okButton"), buttonX, lineI,
new TAction() {
public void DO() {
result = Result.OK;
getApplication().closeWindow(TMessageBox.this);
}
}
)
);
buttonX += 8 + 4;
buttons.add(addButton(i18n.getString("cancelButton"), buttonX, lineI,
new TAction() {
public void DO() {
result = Result.CANCEL;
getApplication().closeWindow(TMessageBox.this);
}
}
)
);
break;
case YESNO:
result = Result.NO;
if (getWidth() < 20) {
setWidth(20);
}
buttonX = (getWidth() - 16) / 2;
buttons.add(addButton(i18n.getString("yesButton"), buttonX, lineI,
new TAction() {
public void DO() {
result = Result.YES;
getApplication().closeWindow(TMessageBox.this);
}
}
)
);
buttonX += 5 + 4;
buttons.add(addButton(i18n.getString("noButton"), buttonX, lineI,
new TAction() {
public void DO() {
result = Result.NO;
getApplication().closeWindow(TMessageBox.this);
}
}
)
);
break;
case YESNOCANCEL:
result = Result.CANCEL;
if (getWidth() < 31) {
setWidth(31);
}
buttonX = (getWidth() - 27) / 2;
buttons.add(addButton(i18n.getString("yesButton"), buttonX, lineI,
new TAction() {
public void DO() {
result = Result.YES;
getApplication().closeWindow(TMessageBox.this);
}
}
)
);
buttonX += 5 + 4;
buttons.add(addButton(i18n.getString("noButton"), buttonX, lineI,
new TAction() {
public void DO() {
result = Result.NO;
getApplication().closeWindow(TMessageBox.this);
}
}
)
);
buttonX += 4 + 4;
buttons.add(addButton(i18n.getString("cancelButton"), buttonX,
lineI,
new TAction() {
public void DO() {
result = Result.CANCEL;
getApplication().closeWindow(TMessageBox.this);
}
}
)
);
break;
default:
throw new IllegalArgumentException("Invalid message box type: " +
type);
}
if (yield) {
// Set the secondaryThread to run me
getApplication().enableSecondaryEventReceiver(this);
// Yield to the secondary thread. When I come back from the
// constructor response will already be set.
getApplication().yield();
}
}
// ------------------------------------------------------------------------
// TWindow ----------------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Handle keystrokes.
*
* @param keypress keystroke event
*/
@Override
public void onKeypress(final TKeypressEvent keypress) {
if (this instanceof TInputBox) {
super.onKeypress(keypress);
return;
}
// Some convenience for message boxes: Alt won't be needed for the
// buttons.
switch (type) {
case OK:
if (keypress.equals(kbO)) {
buttons.get(0).dispatch();
return;
}
break;
case OKCANCEL:
if (keypress.equals(kbO)) {
buttons.get(0).dispatch();
return;
} else if (keypress.equals(kbC)) {
buttons.get(1).dispatch();
return;
}
break;
case YESNO:
if (keypress.equals(kbY)) {
buttons.get(0).dispatch();
return;
} else if (keypress.equals(kbN)) {
buttons.get(1).dispatch();
return;
}
break;
case YESNOCANCEL:
if (keypress.equals(kbY)) {
buttons.get(0).dispatch();
return;
} else if (keypress.equals(kbN)) {
buttons.get(1).dispatch();
return;
} else if (keypress.equals(kbC)) {
buttons.get(2).dispatch();
return;
}
break;
default:
throw new IllegalArgumentException("Invalid message box type: " +
type);
}
super.onKeypress(keypress);
}
// ------------------------------------------------------------------------
// TMessageBox ------------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Get the result.
*
* @return the result: OK, CANCEL, YES, or NO.
*/
public final Result getResult() {
return result;
}
/**
* See if the user clicked YES.
*
* @return true if the user clicked YES
*/
public final boolean isYes() {
return (result == Result.YES);
}
/**
* See if the user clicked NO.
*
* @return true if the user clicked NO
*/
public final boolean isNo() {
return (result == Result.NO);
}
/**
* See if the user clicked OK.
*
* @return true if the user clicked OK
*/
public final boolean isOk() {
return (result == Result.OK);
}
/**
* See if the user clicked CANCEL.
*
* @return true if the user clicked CANCEL
*/
public final boolean isCancel() {
return (result == Result.CANCEL);
}
}