diff --git a/Ghidra/Features/Base/src/test.slow/java/docking/widgets/dialogs/AbstractNumberInputDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/dialogs/AbstractNumberInputDialogTest.java new file mode 100644 index 0000000000..3bcea566e1 --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/dialogs/AbstractNumberInputDialogTest.java @@ -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); + } +} diff --git a/Ghidra/Features/Base/src/test.slow/java/docking/widgets/dialogs/BigIntegerNumberInputDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/dialogs/BigIntegerNumberInputDialogTest.java new file mode 100644 index 0000000000..102be2febf --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/dialogs/BigIntegerNumberInputDialogTest.java @@ -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(); + } +} diff --git a/Ghidra/Features/Base/src/test.slow/java/docking/widgets/dialogs/NumberInputDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/dialogs/NumberInputDialogTest.java index bb5b2cbc7d..86636a1269 100644 --- a/Ghidra/Features/Base/src/test.slow/java/docking/widgets/dialogs/NumberInputDialogTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/dialogs/NumberInputDialogTest.java @@ -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) 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. + * + *

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(); } } diff --git a/Ghidra/Features/BytePatterns/src/main/java/ghidra/bitpatterns/gui/InstructionSequenceTreePanelBuilder.java b/Ghidra/Features/BytePatterns/src/main/java/ghidra/bitpatterns/gui/InstructionSequenceTreePanelBuilder.java index 17417b9fad..e1490f4b4d 100644 --- a/Ghidra/Features/BytePatterns/src/main/java/ghidra/bitpatterns/gui/InstructionSequenceTreePanelBuilder.java +++ b/Ghidra/Features/BytePatterns/src/main/java/ghidra/bitpatterns/gui/InstructionSequenceTreePanelBuilder.java @@ -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() { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/AbstractNumberInputDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/AbstractNumberInputDialog.java new file mode 100644 index 0000000000..3255482526 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/AbstractNumberInputDialog.java @@ -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 Main Panel + */ + 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(); + }); + + } + + /** + * show 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); + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/BigIntegerNumberInputDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/BigIntegerNumberInputDialog.java new file mode 100644 index 0000000000..695864ec4c --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/BigIntegerNumberInputDialog.java @@ -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; + +/** + *

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.

+ * + *

Note: if you intend to only work with number values less than {@link Integer#MAX_VALUE}, + * then you should use the {@link NumberInputDialog}. + * + *

If an initial value is specified it is not in the range of min,max, it will be set to the min.

+ * + *

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.

+ * + *

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.

+ *
To display the dialog call: + *
+ * 
+ *     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();
+ *     }
+ * 
+ * 
+ */ +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(); + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/NumberInputDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/NumberInputDialog.java index 3a2ccd91fa..0409f5285a 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/NumberInputDialog.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/NumberInputDialog.java @@ -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; - /** *

DialogComponentProvider that provides information to create a modal dialog * to prompt for a number (int) to be input by the user.

* - *

If an initial value is specified it is not in the range of min,max, it will be set to the min.

+ *

If an initial value is specified it is not in the range of min,max, it will be set to the + * min.

* *

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; * * */ -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 Main Panel - */ - 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(); - }); - - } - - /** - * show 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 + ") : "; - } - } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/IntegerTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/IntegerTextField.java index eb44b42c7c..d7686304b1 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/IntegerTextField.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/IntegerTextField.java @@ -161,14 +161,15 @@ public class IntegerTextField { *

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 { *

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(); } /**