GT-3607 - Added a version of the Number Input dialog that allows for

numbers larger than int
This commit is contained in:
dragonmacher
2020-03-18 13:25:12 -04:00
parent 01539bedd6
commit 378c9a5ae0
8 changed files with 791 additions and 507 deletions
@@ -0,0 +1,76 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.dialogs;
import javax.swing.JButton;
import javax.swing.JTextField;
import org.junit.After;
import docking.DockingWindowManager;
import docking.test.AbstractDockingTest;
import docking.widgets.textfield.IntegerTextField;
public abstract class AbstractNumberInputDialogTest extends AbstractDockingTest {
protected AbstractNumberInputDialog dialog;
protected JButton okButton;
protected JTextField textField;
@After
public void tearDown() throws Exception {
if (dialog != null) {
runSwing(() -> dialog.close());
}
}
protected void createAndShowDialog(int initialValue, int min, int max) {
dialog = new NumberInputDialog(null, initialValue, min, max);
showDialogOnSwingWithoutBlocking(dialog);
okButton = (JButton) getInstanceField("okButton", dialog);
textField = getTextFieldForDialog(dialog);
}
protected void createAndShowDialog(int initial, int min) {
dialog = new NumberInputDialog(null, initial, min);
showDialogOnSwingWithoutBlocking(dialog);
okButton = (JButton) getInstanceField("okButton", dialog);
textField = getTextFieldForDialog(dialog);
}
protected void oK() {
runSwing(() -> okButton.doClick());
}
protected void setText(String value) {
setText(textField, value);
}
protected void showDialogOnSwingWithoutBlocking(AbstractNumberInputDialog theDialog) {
runSwing(() -> {
DockingWindowManager.showDialog(theDialog);
}, false);
waitForDialogComponent(AbstractNumberInputDialog.class);
}
protected JTextField getTextFieldForDialog(AbstractNumberInputDialog theDialog) {
IntegerTextField inputField = theDialog.getNumberInputField();
return (JTextField) getInstanceField("textField", inputField);
}
}
@@ -0,0 +1,241 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.dialogs;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.math.BigInteger;
import javax.swing.JButton;
import org.junit.Test;
public class BigIntegerNumberInputDialogTest extends AbstractNumberInputDialogTest {
@Test
public void testOkWithInitialValue() throws Exception {
int initial = 2;
int min = 2;
int max = 5;
createAndShowDialog(initial, min, max);
oK();
assertFalse("The dialog is open after pressing 'OK' with valid value", dialog.isVisible());
assertEquals("The returned value is not the expected value", 2, getValue());
}
@Test
public void testOkWithNewAllowedValue() throws Exception {
int initial = 2;
int min = 2;
int max = 5;
createAndShowDialog(initial, min, max);
setText("4");
oK();
assertFalse("The dialog is open after pressing 'OK' with valid value", dialog.isVisible());
assertEquals("The returned value is not the entered value", 4, getValue());
}
@Test
public void testTypingInHigherThanAllowed() {
int initial = 2;
int min = 2;
int max = 5;
createAndShowDialog(initial, min, max);
setText("7");
assertTrue(!okButton.isEnabled());
assertEquals("Value must be between 2 and 5", dialog.getStatusText());
}
@Test
public void testTypingInLowerThanAllowed() {
int initial = 2;
int min = 2;
int max = 5;
createAndShowDialog(initial, min, max);
setText("1");
assertTrue(!okButton.isEnabled());
assertEquals("Value must be between 2 and 5", dialog.getStatusText());
}
@Test
public void testTypingValidHex() {
int initial = 2;
int min = 2;
int max = 5;
createAndShowDialog(initial, min, max);
setText("0x4");
oK();
assertTrue("The dialog is open after pressing 'OK' with a valid hex value",
!dialog.isVisible());
assertEquals("The returned value is not the entered value", 4, getValue());
}
@Test
public void testTypeIntTooBigWithOverflow() {
int initial = 2;
createAndShowDialog(initial, 0, Integer.MAX_VALUE);
String okInt = "500000000";
setText(okInt);
assertTrue(okButton.isEnabled());
setText(okInt + "0");
assertEquals("Value must be between 0 and " + Integer.MAX_VALUE, dialog.getStatusText());
setText(okInt + "00");
assertEquals("Value must be between 0 and " + Integer.MAX_VALUE, dialog.getStatusText());
setText(okInt + "000");
assertEquals("Value must be between 0 and " + Integer.MAX_VALUE, dialog.getStatusText());
}
@Test
public void testTypeHexTooBig() {
int initial = 2;
int min = 2;
int max = 5;
createAndShowDialog(initial, min, max);
setText("0x7");
assertTrue(!okButton.isEnabled());
assertEquals("Value must be between 2 and 5", dialog.getStatusText());
}
@Test
public void testTypeLargeHexValue() {
int initial = 2;
int min = 2;
int max = Integer.MAX_VALUE;
createAndShowDialog(initial, min, max);
setText("0xfff");
oK();
assertFalse("The dialog is open after pressing 'OK' with valid value", dialog.isVisible());
assertEquals("The returned value is not the entered value", 4095, getValue());
}
@Test
public void testTypingNegativeValidNumber() {
int initial = 2;
int min = -5;
int max = 10;
createAndShowDialog(initial, min, max);
setText("-3");
oK();
assertFalse("The dialog is open after pressing 'OK' with valid value", dialog.isVisible());
assertEquals("The returned value is not the entered value", -3, getValue());
}
@Test
public void testTypingNegativeValidHexNumber() {
int initial = 2;
int min = -5;
int max = 10;
createAndShowDialog(initial, min, max);
setText("-0x3");
oK();
assertFalse("The dialog is open after pressing 'OK' with valid value", dialog.isVisible());
assertEquals("The returned value is not the entered value", -3, getValue());
}
@Test
public void testSettingNoMaximum() {
int initial = 1;
int min = 1;
createAndShowDialog(initial, min);
int max = dialog.getMax();
assertThat(max, is(Integer.MAX_VALUE));
setText(Integer.toString(min + 1));
oK();
assertFalse("The dialog is open after pressing 'OK' with valid value", dialog.isVisible());
assertEquals("The returned value is not the entered value", (min + 1), getValue());
}
@Test
public void testBigInteger() {
createAndShowDialog(BigInteger.valueOf(0), new BigInteger("fffffffffffffffffffff", 16));
String okInt = "500000000";
setText(okInt);
assertTrue(okButton.isEnabled());
setText(okInt + "0");
assertTrue(okButton.isEnabled());
setText(okInt + "00");
assertTrue(okButton.isEnabled());
setText(okInt + "000");
assertTrue(okButton.isEnabled());
oK();
assertEquals(new BigInteger(okInt + "000"), dialog.getBigIntegerValue());
}
private void createAndShowDialog(BigInteger min, BigInteger max) {
dialog = new BigIntegerNumberInputDialog("Title", null, null, min, max, false);
showDialogOnSwingWithoutBlocking(dialog);
okButton = (JButton) getInstanceField("okButton", dialog);
textField = getTextFieldForDialog(dialog);
}
private int getValue() {
return dialog.getIntValue();
}
}
@@ -15,251 +15,42 @@
*/
package docking.widgets.dialogs;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*;
import java.awt.Image;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JTextField;
import org.junit.After;
import org.junit.Test;
import docking.DockingWindowManager;
import docking.test.AbstractDockingTest;
import docking.widgets.textfield.IntegerTextField;
import ghidra.test.DummyTool;
public class NumberInputDialogTest extends AbstractDockingTest {
private DockingWindowManager dwm =
new DockingWindowManager(new DummyTool(), (List<Image>) null);
private NumberInputDialog dialog;
private JButton okButton;
private JTextField textField;
@After
public void tearDown() throws Exception {
if (dialog != null) {
runSwing(() -> dialog.close());
}
}
/**
* {@link NumberInputDialog} test.
*
* <p>Note: most of the tests for the base class are in {@link BigIntegerNumberInputDialogTest}.
*/
public class NumberInputDialogTest extends AbstractNumberInputDialogTest {
@Test
public void testOkWithInitialValue() throws Exception {
public void testTypeIntTooBigWithOverflow() {
int initial = 2;
int min = 2;
int max = 5;
createAndShowDialog(initial, min, max);
createAndShowDialog(initial, 0, Integer.MAX_VALUE);
clickOK();
String okInt = "500000000";
setText(okInt);
assertTrue(okButton.isEnabled());
assertFalse("The dialog is open after pressing 'OK' with valid value", dialog.isVisible());
setText(okInt + "0");
assertEquals("Value must be between 0 and " + Integer.MAX_VALUE, dialog.getStatusText());
assertEquals("The returned value is not the expected value", 2, dialog.getValue());
setText(okInt + "00");
assertEquals("Value must be between 0 and " + Integer.MAX_VALUE, dialog.getStatusText());
setText(okInt + "000");
assertEquals("Value must be between 0 and " + Integer.MAX_VALUE, dialog.getStatusText());
int valid = Integer.MAX_VALUE - 1;
setText(Integer.toString(valid));
oK();
assertEquals(valid, getValue());
}
@Test
public void testOkWithNewAllowedValue() throws Exception {
int initial = 2;
int min = 2;
int max = 5;
createAndShowDialog(initial, min, max);
setText("4");
clickOK();
assertFalse("The dialog is open after pressing 'OK' with valid value", dialog.isVisible());
assertEquals("The returned value is not the entered value", 4, dialog.getValue());
}
@Test
public void testTypingInHigherThanAllowed() {
int initial = 2;
int min = 2;
int max = 5;
createAndShowDialog(initial, min, max);
setText("7");
assertTrue(!okButton.isEnabled());
assertEquals("Value must be between 2 and 5", dialog.getStatusText());
}
@Test
public void testTypingInLowerThanAllowed() {
int initial = 2;
int min = 2;
int max = 5;
createAndShowDialog(initial, min, max);
setText("1");
assertTrue(!okButton.isEnabled());
assertEquals("Value must be between 2 and 5", dialog.getStatusText());
}
@Test
public void testTypingValidHex() {
int initial = 2;
int min = 2;
int max = 5;
createAndShowDialog(initial, min, max);
setText("0x4");
clickOK();
assertTrue("The dialog is open after pressing 'OK' with a valid hex value",
!dialog.isVisible());
assertEquals("The returned value is not the entered value", 4, dialog.getValue());
}
@Test
public void testTypeHexTooBig() {
int initial = 2;
int min = 2;
int max = 5;
createAndShowDialog(initial, min, max);
setText("0x7");
assertTrue(!okButton.isEnabled());
assertEquals("Value must be between 2 and 5", dialog.getStatusText());
}
@Test
public void testTypeLargeHexValue() {
int initial = 2;
int min = 2;
int max = Integer.MAX_VALUE;
createAndShowDialog(initial, min, max);
setText("0xfff");
clickOK();
assertFalse("The dialog is open after pressing 'OK' with valid value", dialog.isVisible());
assertEquals("The returned value is not the entered value", 4095, dialog.getValue());
}
@Test
public void testTypingNegativeValidNumber() {
int initial = 2;
int min = -5;
int max = 10;
createAndShowDialog(initial, min, max);
setText("-3");
clickOK();
assertFalse("The dialog is open after pressing 'OK' with valid value", dialog.isVisible());
assertEquals("The returned value is not the entered value", -3, dialog.getValue());
}
@Test
public void testTypingNegativeValidHexNumber() {
int initial = 2;
int min = -5;
int max = 10;
createAndShowDialog(initial, min, max);
setText("-0x3");
clickOK();
assertFalse("The dialog is open after pressing 'OK' with valid value", dialog.isVisible());
assertEquals("The returned value is not the entered value", -3, dialog.getValue());
}
@Test
public void testSettingNoMaximum() {
int initial = 1;
int min = 1;
createAndShowDialog(initial, min);
int max = dialog.getMax();
assertThat(max, is(Integer.MAX_VALUE));
setText(Integer.toString(min + 1));
clickOK();
assertFalse("The dialog is open after pressing 'OK' with valid value", dialog.isVisible());
assertEquals("The returned value is not the entered value", (min + 1), dialog.getValue());
}
@Test
public void testSettingInvalidMaximum() {
int initial = 1;
int min = 2;
int max = min - 1;
try {
new NumberInputDialog(null, initial, min, max);
fail("Expected an exception with a max lower than min");
}
catch (IllegalArgumentException e) {
// good
}
}
private void createAndShowDialog(int initialValue, int min, int max) {
dialog = new NumberInputDialog(null, initialValue, min, max);
showDialogOnSwingWithoutBlocking(dialog);
okButton = (JButton) getInstanceField("okButton", dialog);
textField = getTextFieldForDialog(dialog);
}
private void createAndShowDialog(int initial, int min) {
dialog = new NumberInputDialog(null, initial, min);
showDialogOnSwingWithoutBlocking(dialog);
okButton = (JButton) getInstanceField("okButton", dialog);
textField = getTextFieldForDialog(dialog);
}
private void clickOK() {
runSwing(() -> okButton.doClick());
}
private void setText(String value) {
triggerText(textField, value);
}
private void showDialogOnSwingWithoutBlocking(NumberInputDialog theDialog) {
runSwing(() -> {
dwm.showDialog(theDialog);
theDialog.getValue();
}, false);
waitForDialogComponent(null, NumberInputDialog.class, DEFAULT_WINDOW_TIMEOUT);
}
private JTextField getTextFieldForDialog(NumberInputDialog theDialog) {
IntegerTextField inputField = theDialog.getNumberInputField();
return (JTextField) getInstanceField("textField", inputField);
private int getValue() {
return ((NumberInputDialog) dialog).getValue();
}
}
@@ -91,12 +91,13 @@ public class InstructionSequenceTreePanelBuilder extends ContextRegisterFilterab
applyPercentageFilterButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
NumberInputDialog percentageFilterCreater =
new NumberInputDialog(PERCENTAGE_FILTER_TITLE, null, DEFAULT_PERCENTAGE_FILTER);
percentageFilterCreater.show();
NumberInputDialog numberDialog =
new NumberInputDialog(PERCENTAGE_FILTER_TITLE, DEFAULT_PERCENTAGE_FILTER, 0,
100);
numberDialog.show();
double value = 0.0;
if (!percentageFilterCreater.wasCancelled()) {
value = percentageFilterCreater.getValue();
if (!numberDialog.wasCancelled()) {
value = numberDialog.getValue();
}
percentageFilter = new PercentageFilter(value);
applyFilterAction();
@@ -192,7 +193,7 @@ public class InstructionSequenceTreePanelBuilder extends ContextRegisterFilterab
}
/**
* Returns the selection path of the {@link FunctionBitPatternGTree} associated with this panel.
* Returns the selection path of the {@link FunctionBitPatternsGTree} associated with this panel.
* @return the selection path
*/
public TreePath getSelectionPath() {
@@ -0,0 +1,335 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.dialogs;
import java.awt.BorderLayout;
import java.math.BigInteger;
import javax.swing.*;
import docking.DialogComponentProvider;
import docking.DockingWindowManager;
import docking.widgets.label.GDLabel;
import docking.widgets.textfield.IntegerTextField;
import ghidra.util.Swing;
/**
* A base class for prompting users to enter a number into this dialog
*/
public abstract class AbstractNumberInputDialog extends DialogComponentProvider {
protected boolean wasCancelled = false;
protected IntegerTextField numberInputField;
protected BigInteger min;
protected BigInteger max;
protected JLabel label;
protected String defaultMessage;
/**
* Show a number input dialog
* @param title The title of the dialog
* @param prompt the prompt to display before the number input field
* @param initialValue the default value to display, null will leave the field blank
* @param min the minimum allowed value of the field
* @param max the maximum allowed value of the field
* @param showAsHex if true, the initial value will be displayed as hex
*/
public AbstractNumberInputDialog(String title, String prompt, Integer initialValue, int min,
int max,
boolean showAsHex) {
this(title, prompt, toBig(initialValue), toBig(min), toBig(max), showAsHex);
}
/**
* Show a number input dialog
* @param title The title of the dialog
* @param prompt the prompt to display before the number input field
* @param initialValue the default value to display, null will leave the field blank
* @param min the minimum allowed value of the field
* @param max the maximum allowed value of the field
* @param showAsHex if true, the initial value will be displayed as hex
*/
public AbstractNumberInputDialog(String title, String prompt, BigInteger initialValue,
BigInteger min,
BigInteger max,
boolean showAsHex) {
super(title, true, true, true, false);
this.min = min;
if (max.compareTo(min) < 0) {
throw new IllegalArgumentException(
"'min' cannot be less than 'max'. 'min' = " + min + ", 'max' = " + max);
}
this.max = max;
addWorkPanel(buildMainPanel(prompt, showAsHex));
addOKButton();
addCancelButton();
setRememberLocation(false);
setRememberSize(false);
initializeDefaultValue(initialValue);
selectAndFocusText();
}
private static String nonNull(String s) {
if (s == null) {
return "items";
}
return s;
}
/**
* Define the Main panel for the dialog here
* @param prompt the prompt label text
* @param showAsHex if true, show the value as hex
* @return JPanel the completed <CODE>Main Panel</CODE>
*/
protected JPanel buildMainPanel(String prompt, boolean showAsHex) {
JPanel panel = createPanel(prompt);
numberInputField.addActionListener(e -> okCallback());
if (showAsHex) {
numberInputField.setHexMode();
}
if (min.compareTo(BigInteger.valueOf(0)) >= 0) {
numberInputField.setAllowNegativeValues(false);
}
return panel;
}
/**
* Gets called when the user clicks on the OK Action for the dialog.
*/
@Override
protected void okCallback() {
if (checkInput()) {
close();
}
}
/**
* Gets called when the user clicks on the Cancel Action for the dialog.
*/
@Override
protected void cancelCallback() {
wasCancelled = true;
close();
}
/**
* Return whether the user cancelled the input dialog
* @return true if cancelled
*/
public boolean wasCancelled() {
return wasCancelled;
}
/**
* Get the current input value
* @return the value
* @throws NumberFormatException if entered value cannot be parsed
* @throws IllegalStateException if the dialog was cancelled
*/
public BigInteger getBigIntegerValue() {
if (wasCancelled()) {
throw new IllegalStateException("User cancelled the dialog");
}
return numberInputField.getValue();
}
/**
* Get the current input value as a long
* @return the value
* @throws NumberFormatException if entered value cannot be parsed
* @throws IllegalStateException if the dialog was cancelled
* @throws ArithmeticException if the value in this field will not fit into a long
*/
public long getLongValue() {
if (wasCancelled()) {
throw new IllegalStateException("User cancelled the dialog");
}
return numberInputField.getLongValue();
}
/**
* Get the current input value as an int
* @return the value
* @throws NumberFormatException if entered value cannot be parsed
* @throws IllegalStateException if the dialog was cancelled
* @throws ArithmeticException if the value in this field will not fit into an int
*/
public int getIntValue() {
if (wasCancelled()) {
throw new IllegalStateException("User cancelled the dialog");
}
return numberInputField.getIntValue();
}
private void initializeDefaultValue(BigInteger initial) {
if (initial == null) {
return;
}
// Adjust the initial value if it is not valid
BigInteger value = initial;
if (initial.compareTo(min) < 0) {
value = min;
}
else if (initial.compareTo(max) > 0) {
value = max;
}
numberInputField.setValue(value);
}
private void selectAndFocusText() {
Swing.runLater(() -> {
numberInputField.requestFocus();
numberInputField.selectAll();
});
}
/**
* <code>show</code> displays the dialog, gets the user input
*
* @return false if the user cancelled the operation
*/
public boolean show() {
DockingWindowManager.showDialog(this);
return !wasCancelled;
}
/**
* Sets the value in the input field to the indicated value.
* @param value the value
*/
public void setInput(int value) {
numberInputField.setValue(value);
}
/**
* Sets the default message to be displayed when valid values are in the text fields.
* @param defaultMessage the message to be displayed when valid values are in the text fields.
*/
public void setDefaultMessage(String defaultMessage) {
this.defaultMessage = defaultMessage;
setStatusText(defaultMessage);
}
/**
* Return the minimum acceptable value.
* @return the min
*/
public int getMin() {
return min.intValue();
}
/**
* Return the maximum acceptable value.
* @return the max
*/
public int getMax() {
return max.intValue();
}
//==================================================================================================
// Test Methods
//==================================================================================================
IntegerTextField getNumberInputField() {
return numberInputField;
}
//==================================================================================================
// Private Methods
//==================================================================================================
/**
* Create the main panel.
*/
private JPanel createPanel(String prompt) {
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
label = new GDLabel(prompt);
numberInputField = new IntegerTextField(12);
numberInputField.addChangeListener(e -> updateOKButtonEnablement());
// Actually assemble the parts into a status panel.
panel.add(label, BorderLayout.WEST);
panel.add(numberInputField.getComponent(), BorderLayout.CENTER);
return panel;
}
protected void updateOKButtonEnablement() {
clearStatusText();
BigInteger value = numberInputField.getValue();
if (value == null) {
setOkEnabled(false);
if (defaultMessage != null) {
setStatusText(defaultMessage);
}
else {
setStatusText("Enter a value between " + min + " and " + max);
}
return;
}
setOkEnabled(checkInput());
}
/**
* Check the entry;
*
* @return boolean true if input is OK
*/
private boolean checkInput() {
BigInteger value = numberInputField.getValue();
if (value.compareTo(min) >= 0 && value.compareTo(max) <= 0) {
if (defaultMessage != null) {
setStatusText(defaultMessage);
}
return true;
}
setStatusText("Value must be between " + min + " and " + max);
return false;
}
protected static String buildDefaultPrompt(String entryType, int min, int max) {
String type = nonNull(entryType);
if (min == 0 && max == Integer.MAX_VALUE) {
// full range
return "Enter number of " + type + ": ";
}
else if (max == Integer.MAX_VALUE) {
return "Enter number of " + type + " (minimum is " + min + ") : ";
}
else {
return "Enter number of " + type + " (" + min + ", " + max + ") : ";
}
}
protected static BigInteger toBig(Integer i) {
if (i == null) {
return null;
}
return BigInteger.valueOf(i);
}
}
@@ -0,0 +1,72 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.widgets.dialogs;
import java.math.BigInteger;
/**
* <P>DialogComponentProvider that provides information to create a modal dialog
* to prompt for a number larger than an {@code int} or {@code long} to be input by the user.</P>
*
* <P>Note: if you intend to only work with number values less than {@link Integer#MAX_VALUE},
* then you should use the {@link NumberInputDialog}.
*
* <P>If an initial value is specified it is not in the range of min,max, it will be set to the min.</P>
*
* <P>If the maximum value indicated is less than the minimum then the max
* is the largest positive integer. Otherwise the maximum valid value is
* as indicated.</P>
*
* <P>This dialog component provider class can be used by various classes and
* therefore should not have its size or position remembered by the
* tool.showDialog() call parameters.</P>
* <br>To display the dialog call:
* <pre>
* <code>
* String entryType = "items";
* BigInteger initial = 5; // initial value in text field
* BigInteger min = BigInteger.valueOf(1); // minimum valid value in text field
* BigInteger max = BigInteger.valueOf(10); // maximum valid value in text field
*
* BigIntegerNumberInputDialog provider =
* new BigIntegerNumberInputDialog("Title", entryType, initial, min, max);
* if (numInputProvider.show()) {
* // not cancelled
* BigInteger result = provider.getValue();
* long longResult = provider.getLongValue();
* }
* </code>
* </pre>
*/
public class BigIntegerNumberInputDialog extends AbstractNumberInputDialog {
public BigIntegerNumberInputDialog(String title, String prompt, BigInteger initialValue,
BigInteger min,
BigInteger max,
boolean showAsHex) {
super(title, prompt, initialValue, min, max, showAsHex);
}
/**
* Get the current input value
* @return the value
* @throws NumberFormatException if entered value cannot be parsed
* @throws IllegalStateException if the dialog was cancelled
*/
public BigInteger getValue() {
return getBigIntegerValue();
}
}
@@ -15,22 +15,12 @@
*/
package docking.widgets.dialogs;
import java.awt.BorderLayout;
import java.awt.Component;
import java.math.BigInteger;
import javax.swing.*;
import docking.DialogComponentProvider;
import docking.DockingWindowManager;
import docking.widgets.label.GDLabel;
import docking.widgets.textfield.IntegerTextField;
/**
* <P>DialogComponentProvider that provides information to create a modal dialog
* to prompt for a number (int) to be input by the user.</P>
*
* <P>If an initial value is specified it is not in the range of min,max, it will be set to the min.</P>
* <P>If an initial value is specified it is not in the range of min,max, it will be set to the
* min.</P>
*
* <P>If the maximum value indicated is less than the minimum then the max
* is the largest positive integer. Otherwise the maximum valid value is
@@ -55,280 +45,56 @@ import docking.widgets.textfield.IntegerTextField;
* </code>
* </pre>
*/
public class NumberInputDialog extends DialogComponentProvider {
private boolean wasCancelled = false;
private IntegerTextField numberInputField;
private int min;
private int max;
private JLabel label;
private String defaultMessage;
public class NumberInputDialog extends AbstractNumberInputDialog {
/**
* Constructs a new NumberInputDialog
*
* @param entryType item type the number indicates
* (i.e. "duplicates", "items", or "elements")
* @param initial default value displayed in the text field
* @param min minimum value allowed
*/
public NumberInputDialog(String entryType, int initial, int min) {
this("Enter Number", buildDefaultPrompt(entryType, min, min - 1), initial, min,
Integer.MAX_VALUE, false);
}
/**
* Constructs a new NumberInputDialog.
* Constructs a new NumberInputDialog
*
* @param entryType item type the number indicates
* (i.e. "duplicates", "items", or "elements").
* @param initial default value displayed in the text field.
* @param min minimum value allowed.
* @param max maximum value allowed.
* (i.e. "duplicates", "items", or "elements")
* @param initial default value displayed in the text field
* @param min minimum value allowed
* @param max maximum value allowed
*/
public NumberInputDialog(String entryType, int initial, int min, int max) {
this("Enter Number", buildDefaultPrompt(entryType, min, max), initial, min, max, false);
}
/**
* Create a numberInputDialog where the the min is 0 and the max is INTEGER.MAX_VALUE
* @param title the title of the dialog
* @param prompt the prompt in the dialog
* @param initialValue the initial value. If null, the text input will be blank.
*/
public NumberInputDialog(String title, String prompt, Integer initialValue) {
this(title, prompt, initialValue, 0, Integer.MAX_VALUE, false);
}
/**
* Show a number input dialog.
* @param title The title of the dialog.
* @param prompt the prompt to display before the number input field.
* @param initialValue the default value to display, null will leave the field blank.
* @param min the minimum allowed value of the field.
* @param max the maximum allowed value of the field.
* @param showAsHex if true, the initial value will be displayed as hex.
* Show a number input dialog
* @param title The title of the dialog
* @param prompt the prompt to display before the number input field
* @param initialValue the default value to display, null will leave the field blank
* @param min the minimum allowed value of the field
* @param max the maximum allowed value of the field
* @param showAsHex if true, the initial value will be displayed as hex
*/
public NumberInputDialog(String title, String prompt, Integer initialValue, int min, int max,
boolean showAsHex) {
super(title, true, true, true, false);
this.min = min;
if (max < min) {
throw new IllegalArgumentException(
"'min' cannot be less than 'max'. 'min' = " + min + ", 'max' = " + max);
}
this.max = max;
addWorkPanel(buildMainPanel(prompt, showAsHex));
addOKButton();
addCancelButton();
setRememberLocation(false);
setRememberSize(false);
initializeDefaultValue(initialValue);
selectAndFocusText();
}
private static String nonNull(String s) {
if (s == null) {
return "items";
}
return s;
super(title, prompt, initialValue, min, max, showAsHex);
}
/**
* Define the Main panel for the dialog here.
* @param showAsHex
* @return JPanel the completed <CODE>Main Panel</CODE>
*/
protected JPanel buildMainPanel(String prompt, boolean showAsHex) {
JPanel panel = createPanel(prompt);
numberInputField.addActionListener(e -> okCallback());
if (showAsHex) {
numberInputField.setHexMode();
}
if (min >= 0) {
numberInputField.setAllowNegativeValues(false);
}
return panel;
}
/**
* Gets called when the user clicks on the OK Action for the dialog.
*/
@Override
protected void okCallback() {
if (checkInput()) {
close();
}
}
/**
* Gets called when the user clicks on the Cancel Action for the dialog.
*/
@Override
protected void cancelCallback() {
wasCancelled = true;
close();
}
/**
* Return whether the user cancelled the input dialog.
*/
public boolean wasCancelled() {
return wasCancelled;
}
private void initializeDefaultValue(Integer initial) {
if (initial == null) {
return;
}
int value = initial.intValue();
// Adjust the initial value if it is not valid.
if (value < min) {
value = min;
}
else if (value > max) {
value = max;
}
numberInputField.setValue(value);
}
private void selectAndFocusText() {
SwingUtilities.invokeLater(() -> {
numberInputField.requestFocus();
numberInputField.selectAll();
});
}
/**
* <code>show</code> displays the dialog, gets the user input
*
* @return false if the user cancelled the operation
*/
public boolean show() {
Component parent = DockingWindowManager.getActiveInstance().getActiveComponent();
DockingWindowManager.showDialog(parent, this);
return !wasCancelled;
}
/**
* Convert the input to an int value.
* @throws NumberFormatException if entered value cannot be parsed.
* Convert the input to an int value
* @return the int value
* @throws NumberFormatException if entered value cannot be parsed
* @throws IllegalStateException if the dialog was cancelled
*/
public int getValue() {
if (wasCancelled()) {
throw new IllegalStateException();
}
return numberInputField.getIntValue();
return getIntValue();
}
/**
* Sets the value in the input field to the indicated value.
*/
public void setInput(int value) {
numberInputField.setValue(value);
}
/**
* Sets the default message to be displayed when valid values are in the text fields.
* @param defaultMessage the message to be displayed when valid values are in the text fields.
*/
public void setDefaultMessage(String defaultMessage) {
this.defaultMessage = defaultMessage;
setStatusText(defaultMessage);
}
/**
* Return the minimum acceptable value.
*/
public int getMin() {
return min;
}
/**
* Return the maximum acceptable value.
*/
public int getMax() {
return max;
}
//==================================================================================================
// Test Methods
//==================================================================================================
IntegerTextField getNumberInputField() {
return numberInputField;
}
//==================================================================================================
// Private Methods
//==================================================================================================
/**
* Create the main panel.
*/
private JPanel createPanel(String prompt) {
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
label = new GDLabel(prompt);
numberInputField = new IntegerTextField(12);
numberInputField.addChangeListener(e -> updateOKButtonEnablement());
// Actually assemble the parts into a status panel.
panel.add(label, BorderLayout.WEST);
panel.add(numberInputField.getComponent(), BorderLayout.CENTER);
return panel;
}
protected void updateOKButtonEnablement() {
clearStatusText();
BigInteger value = numberInputField.getValue();
if (value == null) {
setOkEnabled(false);
if (defaultMessage != null) {
setStatusText(defaultMessage);
}
else {
setStatusText("Enter a value between " + min + " and " + max);
}
return;
}
setOkEnabled(checkInput());
}
/**
* Check the entry;
*
* @return boolean true if input is OK
*/
private boolean checkInput() {
int value = numberInputField.getIntValue();
return checkDecimalRange(value);
}
private boolean checkDecimalRange(int decimalValue) {
if (decimalValue >= min && decimalValue <= max) {
if (defaultMessage != null) {
setStatusText(defaultMessage);
}
return true;
}
setStatusText("Value must be between " + min + " and " + max);
return false;
}
private static String buildDefaultPrompt(String entryType, int min, int max) {
String type = nonNull(entryType);
if (min == 0 && max == Integer.MAX_VALUE) {
// full range
return "Enter number of " + type + ": ";
}
else if (max == Integer.MAX_VALUE) {
return "Enter number of " + type + " (minimum is " + min + ") : ";
}
else {
return "Enter number of " + type + " (" + min + ", " + max + ") : ";
}
}
}
@@ -161,14 +161,15 @@ public class IntegerTextField {
* <P> If using this method, it is highly recommended that you set the max value to {@link Integer#MAX_VALUE}
* or lower.
*
* @return the current value as an int. Or 0 if there is no value.
* @return the current value as an int. Or 0 if there is no value
* @throws ArithmeticException if the value in this field will not fit into an int
*/
public int getIntValue() {
BigInteger currentValue = getValue();
if (currentValue == null) {
return 0;
}
return currentValue.intValue();
return currentValue.intValueExact();
}
/**
@@ -180,14 +181,15 @@ public class IntegerTextField {
* <P> If using this method, it is highly recommended that you set the max value to {@link Long#MAX_VALUE}
* or lower.
*
* @return the current value as a long. Or 0 if there is no value.
* @return the current value as a long. Or 0 if there is no value
* @throws ArithmeticException if the value in this field will not fit into a long
*/
public long getLongValue() {
BigInteger currentValue = getValue();
if (currentValue == null) {
return 0;
}
return currentValue.longValue();
return currentValue.longValueExact();
}
/**