From 220e9042d1d0e58e232061c3fb2bc4a9f9e09f42 Mon Sep 17 00:00:00 2001 From: ghidragon <106987263+ghidragon@users.noreply.github.com> Date: Mon, 20 Apr 2026 16:27:25 -0400 Subject: [PATCH] GP-3049 Improved Register Manager provider --- .../table/HexBigIntegerTableCellEditor.java | 9 +- .../FunctionStartRFParamsDialog.java | 3 +- .../gui/search/dialog/BSimSearchDialog.java | 3 +- .../help/topics/RegisterPlugin/Registers.htm | 17 +- .../images/ClearRegisterValues.png | Bin 10369 -> 8759 bytes .../images/EditRegisterValueRange.png | Bin 9984 -> 7401 bytes .../RegisterPlugin/images/RegisterManager.png | Bin 31633 -> 29940 bytes .../images/SetRegisterValues.png | Bin 11456 -> 9446 bytes .../core/data/AbstractSettingsDialog.java | 10 +- .../editor/VarnodeLocationCellEditor.java | 3 +- .../editor/VarnodeSizeCellEditor.java | 9 +- .../plugin/core/memory/AddBlockDialog.java | 14 +- .../register/EditRegisterValueDialog.java | 20 +- .../register/RegisterManagerProvider.java | 182 +++-- .../plugin/core/register/RegisterPlugin.java | 6 +- .../core/register/RegisterValuesPanel.java | 6 +- .../core/register/SetRegisterValueDialog.java | 143 ++-- .../plugin/core/select/SelectBlockDialog.java | 6 +- .../core/string/StringTableProvider.java | 3 +- .../java/ghidra/app/util/AddressInput.java | 45 +- .../util/importer/options/HexLongOption.java | 3 +- .../AddressFieldOptionsPropertyEditor.java | 5 +- .../field/ArrayElementPropertyEditor.java | 10 +- .../bytesource/ProgramByteSource.java | 2 +- .../dialogs/AbstractNumberInputDialog.java | 9 +- .../IntegerRangeConstraintEditor.java | 9 +- .../UnsignedLongConstraintEditor.java | 9 +- .../UnsignedLongRangeConstraintEditor.java | 9 +- .../textfield/FixedSizeIntegerTextField.java | 173 +++++ .../textfield/HexDecimalModeTextField.java | 114 --- .../widgets/textfield/IntegerTextField.java | 652 ++---------------- .../integer/AbstractIntegerTextField.java | 515 ++++++++++++++ .../textfield/integer/IntegerFormat.java | 107 +++ .../integer/MultiFormatTextField.java | 172 +++++ .../java/docking/widgets/values/IntValue.java | 9 +- .../docking/widgets/values/LongValue.java | 9 +- .../AbstractIntegerTextFieldTest.java | 115 +++ .../FixedSizeIntegerTextFieldTest.java | 433 ++++++++++++ .../textfield/IntegerTextFieldTest.java | 289 ++++---- 39 files changed, 2029 insertions(+), 1094 deletions(-) create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/FixedSizeIntegerTextField.java delete mode 100644 Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/HexDecimalModeTextField.java create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/integer/AbstractIntegerTextField.java create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/integer/IntegerFormat.java create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/integer/MultiFormatTextField.java create mode 100644 Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/textfield/AbstractIntegerTextFieldTest.java create mode 100644 Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/textfield/FixedSizeIntegerTextFieldTest.java diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/HexBigIntegerTableCellEditor.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/HexBigIntegerTableCellEditor.java index 1abfa51bf9..a5b685e578 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/HexBigIntegerTableCellEditor.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/HexBigIntegerTableCellEditor.java @@ -24,6 +24,7 @@ import javax.swing.*; import javax.swing.table.TableCellEditor; import docking.widgets.textfield.IntegerTextField; +import docking.widgets.textfield.integer.IntegerFormat; public class HexBigIntegerTableCellEditor extends AbstractCellEditor implements TableCellEditor { protected IntegerTextField input; @@ -48,11 +49,11 @@ public class HexBigIntegerTableCellEditor extends AbstractCellEditor implements int row, int column) { input = new IntegerTextField(); input.getComponent().setBorder(UIManager.getBorder("Table.focusCellHighlightBorder")); - input.setAllowNegativeValues(true); - input.setHexMode(); - input.setAllowsHexPrefix(false); + input.setMinValue(null); // allow negative numbers + input.setFormat(IntegerFormat.HEX); + input.setUseNumberPrefix(false); input.setShowNumberMode(true); - input.setHorizontalAlignment(JTextField.RIGHT); + input.setHorizontalAlignment(SwingConstants.RIGHT); if (value != null) { input.setValue((BigInteger) value); diff --git a/Ghidra/Extensions/MachineLearning/src/main/java/ghidra/machinelearning/functionfinding/FunctionStartRFParamsDialog.java b/Ghidra/Extensions/MachineLearning/src/main/java/ghidra/machinelearning/functionfinding/FunctionStartRFParamsDialog.java index dbba764f20..9ad48f2eb9 100644 --- a/Ghidra/Extensions/MachineLearning/src/main/java/ghidra/machinelearning/functionfinding/FunctionStartRFParamsDialog.java +++ b/Ghidra/Extensions/MachineLearning/src/main/java/ghidra/machinelearning/functionfinding/FunctionStartRFParamsDialog.java @@ -16,6 +16,7 @@ package ghidra.machinelearning.functionfinding; import java.awt.BorderLayout; +import java.math.BigInteger; import java.util.*; import java.util.stream.Collectors; import java.util.stream.LongStream; @@ -438,7 +439,7 @@ public class FunctionStartRFParamsDialog extends ReusableDialogComponentProvider minUndefRangeLabel.setToolTipText(MIN_UNDEFINED_RANGE_SIZE_TIP); funcDataPanel.add(minUndefRangeLabel); minUndefRangeField = new IntegerTextField(); - minUndefRangeField.setAllowNegativeValues(false); + minUndefRangeField.setMinValue(BigInteger.ZERO); minUndefRangeField.setValue(plugin.getMinUndefinedRangeSize()); funcDataPanel.add(minUndefRangeField.getComponent()); diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimSearchDialog.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimSearchDialog.java index ed17da28f2..89f1389d7f 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimSearchDialog.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimSearchDialog.java @@ -127,8 +127,7 @@ public class BSimSearchDialog extends AbstractBSimSearchDialog { maxResultsField = new IntegerTextField(10); maxResultsField.setValue(100); maxResultsField.setMinValue(BigInteger.ONE); - maxResultsField.setAllowNegativeValues(false); - maxResultsField.setAllowsHexPrefix(false); + maxResultsField.setUseNumberPrefix(false); maxResultsField.setShowNumberMode(false); JComponent maxResultsComponent = maxResultsField.getComponent(); diff --git a/Ghidra/Features/Base/src/main/help/help/topics/RegisterPlugin/Registers.htm b/Ghidra/Features/Base/src/main/help/help/topics/RegisterPlugin/Registers.htm index 2935b5eec7..9957f9c981 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/RegisterPlugin/Registers.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/RegisterPlugin/Registers.htm @@ -71,24 +71,27 @@
-

Tool Buttons

-
+

Toolbar Buttons

-
-

Toggles whether or not to select the row in the +

 Toggles whether or not to select the row in the currently selected register value table whose address range contains the current address of the cursor in the listing view. For example, in the Register Manager image show above, if the user clicks on any address between 804c12 and 804c24, then the first row of the table will be selected if this action toggle is on.

-

Deletes the register value associations for all the +

 Brings up the Add Register Value Range dialog for setting a + value over an address range for the selected register. See the + Edit Address Range section below for dialog details as this action shares the + same dialog as the edit range action.

+ +

 Deletes the register value associations for all the selected ranges in the table.

-

Creates a selection in the browser for all the address +

 Creates a selection in the browser for all the address ranges selected in the register values table.

-

Filters out all registers in the register tree that +

 Filters out all registers in the register tree that don't have any associated values (default or otherwise).


diff --git a/Ghidra/Features/Base/src/main/help/help/topics/RegisterPlugin/images/ClearRegisterValues.png b/Ghidra/Features/Base/src/main/help/help/topics/RegisterPlugin/images/ClearRegisterValues.png index 621122c4fd8803bf35477a437acd99dfd8e39c0c..ac97682b565f2d95c60603a3c5aa579b5c9874fc 100644 GIT binary patch literal 8759 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 Vv)qtN1^_Oan>UQ0nClNi{tvg&&}aYv literal 10369 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 diff --git a/Ghidra/Features/Base/src/main/help/help/topics/RegisterPlugin/images/EditRegisterValueRange.png b/Ghidra/Features/Base/src/main/help/help/topics/RegisterPlugin/images/EditRegisterValueRange.png index fc6699904db88b1693ab82e89176630bf174e43b..8d2140e41f67bd1994210c996969b15dec6b5931 100644 GIT binary patch literal 7401 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 M%WKG$K}>@G1KwChVE_OC literal 9984 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

RwxE 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 diff --git a/Ghidra/Features/Base/src/main/help/help/topics/RegisterPlugin/images/SetRegisterValues.png b/Ghidra/Features/Base/src/main/help/help/topics/RegisterPlugin/images/SetRegisterValues.png index 18b5ebfe1c9e8f0d4b51e5ebb76f13e099312099..5c6bf973098c5b50d5f78ac19b9675ec33e73d01 100644 GIT binary patch literal 9446 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 schemeDestByteCountChanged()); schemeDestByteCountField.setAccessibleName("Mapping Ratio: Destination Size"); schemeSrcByteCountField = new IntegerTextField(4, 1); - schemeSrcByteCountField.setAllowNegativeValues(false); - schemeSrcByteCountField.setAllowsHexPrefix(false); - schemeSrcByteCountField.setDecimalMode(); + schemeSrcByteCountField.setMinValue(BigInteger.ZERO); + schemeSrcByteCountField.setUseNumberPrefix(false); + schemeSrcByteCountField.setFormat(IntegerFormat.DEC); schemeSrcByteCountField.addChangeListener(ev -> schemeSrcByteCountChanged()); schemeSrcByteCountField.setAccessibleName("Mapping Ratio: Source Size"); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/EditRegisterValueDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/EditRegisterValueDialog.java index 7794d433a7..21b9d1f733 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/EditRegisterValueDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/EditRegisterValueDialog.java @@ -22,8 +22,8 @@ import javax.swing.*; import docking.DialogComponentProvider; import docking.widgets.label.GLabel; +import docking.widgets.textfield.FixedSizeIntegerTextField; import ghidra.app.util.AddressInput; -import ghidra.app.util.bean.FixedBitSizeValueField; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Register; @@ -36,7 +36,7 @@ class EditRegisterValueDialog extends DialogComponentProvider { private AddressInput startAddrField; private AddressInput endAddrField; - private FixedBitSizeValueField registerValueField; + private FixedSizeIntegerTextField registerValueField; private boolean wasCancelled = true; EditRegisterValueDialog(Register register, Address start, Address end, BigInteger value, @@ -61,10 +61,16 @@ class EditRegisterValueDialog extends DialogComponentProvider { startAddrField = new AddressInput(program, addressChangeListener); endAddrField = new AddressInput(program, addressChangeListener); - registerValueField = new FixedBitSizeValueField(register.getBitLength(), true, false); - registerValueField.getAccessibleContext().setAccessibleName("Register Value"); - startAddrField.setAddress(start); - endAddrField.setAddress(end); + registerValueField = new FixedSizeIntegerTextField(16, register.getBitLength()); + registerValueField.getComponent() + .getAccessibleContext() + .setAccessibleName("Register Value"); + if (start != null) { + startAddrField.setAddress(start); + } + if (end != null) { + endAddrField.setAddress(end); + } registerValueField.setValue(value); JPanel panel = new JPanel(new PairLayout(5, 1)); @@ -77,7 +83,7 @@ class EditRegisterValueDialog extends DialogComponentProvider { panel.add(new GLabel("End Address:")); panel.add(endAddrField); panel.add(new GLabel("Value:")); - panel.add(registerValueField); + panel.add(registerValueField.getComponent()); panel.getAccessibleContext().setAccessibleName("Edit Register Value"); return panel; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterManagerProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterManagerProvider.java index d257767edf..965643212e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterManagerProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterManagerProvider.java @@ -19,14 +19,19 @@ import static ghidra.framework.model.DomainObjectEvent.*; import static ghidra.program.util.ProgramEvent.*; import java.awt.event.MouseEvent; +import java.math.BigInteger; import javax.swing.*; import docking.ActionContext; import docking.WindowPosition; -import docking.action.*; +import docking.action.ToggleDockingAction; +import docking.action.builder.ActionBuilder; +import docking.action.builder.ToggleActionBuilder; import generic.theme.GIcon; +import ghidra.app.cmd.register.SetRegisterCmd; import ghidra.app.context.ProgramActionContext; +import ghidra.framework.cmd.Command; import ghidra.framework.model.DomainObjectChangedEvent; import ghidra.framework.model.DomainObjectListener; import ghidra.framework.plugintool.ComponentProviderAdapter; @@ -52,16 +57,13 @@ public class RegisterManagerProvider extends ComponentProviderAdapter { private RegisterTree tree; private RegisterValuesPanel values; - private DockingAction deleteRegisterValuesAction; - - private DockingAction selectRegisterValuesAction; - private ToggleDockingAction showDefaultRegisterValuesAction; + private ToggleDockingAction showDefaultValuesAction; private DomainObjectListener domainObjectListener; private ToggleDockingAction filterRegistersAction; private SwingUpdateManager updateMgr; - private ToggleDockingAction followLocationToggleAction; - protected boolean followLocation; + private boolean followLocation = false; + private Address currentAddress; RegisterManagerProvider(PluginTool tool, String owner) { super(tool, "Register Manager", owner, ProgramActionContext.class); @@ -86,9 +88,7 @@ public class RegisterManagerProvider extends ComponentProviderAdapter { tree.addGTreeSelectionListener(e -> showRegister()); values.getTable().getSelectionModel().addListSelectionListener(e -> { - JTable table = values.getTable(); - deleteRegisterValuesAction.setEnabled(table.getSelectedRowCount() > 0); - selectRegisterValuesAction.setEnabled(table.getSelectedRowCount() > 0); + contextChanged(); }); tree.setAccessibleNamePrefix("Register Manager"); @@ -100,80 +100,91 @@ public class RegisterManagerProvider extends ComponentProviderAdapter { void createActions() { HelpLocation helpLocation = new HelpLocation("RegisterPlugin", "tool_buttons"); - deleteRegisterValuesAction = new DockingAction("Delete Register Value Ranges", getOwner()) { - @Override - public void actionPerformed(ActionContext context) { - values.deleteSelectedRanges(); - } - }; - deleteRegisterValuesAction.setEnabled(false); - deleteRegisterValuesAction.setToolBarData(new ToolBarData(DELETE_REGISTER_VALUES_ICON)); - deleteRegisterValuesAction - .setPopupMenuData(new MenuData(new String[] { "Delete Register Value Ranges" })); - deleteRegisterValuesAction.setDescription("Delete Register Value Ranges"); - deleteRegisterValuesAction.setHelpLocation(helpLocation); - tool.addLocalAction(this, deleteRegisterValuesAction); - selectRegisterValuesAction = new DockingAction("Select Register Value Ranges", getOwner()) { - @Override - public void actionPerformed(ActionContext context) { - values.selectedRanges(); - } - }; - selectRegisterValuesAction.setEnabled(false); - selectRegisterValuesAction.setToolBarData(new ToolBarData(SELECT_REGISTER_VALUES_ICON)); - selectRegisterValuesAction - .setPopupMenuData(new MenuData(new String[] { "Select Register Value Ranges" })); - selectRegisterValuesAction.setDescription("Select Register Value Ranges"); - selectRegisterValuesAction.setHelpLocation(helpLocation); - tool.addLocalAction(this, selectRegisterValuesAction); + new ActionBuilder("Add Register Value Range", getOwner()) + .toolBarIcon(Icons.ADD_ICON) + .popupMenuPath("Add Value Range") + .popupMenuIcon(Icons.ADD_ICON) + .description("Add a new register value range") + .helpLocation(helpLocation) + .withContext(RegisterManagerContext.class) + .enabledWhen(c -> c.getSelectedRegister() != null) + .onAction(c -> addRange(c.getSelectedRegister())) + .buildAndInstallLocal(this); - showDefaultRegisterValuesAction = - new ToggleDockingAction("Show default register values", getOwner()) { - @Override - public void actionPerformed(ActionContext context) { - values.setShowDefaultValues(showDefaultRegisterValuesAction.isSelected()); - } - }; - showDefaultRegisterValuesAction.setSelected(false); - showDefaultRegisterValuesAction - .setDescription("Toggles showing of default register values"); - showDefaultRegisterValuesAction - .setMenuBarData(new MenuData(new String[] { "Show Default Values" })); - showDefaultRegisterValuesAction - .setHelpLocation(new HelpLocation("RegisterPlugin", "menu_actions")); - tool.addLocalAction(this, showDefaultRegisterValuesAction); + new ActionBuilder("Delete Register Value Ranges", getOwner()) + .toolBarIcon(DELETE_REGISTER_VALUES_ICON) + .popupMenuPath("Delete Register Value Ranges") + .popupMenuIcon(DELETE_REGISTER_VALUES_ICON) + .description("Delete selected register value ranges") + .helpLocation(helpLocation) + .withContext(RegisterManagerContext.class) + .enabledWhen(c -> c.hasSelectedRegisterValueRanges()) + .onAction(c -> values.deleteSelectedRanges()) + .buildAndInstallLocal(this); - filterRegistersAction = new ToggleDockingAction("Filter Registers", getOwner()) { - @Override - public void actionPerformed(ActionContext context) { - tree.setFiltered(filterRegistersAction.isSelected()); - } - }; - filterRegistersAction.setSelected(false); - filterRegistersAction.setDescription( - "Toggles filtering out registers that don't have values or default values."); - filterRegistersAction.setToolBarData(new ToolBarData(FILTER_ICON)); - filterRegistersAction.setHelpLocation(helpLocation); - tool.addLocalAction(this, filterRegistersAction); + new ActionBuilder("Select Register Value Ranges", getOwner()) + .toolBarIcon(SELECT_REGISTER_VALUES_ICON) + .popupMenuPath("Select Register Value Ranges") + .popupMenuIcon(SELECT_REGISTER_VALUES_ICON) + .helpLocation(helpLocation) + .description("Create a program selection from the selected rows") + .withContext(RegisterManagerContext.class) + .enabledWhen(c -> c.hasSelectedRegisterValueRanges()) + .onAction(c -> values.selectRanges()) + .buildAndInstallLocal(this); - followLocationToggleAction = - new ToggleDockingAction("Follow location changes", getOwner()) { - @Override - public void actionPerformed(ActionContext context) { - followLocation = followLocationToggleAction.isSelected(); - } - }; - followLocationToggleAction.setEnabled(true); - followLocationToggleAction.setHelpLocation(helpLocation); - followLocationToggleAction.setToolBarData(new ToolBarData(RECV_LOCATION_ICON, "NavAction")); - tool.addLocalAction(this, followLocationToggleAction); + showDefaultValuesAction = + new ToggleActionBuilder("Show Default Register Values", getOwner()) + .menuPath("Show Default Values") + .description("Toggles showing of default register values") + .helpLocation(helpLocation) + .selected(false) + .onAction(c -> updateShowingDefaultRegisterValues()) + .buildAndInstallLocal(this); + filterRegistersAction = new ToggleActionBuilder("Filter Registers", getOwner()) + .toolBarIcon(FILTER_ICON) + .description("Toggles showing only registers with values or default values") + .helpLocation(helpLocation) + .selected(false) + .onAction(c -> tree.setFiltered(filterRegistersAction.isSelected())) + .buildAndInstallLocal(this); + + new ToggleActionBuilder("Follow location changes", getOwner()) + .toolBarIcon(RECV_LOCATION_ICON) + .toolBarGroup("NavAction") + .description( + "If selected, auto select register and value range from listing location") + .helpLocation(helpLocation) + .selected(false) + .onAction(c -> followLocation = !followLocation) + .buildAndInstallLocal(this); + } + + private void updateShowingDefaultRegisterValues() { + values.setShowDefaultValues(showDefaultValuesAction.isSelected()); + } + + private void addRange(Register register) { + EditRegisterValueDialog dialog = + new EditRegisterValueDialog(register, currentAddress, currentAddress, null, program); + dialog.setTitle("Add Value Range"); + tool.showDialog(dialog, this); + + if (!dialog.wasCancelled()) { + Address start = dialog.getStartAddress(); + Address end = dialog.getEndAddress(); + BigInteger value = dialog.getValue(); + Command command = new SetRegisterCmd(register, start, end, value); + tool.execute(command, program); + } } private void showRegister() { Register register = tree.getSelectedRegister(); values.setRegister(register); + contextChanged(); } @Override @@ -191,7 +202,8 @@ public class RegisterManagerProvider extends ComponentProviderAdapter { if (program == null) { return null; } - return new ProgramActionContext(this, program); + return new RegisterManagerContext(program, tree.getSelectedRegister(), + values.hasSelectedRows()); } @Override @@ -245,6 +257,7 @@ public class RegisterManagerProvider extends ComponentProviderAdapter { } public void setLocation(Register register, Address address) { + currentAddress = address; if (!followLocation) { return; } @@ -254,4 +267,23 @@ public class RegisterManagerProvider extends ComponentProviderAdapter { values.setAddress(address); } + private class RegisterManagerContext extends ProgramActionContext { + private Register register; + private boolean hasSelectedRows; + + RegisterManagerContext(Program program, Register register, boolean hasSelectedRows) { + super(RegisterManagerProvider.this, program); + this.register = register; + this.hasSelectedRows = hasSelectedRows; + } + + public boolean hasSelectedRegisterValueRanges() { + return hasSelectedRows; + } + + public Register getSelectedRegister() { + return register; + } + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterPlugin.java index 1a4d1018da..c7e04686be 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterPlugin.java @@ -358,15 +358,13 @@ public class RegisterPlugin extends ProgramPlugin { @Override protected void locationChanged(ProgramLocation loc) { - if (loc instanceof RegisterTransitionFieldLocation) { - RegisterTransitionFieldLocation regLoc = (RegisterTransitionFieldLocation) loc; + if (loc instanceof RegisterTransitionFieldLocation regLoc) { Register reg = regLoc.getRegister(); if (reg != null) { registerMgrProvider.setLocation(reg, regLoc.getAddress()); } } - else if (loc instanceof RegisterFieldLocation) { - RegisterFieldLocation regLoc = (RegisterFieldLocation) loc; + else if (loc instanceof RegisterFieldLocation regLoc) { Register reg = regLoc.getRegister(); if (reg != null) { registerMgrProvider.setLocation(reg, regLoc.getAddress()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterValuesPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterValuesPanel.java index b999e1d746..6ee2981f64 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterValuesPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterValuesPanel.java @@ -83,6 +83,10 @@ class RegisterValuesPanel extends JPanel { } + boolean hasSelectedRows() { + return table.getSelectedRowCount() > 0; + } + private void editRow(int row) { RegisterValueRange range = model.values.get(row); Address start = range.getStartAddress(); @@ -275,7 +279,7 @@ class RegisterValuesPanel extends JPanel { } } - void selectedRanges() { + void selectRanges() { int[] rows = table.getSelectedRows(); AddressSet set = new AddressSet(); for (int element : rows) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/SetRegisterValueDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/SetRegisterValueDialog.java index 8eb33f004a..cac24f7a0b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/SetRegisterValueDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/SetRegisterValueDialog.java @@ -4,9 +4,9 @@ * 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. @@ -15,11 +15,12 @@ */ package ghidra.app.plugin.core.register; -import java.awt.*; +import java.awt.Component; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.math.BigInteger; import java.util.Arrays; +import java.util.Iterator; import javax.swing.*; import javax.swing.event.ChangeEvent; @@ -28,19 +29,21 @@ import javax.swing.event.ChangeListener; import docking.DialogComponentProvider; import docking.widgets.combobox.GComboBox; import docking.widgets.label.GLabel; +import docking.widgets.list.GListCellRenderer; +import docking.widgets.textfield.FixedSizeIntegerTextField; import generic.theme.GThemeDefaults.Ids.Fonts; import generic.theme.Gui; -import ghidra.app.util.bean.FixedBitSizeValueField; import ghidra.program.model.address.*; import ghidra.program.model.lang.Register; import ghidra.program.model.lang.RegisterValue; import ghidra.program.model.listing.Program; import ghidra.util.HelpLocation; +import ghidra.util.layout.VariableHeightPairLayout; public class SetRegisterValueDialog extends DialogComponentProvider { private JComboBox registerComboBox; - private FixedBitSizeValueField registerValueField; - private JList addressRangeList; + private FixedSizeIntegerTextField registerValueField; + private JList addressRangeList; private BigInteger registerValue; private Register selectedRegister; private boolean useValueField; @@ -58,7 +61,7 @@ public class SetRegisterValueDialog extends DialogComponentProvider { addOKButton(); addCancelButton(); if (useValueField) { - setFocusComponent(registerValueField.getTextComponent()); + setFocusComponent(registerValueField.getComponent()); } setSelectedRegister(register); setAddressRanges(addrSet); @@ -77,79 +80,72 @@ public class SetRegisterValueDialog extends DialogComponentProvider { } private JComponent buildWorkPanel(Register[] registers) { - registerComboBox = new GComboBox<>(wrapRegisters(registers)); - registerValueField = new FixedBitSizeValueField(32, true, false); + GLabel registerLabel = new GLabel("Register:"); + GLabel valueLabel = new GLabel("Value:"); + GLabel addressLabel = new GLabel("Address(es):"); + registerLabel.setHorizontalAlignment(SwingConstants.RIGHT); + valueLabel.setHorizontalAlignment(SwingConstants.RIGHT); + addressLabel.setHorizontalAlignment(SwingConstants.RIGHT); + addressLabel.setVerticalAlignment(SwingConstants.TOP); + + JPanel panel = new JPanel(new VariableHeightPairLayout(10, 10)); + panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5)); + panel.add(registerLabel); + panel.add(buildRegisterComboBox(registers)); + panel.add(valueLabel); + panel.add(buildValueField(registers)); + panel.add(addressLabel); + panel.add(buildAddressPanel()); + return panel; + } + + private Component buildValueField(Register[] registers) { + registerValueField = new FixedSizeIntegerTextField(16, 16); registerValueField.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { updateOkEnablement(); } }); + return registerValueField.getComponent(); + } + + private Component buildRegisterComboBox(Register[] registers) { + registerComboBox = new GComboBox<>(wrapRegisters(registers)); + registerComboBox.setRenderer(new RegisterComboRenderer()); registerComboBox.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { registerChanged(); + updateComboToolTip(); } }); + updateComboToolTip(); + return registerComboBox; + } - addressRangeList = new JList(); + private void updateComboToolTip() { + RegisterWrapper item = (RegisterWrapper) registerComboBox.getSelectedItem(); + String tooltip = item == null ? "" : item.getToolTip(); + registerComboBox.setToolTipText(tooltip); + } + + private Component buildAddressPanel() { + addressRangeList = new JList(); addressRangeList.setEnabled(false); Gui.registerFont(addressRangeList, Fonts.MONOSPACED); JScrollPane scrollPane = new JScrollPane(addressRangeList); scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); - Dimension d = scrollPane.getPreferredSize(); - d.height = 120; - d.width = 180; - scrollPane.setPreferredSize(d); - JPanel panel = new JPanel(new GridBagLayout()); - - GridBagConstraints gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; - gbc.insets = new Insets(5, 5, 1, 5); - gbc.gridx = 0; - gbc.gridy = 0; - panel.add(new GLabel("Register:"), gbc); - gbc.gridy = 1; - if (useValueField) { - panel.add(new GLabel("Value:"), gbc); - } - gbc.gridy = 2; - - gbc.anchor = GridBagConstraints.NORTHWEST; - gbc.insets = new Insets(10, 5, 1, 5); - GLabel addressLabel = new GLabel("Address(es):"); - addressLabel.setVerticalAlignment(SwingConstants.TOP); - panel.add(addressLabel, gbc); - - gbc.insets = new Insets(5, 5, 1, 5); - gbc.weightx = 1.0; - gbc.anchor = GridBagConstraints.WEST; - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.gridx = 1; - gbc.gridy = 0; - panel.add(registerComboBox, gbc); - gbc.gridy = 1; - if (useValueField) { - panel.add(registerValueField, gbc); - } - - gbc.gridy = 2; - gbc.weighty = 1.0; - gbc.fill = GridBagConstraints.BOTH; - panel.add(scrollPane, gbc); - - panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5)); - - return panel; - + return scrollPane; } private void registerChanged() { RegisterWrapper wrapper = (RegisterWrapper) registerComboBox.getSelectedItem(); if (wrapper != null) { - registerValueField.setBitSize(wrapper.register.getBitLength()); + int bitLength = wrapper.register.getBitLength(); + registerValueField.setBitSize(bitLength); updateOkEnablement(); } updateValue(); @@ -232,6 +228,21 @@ public class SetRegisterValueDialog extends DialogComponentProvider { return selectedRegister; } + private static class RegisterComboRenderer extends GListCellRenderer { + @Override + public Component getListCellRendererComponent(JList list, + RegisterWrapper value, int index, boolean isSelected, boolean hasFocus) { + super.getListCellRendererComponent(list, value, index, isSelected, hasFocus); + String toolTip = value.getToolTip(); + setToolTipText(toolTip); + return this; + } + + @Override + protected String getItemText(RegisterWrapper value) { + return value == null ? "" : value.toString(); + } + } } class RegisterWrapper implements Comparable { @@ -240,14 +251,24 @@ class RegisterWrapper implements Comparable { RegisterWrapper(Register register) { this.register = register; - displayName = register.getName() + " (" + register.getBitLength() + getAliases() + ")"; + displayName = register.getName() + " (" + register.getBitLength() + ")"; } - private String getAliases() { + String getToolTip() { StringBuffer buf = new StringBuffer(); - for (String alias : register.getAliases()) { - buf.append(buf.length() == 0 ? "; " : ", "); - buf.append(alias); + buf.append(displayName); + buf.append(" Aliases: "); + Iterator aliases = register.getAliases().iterator(); + if (!aliases.hasNext()) { + buf.append("none"); + } + else { + buf.append(aliases.next()); + buf.append(", "); + } + while (aliases.hasNext()) { + buf.append(", "); + buf.append(aliases.next()); } return buf.toString(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/select/SelectBlockDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/select/SelectBlockDialog.java index 947a6877e5..c42d876112 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/select/SelectBlockDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/select/SelectBlockDialog.java @@ -4,9 +4,9 @@ * 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. @@ -97,7 +97,7 @@ class SelectBlockDialog extends ReusableDialogComponentProvider { numberInputField = new IntegerTextField(10); numberInputField.getComponent().getAccessibleContext().setAccessibleName("Number Input"); numberInputField.setMaxValue(BigInteger.valueOf(Integer.MAX_VALUE)); - numberInputField.setAllowNegativeValues(false); + numberInputField.setMinValue(BigInteger.ZERO); main.add(numberInputField.getComponent()); main.getAccessibleContext().setAccessibleName("Block"); return main; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/StringTableProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/StringTableProvider.java index 0da884bf0a..54f124bf9c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/StringTableProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/StringTableProvider.java @@ -17,6 +17,7 @@ package ghidra.app.plugin.core.string; import java.awt.*; import java.awt.event.KeyEvent; +import java.math.BigInteger; import java.util.ArrayList; import java.util.List; @@ -331,7 +332,7 @@ public class StringTableProvider extends ComponentProviderAdapter implements Dom private Component buildOffsetPanel() { offsetField = new IntegerTextField(4, 0L); - offsetField.setAllowNegativeValues(false); + offsetField.setMinValue(BigInteger.ZERO); offsetField.addChangeListener(e -> updatePreview()); preview = new JTextField(5); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/AddressInput.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/AddressInput.java index 09e798ad70..9a0f7cebff 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/AddressInput.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/AddressInput.java @@ -15,22 +15,22 @@ */ package ghidra.app.util; +import static docking.widgets.textfield.integer.IntegerFormat.*; + import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.event.ActionListener; -import java.util.Arrays; -import java.util.Comparator; +import java.util.*; import java.util.function.Consumer; import java.util.function.Predicate; import javax.swing.*; import javax.swing.border.Border; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; import docking.widgets.combobox.GComboBox; import docking.widgets.table.FocusableEditor; -import docking.widgets.textfield.HexDecimalModeTextField; +import docking.widgets.textfield.integer.MultiFormatTextField; +import docking.widgets.textfield.integer.IntegerFormat; import generic.expressions.ExpressionException; import ghidra.program.model.address.*; import ghidra.program.model.listing.Program; @@ -45,7 +45,7 @@ public class AddressInput extends JPanel implements FocusableEditor { public final static Predicate ALL_MEMORY_SPACES = s -> s.isMemorySpace(); public final static Predicate LOADED_MEMORY_SPACES = s -> s.isLoadedMemorySpace(); - private HexDecimalModeTextField textField; + private MultiFormatTextField textField; private AddressSpaceField addressSpaceField; AddressEvaluator addressEvaluator; private Predicate addressSpaceFilter = LOADED_MEMORY_SPACES; @@ -170,8 +170,9 @@ public class AddressInput extends JPanel implements FocusableEditor { * @param hexMode true to assume numbers are hexadecimal. */ public void setAssumeHex(boolean hexMode) { - textField.setHexMode(hexMode); - hexModeChanged(hexMode); + IntegerFormat format = hexMode ? IntegerFormat.HEX : IntegerFormat.DEC; + textField.setFormat(format); + hexModeChanged(format); } /** @@ -418,35 +419,17 @@ public class AddressInput extends JPanel implements FocusableEditor { private void buildComponent() { setLayout(new BorderLayout()); - textField = new HexDecimalModeTextField(10, b -> hexModeChanged(b)); - textField.setHexMode(true); + textField = new MultiFormatTextField(10, List.of(HEX, DEC), b -> hexModeChanged(b)); textField.setName("JTextField");//for JUnits... addressSpaceField = new AddressSpaceField(); add(textField, BorderLayout.CENTER); comboAdded = false; - - textField.getDocument().addDocumentListener(new DocumentListener() { - @Override - public void insertUpdate(DocumentEvent e) { - notifyAddressChanged(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - notifyAddressChanged(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - notifyAddressChanged(); - } - }); - + textField.addTextChangedCallback(this::notifyAddressChanged); } - private void hexModeChanged(boolean hexMode) { - this.assumeHex = hexMode; - addressEvaluator.setAssumeHex(hexMode); + private void hexModeChanged(IntegerFormat format) { + this.assumeHex = format == HEX; + addressEvaluator.setAssumeHex(assumeHex); notifyAddressChanged(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/options/HexLongOption.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/options/HexLongOption.java index 6436c8bf9b..5466af8918 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/options/HexLongOption.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/options/HexLongOption.java @@ -18,6 +18,7 @@ package ghidra.app.util.importer.options; import java.awt.Component; import docking.widgets.textfield.IntegerTextField; +import docking.widgets.textfield.integer.IntegerFormat; import ghidra.app.util.*; import ghidra.app.util.opinion.Loader; import ghidra.framework.options.SaveState; @@ -66,7 +67,7 @@ public class HexLongOption extends AbstractOption { setValue(new HexLong(initialState)); IntegerTextField field = new IntegerTextField(); field.setValue(initialState); - field.setHexMode(); + field.setFormat(IntegerFormat.HEX); field.getComponent().setToolTipText(getDescription()); field.addChangeListener(e -> { setValue(new HexLong(field.getLongValue())); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AddressFieldOptionsPropertyEditor.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AddressFieldOptionsPropertyEditor.java index 86858db870..f9556e63ba 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AddressFieldOptionsPropertyEditor.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AddressFieldOptionsPropertyEditor.java @@ -26,6 +26,7 @@ import docking.widgets.checkbox.GCheckBox; import docking.widgets.combobox.GhidraComboBox; import docking.widgets.label.GDLabel; import docking.widgets.textfield.IntegerTextField; +import docking.widgets.textfield.integer.IntegerFormat; import ghidra.framework.options.CustomOptionsEditor; import ghidra.util.HTMLUtilities; import ghidra.util.layout.PairLayout; @@ -101,8 +102,8 @@ public class AddressFieldOptionsPropertyEditor extends PropertyEditorSupport panel.add(label); minDigitsField = new IntegerTextField(2); - minDigitsField.setAllowNegativeValues(false); - minDigitsField.setDecimalMode(); + minDigitsField.setMinValue(BigInteger.ZERO); + minDigitsField.setFormat(IntegerFormat.DEC); minDigitsField.setMaxValue(BigInteger.valueOf(32)); minDigitsField.getComponent().setToolTipText(MIN_HEX_DIGITS_TOOLTIP); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ArrayElementPropertyEditor.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ArrayElementPropertyEditor.java index e3c636dcf9..59947d141d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ArrayElementPropertyEditor.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ArrayElementPropertyEditor.java @@ -4,9 +4,9 @@ * 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. @@ -20,6 +20,7 @@ import java.awt.Container; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.beans.PropertyEditorSupport; +import java.math.BigInteger; import javax.swing.*; import javax.swing.border.TitledBorder; @@ -97,7 +98,7 @@ public class ArrayElementPropertyEditor extends PropertyEditorSupport Container parent) { IntegerTextField textField = new IntegerTextField(10); - textField.setAllowNegativeValues(false); + textField.setMinValue(BigInteger.ZERO); textField.setEnabled(true); JPanel textFieldPanel = new JPanel(); @@ -132,7 +133,8 @@ public class ArrayElementPropertyEditor extends PropertyEditorSupport } private void setLocalValues(ArrayElementWrappedOption namespaceOption) { - if (namespaceOption.showMultipleArrayElementPerLine() != groupElementsCheckBox.isSelected()) { + if (namespaceOption.showMultipleArrayElementPerLine() != groupElementsCheckBox + .isSelected()) { groupElementsCheckBox.setSelected(namespaceOption.showMultipleArrayElementPerLine()); } if (namespaceOption.getArrayElementsPerLine() != elementsPerLineField.getIntValue()) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/ProgramByteSource.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/ProgramByteSource.java index 39d44fa82d..4e8715f151 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/ProgramByteSource.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/memsearch/bytesource/ProgramByteSource.java @@ -64,7 +64,7 @@ public class ProgramByteSource implements AddressableByteSource { long offset = location.getByteAddress().subtract(location.getProgram().getImageBase()); return getProgram().getImageBase().add(offset); } - + public Program getProgram() { return memory.getProgram(); } 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 index 78b5ea4b00..71357bccec 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/AbstractNumberInputDialog.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/AbstractNumberInputDialog.java @@ -4,9 +4,9 @@ * 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. @@ -24,6 +24,7 @@ import docking.DialogComponentProvider; import docking.DockingWindowManager; import docking.widgets.label.GDLabel; import docking.widgets.textfield.IntegerTextField; +import docking.widgets.textfield.integer.IntegerFormat; import ghidra.util.Swing; /** @@ -109,10 +110,10 @@ public abstract class AbstractNumberInputDialog extends DialogComponentProvider }); if (showAsHex) { - numberInputField.setHexMode(); + numberInputField.setFormat(IntegerFormat.HEX); } if (min.compareTo(BigInteger.valueOf(0)) >= 0) { - numberInputField.setAllowNegativeValues(false); + numberInputField.setMinValue(BigInteger.ZERO); } return panel; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/IntegerRangeConstraintEditor.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/IntegerRangeConstraintEditor.java index 7a326a4d46..41d245aa29 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/IntegerRangeConstraintEditor.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/IntegerRangeConstraintEditor.java @@ -4,9 +4,9 @@ * 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. @@ -15,6 +15,8 @@ */ package docking.widgets.table.constrainteditor; +import static docking.widgets.textfield.integer.IntegerFormat.*; + import java.awt.Component; import java.awt.GridLayout; import java.math.BigInteger; @@ -129,7 +131,8 @@ public class IntegerRangeConstraintEditor BigInteger delta = bigEnd.subtract(bigStart).add(BigInteger.ONE); boolean hexMode = - lowerSpinner.getTextField().isHexMode() || upperSpinner.getTextField().isHexMode(); + lowerSpinner.getTextField().getFormat() == HEX || + upperSpinner.getTextField().getFormat() == HEX; String statusMsg = formatStatus( String.format("Range Size: " + (hexMode ? "0x%x" : "%,d"), delta), false); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/UnsignedLongConstraintEditor.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/UnsignedLongConstraintEditor.java index be71a17fcd..06a58ee941 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/UnsignedLongConstraintEditor.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/UnsignedLongConstraintEditor.java @@ -4,9 +4,9 @@ * 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. @@ -25,6 +25,7 @@ import docking.widgets.label.GDHtmlLabel; import docking.widgets.table.constraint.ColumnConstraint; import docking.widgets.table.constraint.SingleValueColumnConstraint; import docking.widgets.textfield.IntegerTextField; +import docking.widgets.textfield.integer.IntegerFormat; import generic.theme.GThemeDefaults.Colors.Messages; /** @@ -54,8 +55,8 @@ public class UnsignedLongConstraintEditor extends AbstractColumnConstraintEditor getConstraint().getConstraintValue(); field = new IntegerTextField(16, 0); - field.setHexMode(); - field.setAllowNegativeValues(false); + field.setFormat(IntegerFormat.HEX); + field.setMinValue(BigInteger.ZERO); // don't allow negative numbers field.setMaxValue(new BigInteger("FFFFFFFFFFFFFFFF", 16)); field.addChangeListener(e -> valueChanged()); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/UnsignedLongRangeConstraintEditor.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/UnsignedLongRangeConstraintEditor.java index 7db6f44e83..f6d1f4ed40 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/UnsignedLongRangeConstraintEditor.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/UnsignedLongRangeConstraintEditor.java @@ -4,9 +4,9 @@ * 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. @@ -25,6 +25,7 @@ import docking.widgets.label.GDHtmlLabel; import docking.widgets.table.constraint.ColumnConstraint; import docking.widgets.table.constraint.RangeColumnConstraint; import docking.widgets.textfield.IntegerTextField; +import docking.widgets.textfield.integer.IntegerFormat; import generic.theme.GThemeDefaults.Colors.Messages; import ghidra.util.layout.VerticalLayout; @@ -80,8 +81,8 @@ public class UnsignedLongRangeConstraintEditor extends AbstractColumnConstraintE } private void configureField(IntegerTextField field) { - field.setHexMode(); - field.setAllowNegativeValues(false); + field.setFormat(IntegerFormat.HEX); + field.setMinValue(BigInteger.ZERO); field.setMaxValue(MAX_VALUE); field.addChangeListener(e -> valueChanged()); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/FixedSizeIntegerTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/FixedSizeIntegerTextField.java new file mode 100644 index 0000000000..358c3dafda --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/FixedSizeIntegerTextField.java @@ -0,0 +1,173 @@ +/* ### + * 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.textfield; + +import static docking.widgets.textfield.integer.IntegerFormat.*; + +import java.math.BigInteger; + +import docking.widgets.textfield.integer.AbstractIntegerTextField; +import docking.widgets.textfield.integer.IntegerFormat; + +/** + * TextField for entering numbers where the values are restricted to those that can be represented + * by a specific number of bits. For example, if the bit size is eight, signed values + * must be between -128 and 127 and unsigned values must be between 0 and 255. By + * default, this class uses all the formats from the {@link IntegerFormat} enum class except for + * signed octal and signed binary. + * + *

+ * This field does continuous checking, so you can't enter a bad value. + *

+ * The bitSize can be changed on this field which will cause its min and max values to change to + * the appropriate values for that bit size and signedness of the current {@link IntegerFormat}. + * Also, if the current value doesn't fit in the new bit size, it will be reset to having no + * value (textfield is blank). + *

+ * Internally, values are maintained using BigIntegers so this class can accommodate any bit size + * desired. There are convenience methods for getting the value as either an int or long. You + * should only use these convenience methods if you know the current bit size fits in either a + * int or long respectively. + * + *

+ * There are several configuration options as follows: + *

    + *
  • Bit Size - This value must be 1 or greater and determines the minimum and maximum allowed + * input values when combined with the current format signedness.
  • + *
  • Use number prefix - If this mode is on, then non-decimal values must be typed with its + * prefix(i.e., 0x for hex). When requiring non-decimal prefix, the field is permitted to auto + * switch formats based on the prefix (or lack thereof). When the "use prefix" option is off, the + * only way to switch formats is to use the built-in ctrl-M action.
  • + *
  • Show the number mode as hint text - If showing number mode is on, the format short name + * is displayed lightly in the bottom right portion of the text field. + * See {@link #setShowNumberMode(boolean)}
  • + *
+ * + */ +public class FixedSizeIntegerTextField extends AbstractIntegerTextField { + + private int bitSize; + private BigInteger minSignedValue; + private BigInteger minUnsignedValue; + private BigInteger maxSignedValue; + private BigInteger maxUnsignedValue; + + /** + * Constructor + * @param columns the number of character positions for the preferred size of the text field + * @param bitSize the initial bit size + */ + public FixedSizeIntegerTextField(int columns, int bitSize) { + this(columns, bitSize, null); + } + + /** + * Constructor + * @param columns the number of character positions for the preferred size of the text field + * @param bitSize the initial bit size + * @param initialValue the value to initialize the field to + */ + public FixedSizeIntegerTextField(int columns, int bitSize, long initialValue) { + this(columns, bitSize, BigInteger.valueOf(initialValue)); + } + + /** + * Constructor + * @param columns the number of character positions for the preferred size of the text field + * @param bitSize the initial bit size + * @param initialValue the value to initialize the field to + */ + public FixedSizeIntegerTextField(int columns, int bitSize, BigInteger initialValue) { + super(columns, initialValue, DEC, U_DEC, HEX, U_HEX, U_OCT, U_BIN); + setBitSize(bitSize); + } + + /** + * Sets the bit size for this field, which effectively sets the min and max values for this + * field when combined with the signedness of the currently selected {@link IntegerFormat}. + * @param bitSize the number of bits that will be used to store this value, effectively + * determining its min and max value + */ + public void setBitSize(int bitSize) { + if (bitSize < 1) { + throw new IllegalArgumentException("Bit size must be greater than 0"); + } + minUnsignedValue = BigInteger.ZERO; + maxUnsignedValue = BigInteger.TWO.pow(bitSize).subtract(BigInteger.ONE); + minSignedValue = BigInteger.TWO.pow(bitSize - 1).negate(); + maxSignedValue = BigInteger.TWO.pow(bitSize - 1).subtract(BigInteger.ONE); + + BigInteger value = getValue(); + this.bitSize = bitSize; + updateMinMax(); + setValue(value); + } + + /** + * {@return the current bit size for this field} + */ + public int getBitSize() { + return bitSize; + } + + @Override + public void setValue(BigInteger newValue) { + if (newValue != null && !isInBounds(newValue)) { + if (currentFormat.isUnsigned()) { + newValue = maybeConvertToUnsigned(newValue); + } + else { + newValue = maybeConvertToSigned(newValue); + } + } + super.setValue(newValue); + } + + private BigInteger maybeConvertToUnsigned(BigInteger value) { + // conversion only makes sense if value is a negative number in the signed range + if (value.compareTo(minSignedValue) >= 0 && value.compareTo(BigInteger.ZERO) < 0) { + return value.add(BigInteger.TWO.pow(bitSize)); + } + return value; + } + + private BigInteger maybeConvertToSigned(BigInteger value) { + // conversion only makes sense if value is a positive number in the unsigned range + if (value.compareTo(BigInteger.ZERO) > 0 && value.compareTo(maxUnsignedValue) <= 0) { + return value.subtract(BigInteger.TWO.pow(bitSize)); + } + return value; + } + + private void updateMinMax() { + if (currentFormat.isUnsigned()) { + setMinValue(minUnsignedValue); + setMaxValue(maxUnsignedValue); + } + else { + setMinValue(minSignedValue); + setMaxValue(maxSignedValue); + } + } + + @Override + public void setFormat(IntegerFormat format) { + BigInteger value = getValue(); + super.setFormat(format); + updateMinMax(); + setValue(value); + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/HexDecimalModeTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/HexDecimalModeTextField.java deleted file mode 100644 index 02a1a88169..0000000000 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/HexDecimalModeTextField.java +++ /dev/null @@ -1,114 +0,0 @@ -/* ### - * 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.textfield; - -import java.awt.*; -import java.awt.event.*; -import java.util.function.Consumer; - -import javax.swing.JTextField; -import javax.swing.ToolTipManager; - -import docking.DockingUtils; -import docking.util.GraphicsUtils; -import generic.theme.GThemeDefaults.Colors.Messages; -import generic.theme.Gui; - -/** - * Overrides the JTextField mainly to allow hint painting for the current radix mode. - */ -public class HexDecimalModeTextField extends JTextField { - - private static final String FONT_ID = "font.input.hint"; - private int hintWidth; - private boolean isHexMode; - private boolean showNumbericDecoration = true; - - public HexDecimalModeTextField(int columns, Consumer modeConsumer) { - super(columns); - - FontMetrics fontMetrics = getFontMetrics(Gui.getFont(FONT_ID)); - String mode = isHexMode ? "Hex" : "Dec"; - hintWidth = fontMetrics.stringWidth(mode); - - addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_M && DockingUtils.isControlModifier(e)) { - isHexMode = !isHexMode; - modeConsumer.accept(isHexMode); - repaint(); - } - } - }); - - // make sure tooltips will be activated - ToolTipManager.sharedInstance().registerComponent(this); - } - - @Override - public String getToolTipText(MouseEvent event) { - - int hintStart = getBounds().width - hintWidth; - if (event.getX() > hintStart) { - String key = DockingUtils.CONTROL_KEY_NAME; - return "Press '" + key + "-M' to toggle Hex or Decimal Mode"; - } - - return super.getToolTipText(); - } - - public void setHexMode(boolean hexMode) { - this.isHexMode = hexMode; - } - - /** - * Turns on or off the faded text that displays the field's radix mode (hex or decimal). - * - * @param show true to show the radix mode. - */ - public void setShowNumberMode(boolean show) { - this.showNumbericDecoration = show; - repaint(); - } - - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - if (!showNumbericDecoration) { - return; - } - - Font savedFont = g.getFont(); - g.setFont(Gui.getFont(FONT_ID)); - g.setColor(Messages.HINT); - - Dimension size = getSize(); - Insets insets = getInsets(); - int x; - if (getHorizontalAlignment() == RIGHT) { - x = insets.left; - } - else { - x = size.width - insets.right - hintWidth; - } - int y = size.height - insets.bottom - 1; - String mode = isHexMode ? "Hex" : "Dec"; - GraphicsUtils.drawString(this, g, mode, x, y); - g.setFont(savedFont); - } - -} 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 25deb873be..d81c3feb7e 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 @@ -15,20 +15,17 @@ */ package docking.widgets.textfield; -import java.awt.event.ActionListener; +import static docking.widgets.textfield.integer.IntegerFormat.*; + import java.math.BigInteger; -import java.util.ArrayList; -import java.util.List; -import javax.swing.JComponent; -import javax.swing.JTextField; -import javax.swing.event.*; -import javax.swing.text.*; - -import ghidra.util.SystemUtilities; +import docking.widgets.textfield.integer.AbstractIntegerTextField; +import docking.widgets.textfield.integer.IntegerFormat; /** - * TextField for entering integer numbers, either in decimal or hex. + * TextField for entering integer numbers in one of several number formats. By default, this class + * uses the {@link IntegerFormat#DEC} and the {@link IntegerFormat#HEX}, but it can be constructed + * with any of the formats defined in {@link IntegerFormat}. * *

* This field does continuous checking, so you can't enter a bad value. @@ -43,229 +40,51 @@ import ghidra.util.SystemUtilities; *

* There are several configuration options as follows: *

    - *
  • Allows negative numbers - either support all integer numbers or just non-negative numbers. - * See {@link #setAllowNegativeValues(boolean)}
  • - *
  • Allows hex prefix - If this mode is on, then hex mode is turned on and off automatically - * depending whether or not the text starts with 0x. Otherwise, the hex/decimal mode is set - * externally (either programmatically or pressing <CTRL> M) and the user is restricted to the - * numbers/letters appropriate for that mode. See {@link #setAllowsHexPrefix(boolean)}
  • - *
  • Have a max value - a max value can be set (must be positive) such that the user can not type - * a number whose absolute value is greater than the max. Otherwise, the value is unlimited if max - * is null/unspecified. See {@link #setMaxValue(BigInteger)}
  • - *
  • Show the number mode as hint text - If on either "Hex" or "Dec" is displayed lightly in the - * bottom right portion of the text field. See {@link #setShowNumberMode(boolean)}
  • + *
  • Max value - This value must be positive and will restrict the input to values less than or + * equal to this value.
  • + *
  • Min value - This value must be generally be negative and will restrict the input to values + * greater than or equal to this value. As a special case, the min value can be set to 1.
  • + *
  • Use number prefix - If this mode is on, then non-decimal values must be typed with its + * prefix(i.e., 0x for hex). When requiring non-decimal prefix, the field is permitted to auto + * switch formats based on the prefix (or lack thereof). When the use prefix is off, the only way + * to switch formats is to use the ctrl-M action.
  • + *
  • Show the number mode as hint text - If showing number mode is on, the format short name + * is displayed lightly in the bottom right portion of the text field. + * See {@link #setShowNumberMode(boolean)}
  • *
* */ -public class IntegerTextField { - private HexDecimalModeTextField textField; +public class IntegerTextField extends AbstractIntegerTextField { + private IntegerFormat hex; + private IntegerFormat decimal; - private boolean isHexMode = false; - private boolean allowsNegative = true; - private boolean allowsHexPrefix = true; - private BigInteger maxValue; - private BigInteger minValue; - - private List listeners = new ArrayList<>(); - - /** - * Creates a new IntegerTextField with 5 columns and no initial value - */ public IntegerTextField() { this(5, null); } - /** - * Creates a new IntegerTextField with the specified number of columns and no initial value - * - * @param columns the number of columns. - */ public IntegerTextField(int columns) { this(columns, null); } - /** - * Creates a new IntegerTextField with the specified number of columns and an initial value - * - * @param columns the number of columns to display in the JTextField. - * @param initialValue the initial value. This constructor takes an initialValue as a long. If - * you need a value that is bigger (or smaller) than can be specified as a long, then - * use the constructor that takes a BigInteger as an initial value. - */ public IntegerTextField(int columns, long initialValue) { this(columns, BigInteger.valueOf(initialValue)); } - /** - * Creates a new IntegerTextField with the specified number of columns and initial value - * - * @param columns the number of columns - * @param initialValue the initial value - */ public IntegerTextField(int columns, BigInteger initialValue) { - textField = new HexDecimalModeTextField(columns, b -> textFieldHexModeChanged(b)); - - AbstractDocument document = (AbstractDocument) textField.getDocument(); - document.setDocumentFilter(new HexDecimalDocumentFilter()); - setValue(initialValue); - - document.addDocumentListener(new DocumentListener() { - - @Override - public void removeUpdate(DocumentEvent e) { - SystemUtilities.runSwingLater(() -> valueChanged()); - } - - @Override - public void insertUpdate(DocumentEvent e) { - SystemUtilities.runSwingLater(() -> valueChanged()); - } - - @Override - public void changedUpdate(DocumentEvent e) { - SystemUtilities.runSwingLater(() -> valueChanged()); - } - }); + super(columns, initialValue, DEC, HEX); + decimal = allFormats.get(0); + hex = allFormats.get(1); } - /** - * Adds a change listener that will be notified whenever the value changes. - * - * @param listener the change listener to add. - */ - public void addChangeListener(ChangeListener listener) { - listeners.add(listener); + @Override + public void setMaxValue(BigInteger maxValue) { + super.setMaxValue(maxValue); } - /** - * Sets the accessible name for the component of this input field. - * @param name the accessible name for this field - */ - public void setAccessibleName(String name) { - textField.getAccessibleContext().setAccessibleName(name); - } - - /** - * Removes the changes listener. - * - * @param listener the listener to be removed. - */ - public void removeChangeListener(ChangeListener listener) { - listeners.remove(listener); - } - - /** - * Returns the current value of the field or null if the field has no current value. - * - * @return the current value of the field or null if the field has no current value. - */ - public BigInteger getValue() { - String text = textField.getText(); - return computeValueFromString(text, isHexMode); - } - - /** - * Returns the current value as an int. - * - *

- * If the field has no current value, 0 will be returned. If the value is bigger (or smaller) - * than an int, it will be cast to an int. - * - *

- * 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 - * @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.intValueExact(); - } - - /** - * Returns the current value as a long. - * - *

- * If the field has no current value, 0 will be returned. If the value is bigger (or smaller) - * than an long, it will be cast to a long. - * - *

- * 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 - * @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.longValueExact(); - } - - /** - * Convenience method for setting the value to a long value; - * - * @param newValue the new value for the field. - */ - public void setValue(long newValue) { - setValue(BigInteger.valueOf(newValue)); - } - - /** - * Convenience method for setting the value to an int value; - * - * @param newValue the new value for the field. - */ - public void setValue(int newValue) { - setValue(BigInteger.valueOf(newValue)); - } - - /** - * Sets the field to the given text. The text must be a properly formated string that is a value - * that is valid for this field. If the field is set to not allow "0x" prefixes, then the input - * string cannot start with 0x and furthermore, if the field is in decimal mode, then input - * string cannot take in hex digits a-f. On the other hand, if "0x" prefixes are allowed, then - * the input string can be either a decimal number or a hex number depending on if the input - * string starts with "0x". In this case, the field's hex mode will be set to match the input - * text. If the text is not valid, the field will not change. - * - * @param text the value as text to set on this field - * @return true if the set was successful - */ - public boolean setText(String text) { - String oldText = textField.getText(); - textField.setText(text); - return !oldText.equals(textField.getText()); - } - - /** - * Sets the value of the field to the given value. A null value will clear the field. - * - * @param newValue the new value or null. - */ - public void setValue(BigInteger newValue) { - - if (!allowsNegative && newValue != null && newValue.signum() < 0) { - newValue = null; - } - - updateTextField(newValue); - } - - /** - * Turns on or off the faded text that displays the field's radix mode (hex or decimal). - * - * @param show true to show the radix mode. - */ - public void setShowNumberMode(boolean show) { - textField.setShowNumberMode(show); + @Override + public void setMinValue(BigInteger minValue) { + super.setMinValue(minValue); } /** @@ -274,17 +93,11 @@ public class IntegerTextField { *

* If the field is currently in decimal mode, the current text will be change from displaying * the current value from decimal to hex. + * @deprecated use {@link #setFormat(IntegerFormat)} instead */ + @Deprecated(forRemoval = true, since = "12.2") public void setHexMode() { - BigInteger value = getValue(); - setHexMode(true); - setValue(value); - - } - - private void setHexMode(boolean hexMode) { - this.isHexMode = hexMode; - textField.setHexMode(hexMode); + setFormat(hex); } /** @@ -293,15 +106,28 @@ public class IntegerTextField { *

* If the field is currently in hex mode, the current text will be change from displaying the * current value from hex to decimal. + * @deprecated use {@link #setFormat(IntegerFormat)} instead */ + @Deprecated(forRemoval = true, since = "12.2") public void setDecimalMode() { - BigInteger value = getValue(); - setHexMode(false); - setValue(value); + setFormat(decimal); } /** - * Sets whether on not the field supports the 0x prefix. + * Returns true if in hex mode, false if in another mode. + * + * @return true if in hex mode, false if in decimal mode. + * @deprecated use {@link #getFormat()} instead + */ + @Deprecated(forRemoval = true, since = "12.2") + public boolean isHexMode() { + return currentFormat == hex; + } + + /** + * Sets whether on not the field supports the 0x prefix for hex numbers. This method is + * deprecated since it now supports addition input modes other than hex or decimal. Turning + * the prefix on for hex will also turn it on for other non-decimal modes as well. * *

* If 0x is supported, hex numbers will be displayed with the 0x prefix. Also, when typing, you @@ -310,387 +136,29 @@ public class IntegerTextField { * can't change the decimal/hex mode by typing 0x. The field will either be in decimal or hex * mode and the typed text will be interpreted appropriately for the mode. * - * @param allowsHexPrefix true to use the 0x convention for hex. + * @param usePrefix true to use the 0x convention for hex. + * @deprecated use {@link #setUseNumberPrefix(boolean)} instead */ - public void setAllowsHexPrefix(boolean allowsHexPrefix) { - BigInteger currentValue = getValue(); - this.allowsHexPrefix = allowsHexPrefix; - updateTextField(currentValue); - } - - /** - * Returns the current text displayed in the field. - * - * @return the current text displayed in the field. - */ - public String getText() { - return textField.getText(); - } - - /** - * Returns true if in hex mode, false if in decimal mode. - * - * @return true if in hex mode, false if in decimal mode. - */ - public boolean isHexMode() { - return isHexMode; + @Deprecated(forRemoval = true, since = "12.2") + public void setAllowsHexPrefix(boolean usePrefix) { + setUseNumberPrefix(usePrefix); } /** * Sets whether or not negative numbers are accepted. * * @param b if true, negative numbers are allowed. + * @deprecated use {@link #setMinValue(BigInteger)} instead */ + @Deprecated(forRemoval = true, since = "12.2") public void setAllowNegativeValues(boolean b) { BigInteger currentValue = getValue(); - allowsNegative = b; - if (!allowsNegative) { + setMinValue(b ? null : BigInteger.ZERO); + if (!b) { if (currentValue != null && currentValue.signum() < 0) { currentValue = null; } } - updateTextField(currentValue); - } - - /** - * Returns the current maximum allowed value. Null indicates that there is no maximum value. If - * negative values are permitted (see {@link #setAllowNegativeValues(boolean)}) this value will - * establish the upper and lower limit of the absolute value. - * - * @return the current maximum value allowed. - */ - public BigInteger getMaxValue() { - return maxValue; - } - - /** - * Sets the maximum allowed value. The maximum must be a positive number. Null indicates that - * there is no maximum value. - *

- * If negative values are permitted (see {@link #setAllowNegativeValues(boolean)}) this value - * will establish the upper and lower limit of the absolute value. - * - * @param maxValue the maximum value to allow. - */ - public void setMaxValue(BigInteger maxValue) { - if (maxValue != null && maxValue.signum() < 0) { - throw new IllegalArgumentException("Max value must be positive"); - } - BigInteger currentValue = getValue(); - this.maxValue = maxValue; - if (maxValue != null && !passesMaxCheck(currentValue)) { - if (currentValue.signum() < 0) { - setValue(maxValue.negate()); - } - else { - setValue(maxValue); - } - } - } - - /** - * Sets the minimum allowed value. The minimum must be a positive number. Null indicates that - * there is no minimum value. - *

- * If negative values are permitted (see {@link #setAllowNegativeValues(boolean)}) this value - * will establish the minimum limit of the absolute value. - * - * @param minValue the minimum value to allow. - */ - public void setMinValue(BigInteger minValue) { - if (minValue != null && minValue.signum() < 0) { - throw new IllegalArgumentException("Min value must be positive"); - } - BigInteger currentValue = getValue(); - this.minValue = minValue; - if (minValue != null && !passesMinCheck(currentValue)) { - if (currentValue.signum() < 0) { - setValue(minValue.negate()); - } - else { - setValue(minValue); - } - } - } - - /** - * Returns the JTextField component that this class manages. - * - * @return the JTextField component that this class manages. - */ - public JComponent getComponent() { - return textField; - } - - /** - * Adds an ActionListener to the TextField. - * - * @param listener the ActionListener to add. - */ - public void addActionListener(ActionListener listener) { - textField.addActionListener(listener); - } - - /** - * Removes an ActionListener from the TextField. - * - * @param listener the ActionListener to remove. - */ - public void removeActionListener(ActionListener listener) { - textField.removeActionListener(listener); - } - - /** - * Sets the enablement on the JTextField component; - * - * @param enabled true for enabled, false for disabled. - */ - public void setEnabled(boolean enabled) { - textField.setEnabled(enabled); - } - - /** - * Sets the editable mode for the JTextField component - * - * @param editable boolean flag, if true component is editable - */ - public void setEditable(boolean editable) { - textField.setEditable(editable); - } - - /** - * Requests focus to the JTextField - */ - public void requestFocus() { - textField.requestFocus(); - } - - /** - * Selects the text in the JTextField - */ - public void selectAll() { - textField.selectAll(); - } - - /** - * Sets the horizontal alignment of the JTextField - * - * @param alignment the alignment as in {@link JTextField#setHorizontalAlignment(int)} - */ - public void setHorizontalAlignment(int alignment) { - textField.setHorizontalAlignment(alignment); - } - - private void textFieldHexModeChanged(boolean hexMode) { - BigInteger value = getValue(); - this.isHexMode = hexMode; - setValue(value); - } - - private String computeTextForValue(BigInteger value) { - if (value == null) { - return ""; - } - if (isHexMode) { - String text = value.toString(16); - if (allowsHexPrefix) { - if (text.startsWith("-")) { - return "-0x" + text.substring(1); - } - return "0x" + text; - } - return text; - } - - // otherwise, show as decimal - return value.toString(10); - } - - private BigInteger computeValueFromString(String text, boolean parseAsHex) { - if (text.isEmpty() || isValidPrefix(text)) { - return null; - } - - if (!parseAsHex) { - return new BigInteger(text, 10); - } - - if (allowsHexPrefix) { - if (text.startsWith("0x")) { - text = text.substring(2); - } - else if (text.startsWith("-0x")) { - text = "-" + text.substring(3); - } - } - if (text.isEmpty()) { - return null; - } - return new BigInteger(text, 16); - } - - private void valueChanged() { - for (ChangeListener listener : listeners) { - listener.stateChanged(new ChangeEvent(this)); - } - } - - private boolean passesMaxCheck(BigInteger value) { - if (value == null) { - return true; - } - if (maxValue == null) { - return true; - } - return value.abs().compareTo(maxValue) <= 0; - } - - private boolean passesMinCheck(BigInteger value) { - if (value == null) { - return true; - } - if (minValue == null) { - return true; - } - return value.abs().compareTo(minValue) >= 0; - } - - private boolean shouldParseAsHex(String text) { - if (allowsHexPrefix) { - // if allowing "0x" prefix, let the incoming text determine if we should parse as hex - return text.startsWith("0x") || text.startsWith("-0x"); - } - // otherwise parse the input string is whatever mode this field has been set to. - return isHexMode; - } - - /** - * Sets the textField to the given value taking into account the current configuration. - * - * @param value the value to convert to a string for the textField. - */ - private void updateTextField(BigInteger value) { - String text = computeTextForValue(value); - textField.setText(text); - } - - private boolean isValidPrefix(String s) { - switch (s) { - case "0x": - return allowsHexPrefix; - case "-0x": - return allowsHexPrefix && allowsNegative; - case "-": - return allowsNegative; - default: - return false; - } - - } - - /** - * DocumentFilter that prevents users from entering invalid data into the field. - */ - private class HexDecimalDocumentFilter extends DocumentFilter { - @Override - public void insertString(FilterBypass fb, int offset, String text, AttributeSet attr) - throws BadLocationException { - - // form a string that is the current document text with the inserted new text - text = text.replace('X', 'x'); - StringBuilder builder = getText(fb); - builder.insert(offset, text); - - // if the newly formed text is valid, allow the operation - if (isValid(builder)) { - super.insertString(fb, offset, text, attr); - } - } - - @Override - public void replace(FilterBypass fb, int offset, int length, String text, - AttributeSet attrs) throws BadLocationException { - - // form a string that is the current document text with the replaced text - text = text.replace('X', 'x'); - StringBuilder builder = getText(fb); - builder.replace(offset, offset + length, text); - - // if the newly formed text is valid, allow the operation - if (isValid(builder)) { - super.replace(fb, offset, length, text, attrs); - } - } - - @Override - public void remove(FilterBypass fb, int offset, int length) throws BadLocationException { - - // form a string that is the current document text with the indicated part deleted - StringBuilder builder = getText(fb); - builder.delete(offset, offset + length); - - // if the new formed text is valid, allow the operation. - if (isValid(builder)) { - super.remove(fb, offset, length); - } - } - - private boolean isValid(StringBuilder builder) { - String valueString = builder.toString(); - - // Depending on configuration and input string, determine if we should parse as hex. - // If we don't allow "0x" prefix, then use the current hex/integer mode, Otherwise, - // parse as hex depending on whether or not the input string starts with the - // "0x" prefix. - boolean parseAsHex = shouldParseAsHex(valueString); - - // allow the string if it is the beginning of a valid string even though - // it doesn't evaluate to a number yet. - if (isValidPrefix(valueString)) { - // When the input is valid, update the hex mode to match how the text was parsed. - // See parseAsHex variable comment above. - setHexMode(parseAsHex); - return true; - } - - // otherwise, it must parse to a number to be valid. - try { - BigInteger value = computeValueFromString(valueString, parseAsHex); - if (isNonAllowedNegativeNumber(value)) { - return false; - } - if (passesMaxCheck(value) && passesMinCheck(value)) { - // When the input is valid, update the hex mode to match how the text was parsed. - // See parseAsHex variable comment above. - setHexMode(parseAsHex); - return true; - } - } - catch (NumberFormatException e) { - return false; - } - return false; - } - - // Retrieves the current document text from inside the document filter. - private StringBuilder getText(FilterBypass fb) throws BadLocationException { - StringBuilder builder = new StringBuilder(); - Document document = fb.getDocument(); - builder.append(document.getText(0, document.getLength())); - return builder; - } - - } - - private boolean isNonAllowedNegativeNumber(BigInteger value) { - if (value == null) { - return false; - } - if (allowsNegative) { - return false; - } - - // so we don't allow negatives - return value.signum() < 0; + setValue(currentValue); } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/integer/AbstractIntegerTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/integer/AbstractIntegerTextField.java new file mode 100644 index 0000000000..d6e9a7dbd2 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/integer/AbstractIntegerTextField.java @@ -0,0 +1,515 @@ +/* ### + * 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.textfield.integer; + +import java.awt.event.ActionListener; +import java.math.BigInteger; +import java.util.*; + +import javax.swing.JComponent; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.text.*; + +/** + * Base class for IntegerTextFields that allow entering integer values based on some + * integer format (i.e., hex, decimal, unsigned hex, binary, etc.). This field does input + * validation, so only valid text for the current format can be typed. + */ +public class AbstractIntegerTextField { + private MultiFormatTextField textField; + private List listeners = new ArrayList<>(); + + protected List allFormats; + protected IntegerFormat currentFormat; + private BigInteger minValue; + private BigInteger maxValue; + private boolean usePrefix = true; + + /** + * Creates a new IntegerTextField with the specified number of columns and initial value + * + * @param columns the number of columns + * @param initialValue the initial value + * @param formats the supported InputNumberModes + */ + @SafeVarargs + public AbstractIntegerTextField(int columns, BigInteger initialValue, + IntegerFormat... formats) { + allFormats = Arrays.asList(formats); + currentFormat = allFormats.get(0); + + textField = new MultiFormatTextField(columns, allFormats, m -> setFormat(m)); + + AbstractDocument document = (AbstractDocument) textField.getDocument(); + document.setDocumentFilter(new HexDecimalDocumentFilter()); + setValue(initialValue); + textField.addTextChangedCallback(this::valueChanged); + } + + /** + * Adds a change listener that will be notified whenever the value changes. + * + * @param listener the change listener to add. + */ + public void addChangeListener(ChangeListener listener) { + listeners.add(listener); + } + + /** + * Sets the accessible name for the component of this input field. + * @param name the accessible name for this field + */ + public void setAccessibleName(String name) { + textField.getAccessibleContext().setAccessibleName(name); + } + + /** + * Removes the changes listener. + * + * @param listener the listener to be removed. + */ + public void removeChangeListener(ChangeListener listener) { + listeners.remove(listener); + } + + /** + * Returns the current value of the field or null if the field has no current value. + * + * @return the current value of the field or null if the field has no current value. + */ + public BigInteger getValue() { + String text = textField.getText(); + return parse(text, currentFormat); + } + + /** + * Returns the current value as an int. + * + *

+ * If the field has no current value, 0 will be returned. If the value is bigger (or smaller) + * than an int, it will be cast to an int. + * + *

+ * 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 + * @throws ArithmeticException if the value in this field will not fit into an int + */ + public int getIntValue() { + BigInteger currentValue = getValue(); + if (currentValue == null) { + if (minValue != null && minValue.equals(BigInteger.ONE)) { + return 1; + } + return 0; + } + return currentValue.intValueExact(); + } + + /** + * Returns the current value as a long. + * + *

+ * If the field has no current value, 0 will be returned. If the value is bigger (or smaller) + * than an long, it will be cast to a long. + * + *

+ * 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 + * @throws ArithmeticException if the value in this field will not fit into a long + */ + public long getLongValue() { + BigInteger currentValue = getValue(); + if (currentValue == null) { + if (minValue != null && minValue.equals(BigInteger.ONE)) { + return 1; + } + return 0; + } + return currentValue.longValueExact(); + } + + /** + * Convenience method for setting the value to a long value; + * + * @param newValue the new value for the field. + */ + public void setValue(long newValue) { + setValue(BigInteger.valueOf(newValue)); + } + + /** + * Convenience method for setting the value to an int value; + * + * @param newValue the new value for the field. + */ + public void setValue(int newValue) { + setValue(BigInteger.valueOf(newValue)); + } + + /** + * Sets the field to the given text. The text must be a properly formated string that is a valid + * value for this field. If the field is set to not allow "0x" prefixes, then the input + * string cannot start with 0x and furthermore, if the field is in decimal mode, then input + * string cannot take in hex digits a-f. On the other hand, if "0x" prefixes are allowed, then + * the input string can be either a decimal number or a hex number depending on if the input + * string starts with "0x". In this case, the field's hex mode will be set to match the input + * text. If the text is not valid, the field will not change. + * + * @param text the value as text to set on this field + * @return true if the set was successful + */ + public boolean setText(String text) { + String oldText = textField.getText(); + textField.setText(text); + return !oldText.equals(textField.getText()); + } + + /** + * Sets the value of the field to the given value. A null value will clear the field. + * + * @param newValue the new value or null. + */ + public void setValue(BigInteger newValue) { + String text = ""; + if (newValue != null && isInBounds(newValue)) { + text = currentFormat.format(newValue); + text = addPrefix(text); + } + textField.setText(text); + } + + private String addPrefix(String text) { + if (!usePrefix) { + return text; + } + String prefix = currentFormat.getPrefix(); + if (prefix.isBlank()) { + return text; + } + if (text.startsWith("-")) { + return "-" + prefix + text.substring(1); + } + return prefix + text; + } + + /** + * Turns on or off the faded text that displays the field's radix mode (hex or decimal). + * + * @param show true to show the radix mode. + */ + public void setShowNumberMode(boolean show) { + textField.setShowInputFormatHint(show); + } + + /** + * Sets the format for entering an integer into this field. The current text in the field + * will change to keep the same numeric value, but in the new input format. + * @param format the format for entering an integer into the field. + */ + public void setFormat(IntegerFormat format) { + if (!allFormats.contains(format)) { + throw new IllegalArgumentException(format.getName() + "is not valid for this field"); + } + BigInteger currentValue = getValue(); + currentFormat = format; + textField.setFormat(format); + setValue(currentValue); + } + + /** + * {@return the current format for entering numbers into this field} + */ + public IntegerFormat getFormat() { + return currentFormat; + } + + /** + * Returns the current text displayed in the field. + * + * @return the current text displayed in the field. + */ + public String getText() { + return textField.getText(); + } + + /** + * Returns the current minimum allowed value. Null indicates that there is no minimum value. + * + * @return the current maximum value allowed. + */ + public BigInteger getMinValue() { + return minValue; + } + + /** + * Returns the current maximum allowed value. Null indicates that there is no maximum value. + * + * @return the current maximum value allowed. + */ + public BigInteger getMaxValue() { + return maxValue; + } + + /** + * Returns the JTextField component that this class manages. + * + * @return the JTextField component that this class manages. + */ + public JComponent getComponent() { + return textField; + } + + /** + * Adds an ActionListener to the TextField. + * + * @param listener the ActionListener to add. + */ + public void addActionListener(ActionListener listener) { + textField.addActionListener(listener); + } + + /** + * Removes an ActionListener from the TextField. + * + * @param listener the ActionListener to remove. + */ + public void removeActionListener(ActionListener listener) { + textField.removeActionListener(listener); + } + + /** + * Sets the enablement on the JTextField component; + * + * @param enabled true for enabled, false for disabled. + */ + public void setEnabled(boolean enabled) { + textField.setEnabled(enabled); + } + + /** + * Sets the editable mode for the JTextField component + * + * @param editable boolean flag, if true component is editable + */ + public void setEditable(boolean editable) { + textField.setEditable(editable); + } + + /** + * Requests focus to the JTextField + */ + public void requestFocus() { + textField.requestFocus(); + } + + /** + * Selects the text in the JTextField + */ + public void selectAll() { + textField.selectAll(); + } + + /** + * Sets the horizontal alignment of the JTextField + * + * @param alignment the alignment as in {@link JTextField#setHorizontalAlignment(int)} + */ + public void setHorizontalAlignment(int alignment) { + textField.setHorizontalAlignment(alignment); + } + + /** + * Sets whether or not that non-decimal formats require using a prefix (i.e., "0x" for hex). + * Generally, using a prefix is preferred as it allows the mode to auto-switch as the user + * types (or not types) a prefix. If the prefix is not used, the only way to change input + * formats is to use the built-in cntr-M action. + * @param usePrefix true to require a prefix, false to not require a prefix + */ + public void setUseNumberPrefix(boolean usePrefix) { + BigInteger value = getValue(); + this.usePrefix = usePrefix; + setValue(value); + } + + /** + * Returns a list of all support {@link IntegerFormat}s supported by this field. + * @return a list of all support number formats for this field. + */ + public List getAllFormats() { + return new ArrayList<>(allFormats); + } + + protected void setMinValue(BigInteger minValue) { + BigInteger value = getValue(); + this.minValue = minValue; + setValue(value); + } + + protected void setMaxValue(BigInteger maxValue) { + BigInteger value = getValue(); + this.maxValue = maxValue; + setValue(value); + } + + private void valueChanged() { + for (ChangeListener listener : listeners) { + listener.stateChanged(new ChangeEvent(this)); + } + } + + private boolean allowsNegative() { + return minValue == null || minValue.compareTo(BigInteger.ZERO) < 0; + } + + protected boolean isInBounds(BigInteger value) { + if (minValue != null && minValue.compareTo(value) > 0) { + return false; + } + return maxValue == null || maxValue.compareTo(value) >= 0; + } + + private BigInteger parse(String text, IntegerFormat format) { + String prefix = format.getPrefix(); + if (usePrefix && !prefix.isBlank()) { + if (text.startsWith(prefix)) { + text = text.substring(prefix.length()); + } + else if (text.startsWith("-" + prefix)) { + text = "-" + text.substring(prefix.length() + 1); + } + else { + return null; + } + } + return format.parse(text); + } + + private boolean isValidPrefix(String text, IntegerFormat format) { + if (text.startsWith("-")) { + if (!allowsNegative()) { + return false; + } + if (text.length() == 1) { + return true; + } + text = text.substring(1); + } + if (!usePrefix) { + return false; + } + return usePrefix && format.getPrefix().startsWith(text); + } + + /** + * DocumentFilter that prevents users from entering invalid data into the field. + */ + private class HexDecimalDocumentFilter extends DocumentFilter { + @Override + public void insertString(FilterBypass fb, int offset, String text, AttributeSet attr) + throws BadLocationException { + + // form a string that is the current document text with the inserted new text + text = text.replace('X', 'x'); + StringBuilder builder = getText(fb); + builder.insert(offset, text); + + // if the newly formed text is valid, allow the operation + if (isValid(builder.toString())) { + super.insertString(fb, offset, text, attr); + } + } + + @Override + public void replace(FilterBypass fb, int offset, int length, String text, + AttributeSet attrs) throws BadLocationException { + + // form a string that is the current document text with the replaced text + text = text.replace('X', 'x'); + StringBuilder builder = getText(fb); + builder.replace(offset, offset + length, text); + + // if the newly formed text is valid, allow the operation + if (isValid(builder.toString())) { + super.replace(fb, offset, length, text, attrs); + } + } + + @Override + public void remove(FilterBypass fb, int offset, int length) throws BadLocationException { + + // form a string that is the current document text with the indicated part deleted + StringBuilder builder = getText(fb); + builder.delete(offset, offset + length); + + // if the new formed text is valid, allow the operation. + if (isValid(builder.toString())) { + super.remove(fb, offset, length); + } + } + + private boolean isValid(String text) { + if (text.isEmpty()) { + return true; + } + if (isValidPrefix(text, currentFormat)) { + return true; + } + + BigInteger value = parse(text, currentFormat); + if (value != null) { + return isInBounds(value); + } + + if (usePrefix) { + // only allow auto switching if using number prefix + return autoSwitchFormat(text); + } + return false; + } + + private boolean autoSwitchFormat(String text) { + for (IntegerFormat format : allFormats) { + if (isValidPrefix(text, format)) { + currentFormat = format; + textField.setFormat(format); + return true; + } + BigInteger value = parse(text, format); + if (value != null && isInBounds(value)) { + currentFormat = format; + textField.setFormat(format); + return true; + } + } + return false; + } + + // Retrieves the current document text from inside the document filter. + private StringBuilder getText(FilterBypass fb) throws BadLocationException { + StringBuilder builder = new StringBuilder(); + Document document = fb.getDocument(); + builder.append(document.getText(0, document.getLength())); + return builder; + } + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/integer/IntegerFormat.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/integer/IntegerFormat.java new file mode 100644 index 0000000000..3409a1750a --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/integer/IntegerFormat.java @@ -0,0 +1,107 @@ +/* ### + * 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.textfield.integer; + +import java.math.BigInteger; + +import docking.widgets.textfield.FixedSizeIntegerTextField; +import docking.widgets.textfield.IntegerTextField; + +/** + * Input formats for entering integers into a text field such as the {@link IntegerTextField} or + * {@link FixedSizeIntegerTextField} + */ +public enum IntegerFormat { + DEC("Dec", "decimal", "", 10, false), + HEX("Hex", "hexadecimal", "0x", 16, false), + OCT("Oct", "octal", "0O", 8, false), + BIN("Bin", "binary", "0b", 2, false), + + U_DEC("uDec", "unsigned decimal", "", 10, true), + U_HEX("uHex", "unsigned hexadecimal", "0x", 16, true), + U_OCT("uOct", "unsigned octal", "0O", 8, true), + U_BIN("uBin", "unsigned binary", "0b", 2, true); + + private String name; + private String longName; + private String prefix; + private int radix; + private boolean isUnsigned; + + private IntegerFormat(String name, String description, String prefix, int radix, + boolean isUnsigned) { + this.name = name; + this.longName = description; + this.prefix = prefix; + this.radix = radix; + this.isUnsigned = isUnsigned; + } + + /** + * {@return the short name of this number format} + */ + public String getName() { + return name; + } + + /** + * {@return a descriptive name of this number format} + */ + public String getDescription() { + return longName; + } + + /** + * Converts the given value into a string representation corresponding to this number format. + * @param value the value to format into a string + * @return A string representation of the given value. + */ + public String format(BigInteger value) { + return value.toString(radix); + } + + /** + * Parses the given string into a BigInteger or null if the string is not properly structured + * for this number format. + * @param text the text to parse into a BigInteger + * @return the BigInteger interpretation of the given string for this number format. + */ + public BigInteger parse(String text) { + try { + return new BigInteger(text, radix); + } + catch (NumberFormatException e) { + // failed to parse, return null + } + return null; + } + + /** + * {@return the prefix associated with this format} + */ + public String getPrefix() { + return prefix; + } + + /** + * Return true if this format is intended only for non-negative values. This is more of a hint + * to the client text field to determine if "-" characters are allowed to be entered. + * @return true if this format is for unsigned numbers + */ + public boolean isUnsigned() { + return isUnsigned; + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/integer/MultiFormatTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/integer/MultiFormatTextField.java new file mode 100644 index 0000000000..1dd7a03560 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/integer/MultiFormatTextField.java @@ -0,0 +1,172 @@ +/* ### + * 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.textfield.integer; + +import java.awt.*; +import java.awt.event.*; +import java.util.List; +import java.util.function.Consumer; + +import javax.swing.JTextField; +import javax.swing.ToolTipManager; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.Document; + +import docking.DockingUtils; +import docking.util.GraphicsUtils; +import generic.theme.GThemeDefaults.Colors.Messages; +import generic.theme.Gui; +import ghidra.util.Swing; +import utility.function.Callback; + +/** + * Overrides the JTextField mainly to allow hint painting for the current input format. It also + * handles processing control-M to switch modes. + */ +public class MultiFormatTextField extends JTextField { + + private static final String FONT_ID = "font.input.hint"; + private int hintWidth; + private boolean showFormatHint = true; + private List formats; + private int currentFormatIndex; + + public MultiFormatTextField(int columns, List formats, + Consumer formatChangeConsumer) { + super(columns); + + this.formats = formats; + updateFormatNameWidth(); + + addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_M && DockingUtils.isControlModifier(e)) { + if (++currentFormatIndex >= formats.size()) { + currentFormatIndex = 0; + } + updateFormatNameWidth(); + formatChangeConsumer.accept(formats.get(currentFormatIndex)); + repaint(); + } + } + }); + + // make sure tooltips will be activated + ToolTipManager.sharedInstance().registerComponent(this); + + } + + /** + * Uses the given callback to notify the client when the text has changed in this text field. + * @param c the callback to be notified when the text changes in this field + */ + public void addTextChangedCallback(Callback c) { + Document document = getDocument(); + + document.addDocumentListener(new DocumentListener() { + + @Override + public void removeUpdate(DocumentEvent e) { + Swing.runLater(() -> c.call()); + } + + @Override + public void insertUpdate(DocumentEvent e) { + Swing.runLater(() -> c.call()); + } + + @Override + public void changedUpdate(DocumentEvent e) { + Swing.runLater(() -> c.call()); + } + }); + } + + private void updateFormatNameWidth() { + FontMetrics fontMetrics = getFontMetrics(Gui.getFont(FONT_ID)); + hintWidth = fontMetrics.stringWidth(formats.get(currentFormatIndex).getName()); + + } + + @Override + public String getToolTipText(MouseEvent event) { + + int hintStart = getBounds().width - hintWidth; + + if (event.getX() > hintStart && formats.size() > 1) { + String key = DockingUtils.CONTROL_KEY_NAME; + IntegerFormat format = formats.get(currentFormatIndex); + return "Enter value in %s format. Press %s-M to cycle input formats." + .formatted(format.getDescription(), key); + } + + return super.getToolTipText(event); + } + + /** + * Sets the {@link IntegerFormat} that will be used to format and parse the text in this + * field. + * @param format the number format that will be used to format and parse the text in this field + */ + public void setFormat(IntegerFormat format) { + int indexOf = formats.indexOf(format); + if (indexOf >= 0) { + currentFormatIndex = indexOf; + } + updateFormatNameWidth(); + repaint(); + } + + /** + * Turns on or off the faded hint text that displays the field's current format (i.e., hex, + * decimal). + * + * @param show true to show the input format. + */ + public void setShowInputFormatHint(boolean show) { + this.showFormatHint = show; + repaint(); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + if (!showFormatHint) { + return; + } + + Font savedFont = g.getFont(); + g.setFont(Gui.getFont(FONT_ID)); + g.setColor(Messages.HINT); + + Dimension size = getSize(); + Insets insets = getInsets(); + int x; + if (getHorizontalAlignment() == RIGHT) { + x = insets.left; + } + else { + x = size.width - insets.right - hintWidth; + } + int y = size.height - insets.bottom - 1; + IntegerFormat format = formats.get(currentFormatIndex); + GraphicsUtils.drawString(this, g, format.getName(), x, y); + g.setFont(savedFont); + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/values/IntValue.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/values/IntValue.java index 57d89707cf..039b208a14 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/values/IntValue.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/values/IntValue.java @@ -4,9 +4,9 @@ * 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. @@ -18,6 +18,7 @@ package docking.widgets.values; import javax.swing.JComponent; import docking.widgets.textfield.IntegerTextField; +import docking.widgets.textfield.integer.IntegerFormat; /** * Value class for {@link Integer} Value with an option for display the value as decimal or hex. The @@ -69,10 +70,10 @@ public class IntValue extends AbstractValue { public JComponent getComponent() { if (field == null) { field = new IntegerTextField(20); - field.setAllowsHexPrefix(false); + field.setUseNumberPrefix(false); field.setShowNumberMode(false); if (displayAsHex) { - field.setHexMode(); + field.setFormat(IntegerFormat.HEX); field.setShowNumberMode(true); } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/values/LongValue.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/values/LongValue.java index be6680bfe1..57a27007c3 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/values/LongValue.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/values/LongValue.java @@ -4,9 +4,9 @@ * 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. @@ -18,6 +18,7 @@ package docking.widgets.values; import javax.swing.JComponent; import docking.widgets.textfield.IntegerTextField; +import docking.widgets.textfield.integer.IntegerFormat; /** * Value class for Long Values with an option for display the value as decimal or hex. The @@ -57,10 +58,10 @@ public class LongValue extends AbstractValue { public JComponent getComponent() { if (field == null) { field = new IntegerTextField(20); - field.setAllowsHexPrefix(false); + field.setUseNumberPrefix(false); field.setShowNumberMode(false); if (displayAsHex) { - field.setHexMode(); + field.setFormat(IntegerFormat.HEX); field.setShowNumberMode(true); } } diff --git a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/textfield/AbstractIntegerTextFieldTest.java b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/textfield/AbstractIntegerTextFieldTest.java new file mode 100644 index 0000000000..b35557ff07 --- /dev/null +++ b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/textfield/AbstractIntegerTextFieldTest.java @@ -0,0 +1,115 @@ +/* ### + * 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.textfield; + +import java.math.BigInteger; +import java.util.concurrent.atomic.AtomicIntegerArray; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.junit.After; +import org.junit.Before; + +import docking.test.AbstractDockingTest; +import docking.widgets.textfield.integer.AbstractIntegerTextField; +import docking.widgets.textfield.integer.IntegerFormat; + +public abstract class AbstractIntegerTextFieldTest + extends AbstractDockingTest { + private JFrame frame; + protected T field; + protected JTextField textField; + + @Before + public void setUp() throws Exception { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + field = createField(); + field.setShowNumberMode(true); + textField = (JTextField) field.getComponent(); + frame = new JFrame("Test"); + frame.getContentPane().add(field.getComponent()); + frame.pack(); + frame.setVisible(true); + } + + abstract protected T createField(); + + @After + public void tearDown() throws Exception { + frame.setVisible(false); + } + + protected void setFormat(IntegerFormat format) { + runSwing(() -> field.setFormat(format)); + } + + protected IntegerFormat getFormat() { + return runSwing(() -> field.getFormat()); + } + + protected void setValue(long value) { + runSwing(() -> field.setValue(value)); + } + + protected long getValue() { + return runSwing(() -> field.getLongValue()); + } + + protected int getIntValue() { + return runSwing(() -> field.getIntValue()); + } + + protected BigInteger getBigIntegerValue() { + return runSwing(() -> field.getValue()); + } + + protected String getText() { + return runSwing(() -> field.getText()); + } + + protected BigInteger getMinValue() { + return runSwing(() -> field.getMinValue()); + } + + protected BigInteger getMaxValue() { + return runSwing(() -> field.getMaxValue()); + } + + protected void setText(String text) { + runSwing(() -> field.setText(text)); + } + + protected void typeText(String text) { + triggerText(textField, text); + } + + protected void setUsePrefix(boolean b) { + runSwing(() -> field.setUseNumberPrefix(b)); + } + + class TestChangeListener implements ChangeListener { + volatile int count; + protected AtomicIntegerArray values = new AtomicIntegerArray(10); + + @Override + public void stateChanged(ChangeEvent e) { + values.set(count++, field.getIntValue()); + } + + } +} diff --git a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/textfield/FixedSizeIntegerTextFieldTest.java b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/textfield/FixedSizeIntegerTextFieldTest.java new file mode 100644 index 0000000000..3bb72bba3a --- /dev/null +++ b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/textfield/FixedSizeIntegerTextFieldTest.java @@ -0,0 +1,433 @@ +/* ### + * 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.textfield; + +import static docking.widgets.textfield.integer.IntegerFormat.*; +import static org.junit.Assert.*; + +import java.math.BigInteger; + +import org.junit.Test; + +public class FixedSizeIntegerTextFieldTest + extends AbstractIntegerTextFieldTest { + @Override + protected FixedSizeIntegerTextField createField() { + return new FixedSizeIntegerTextField(10, 8); + } + + @Test + public void testSignedHexSetValue() { + setFormat(HEX); + setValue(25); + assertEquals("0x19", getText()); + setValue(-1); + assertEquals("-0x1", getText()); + } + + @Test + public void testSignedHexSetText() { + setFormat(HEX); + setText("0x12"); + assertEquals(18, getValue()); + setText(""); + assertEquals(0, getValue()); + } + + @Test + public void testSignedHexTypeText() { + setFormat(HEX); + typeText("mnp0x12"); + assertEquals(18, getValue()); + setText(""); + typeText("-0x5"); + assertEquals(-5, getValue()); + } + + @Test + public void testSignedHexMin() { + setFormat(HEX); + setBitSize(8); + + typeText("-0x8"); + assertEquals("-0x8", getText()); + typeText("1"); + assertEquals("-0x8", getText()); // -0x80 (-128) is min allowed for 8 bits + typeText("0"); + assertEquals("-0x80", getText()); + assertEquals(-128, getValue()); + } + + @Test + public void testSignedHexMax() { + setFormat(HEX); + setBitSize(8); + + typeText("0x8"); + assertEquals("0x8", getText()); + typeText("0"); + assertEquals("0x8", getText()); // 7f (127) is the max allowed for 8 bits + setText(""); + typeText("0x7f"); + assertEquals("0x7f", getText()); + assertEquals(127, getValue()); + typeText("1"); + assertEquals("0x7f", getText()); + } + + @Test + public void testSignedHexConvertsUnsignedValueToNegative() { + setFormat(HEX); + setBitSize(8); + + setValue(0xff); + assertEquals(-1, getValue()); + } + + @Test + public void testUnsignedHexSetValue() { + setFormat(U_HEX); + setValue(25); + assertEquals("0x19", getText()); + setValue(-130); + assertEquals("", getText()); + setValue(0xff); + assertEquals("0xff", getText()); + } + + @Test + public void testUnsignedHexSetText() { + setFormat(U_HEX); + + setText("0x12"); + assertEquals(18, getValue()); + setText("-0x1"); + assertEquals(18, getValue()); // didn't change the setText was ignored + setText("0xff"); + assertEquals(255, getValue()); + } + + @Test + public void testUnsignedHexTypeText() { + setFormat(U_HEX); + typeText("mnp0x12"); + setText("0x13"); + assertEquals(19, getValue()); + } + + @Test + public void testunSignedHexMin() { + setFormat(U_HEX); + setBitSize(8); + + typeText("-0x8"); + assertEquals("0x8", getText()); + setText(""); + typeText("0x0"); + assertEquals("0x0", getText()); + assertEquals(0, getValue()); + } + + @Test + public void testunSignedHexMax() { + setFormat(U_HEX); + setBitSize(8); + + typeText("0xff"); + assertEquals("0xff", getText()); + assertEquals(255, getValue()); + setText(""); + typeText("0x10"); + assertEquals("0x10", getText()); + setText("0x10"); + typeText("0"); + assertEquals("0x10", getText()); + } + + @Test + public void testUnsignedHexConvertsUnsignedValueToNegative() { + setFormat(U_HEX); + setBitSize(8); + setValue(-1); + assertEquals("0xff", getText()); + assertEquals(255, getValue()); + } + + @Test + public void testSignedDecimalSetText() { + setFormat(DEC); + setText("12"); + assertEquals(12, getValue()); + setText("-4"); + assertEquals(-4, getValue()); + } + + @Test + public void testSignedDecimalTypeText() { + setFormat(DEC); + typeText("mnp12"); + setText("12"); + assertEquals(12, getValue()); + setText(""); + typeText("-5"); + assertEquals(-5, getValue()); + } + + @Test + public void testSignedDecimalMin() { + setFormat(DEC); + setBitSize(8); + + typeText("-128"); + assertEquals("-128", getText()); + setText(""); + typeText("-129"); + assertEquals("-12", getText()); + } + + @Test + public void testSignedDecimalMax() { + setFormat(DEC); + setBitSize(8); + + typeText("127"); + assertEquals("127", getText()); + assertEquals(127, getValue()); + typeText("3"); + assertEquals("127", getText()); // 7f (127) is the max allowed for 8 bits + setText(""); + typeText("128"); + assertEquals("12", getText()); + assertEquals(12, getValue()); + } + + @Test + public void testSignedDecimalConvertsUnsignedValueToNegative() { + setFormat(DEC); + setBitSize(8); + + setValue(255); + assertEquals(-1, getValue()); + } + + @Test + public void testUnsignedDecimalSetValue() { + setFormat(U_DEC); + setValue(25); + assertEquals("25", getText()); + setValue(-130); + assertEquals("", getText()); + setValue(255); + assertEquals("255", getText()); + } + + @Test + public void testUnsignedDecimalSetText() { + setFormat(U_DEC); + + setText("12"); + assertEquals(12, getValue()); + setText("-0x1"); + assertEquals(12, getValue()); // didn't change the setText was ignored + setText("255"); + assertEquals(255, getValue()); + } + + @Test + public void testUnsignedDecimalTypeText() { + setFormat(U_DEC); + typeText("mnp12"); + setText("12"); + assertEquals(12, getValue()); + } + + @Test + public void testUnSignedDecimalMin() { + setFormat(U_DEC); + setBitSize(8); + + typeText("-8"); + assertEquals("8", getText()); + setText(""); + typeText("0"); + assertEquals("0", getText()); + assertEquals(0, getValue()); + } + + @Test + public void testUnsignedDecimalMax() { + setFormat(U_DEC); + setBitSize(8); + + typeText("255"); + assertEquals("255", getText()); + assertEquals(255, getValue()); + setText(""); + typeText("256"); + assertEquals("25", getText()); + } + + @Test + public void testUnsignedDecimalConvertsUnsignedValueToNegative() { + setFormat(U_DEC); + setBitSize(8); + setValue(-1); + assertEquals("255", getText()); + assertEquals(255, getValue()); + } + + @Test + public void testUnsignedOctSetValue() { + setFormat(U_OCT); + setValue(25); + assertEquals("0O31", getText()); + setValue(-1); + assertEquals("0O377", getText()); + } + + @Test + public void testUnsignedOctSetText() { + setFormat(U_OCT); + setText("0O12"); + assertEquals(10, getValue()); + setText(""); + assertEquals(0, getValue()); + } + + @Test + public void testSignedOctTypeText() { + setFormat(U_OCT); + typeText("mnp0O12"); + assertEquals(10, getValue()); + setText(""); + typeText("-0O5"); // "-" will be ignored + assertEquals(5, getValue()); + } + + @Test + public void testUnsignedBinarySetValue() { + setFormat(U_BIN); + setValue(0x25); + assertEquals("0b100101", getText()); + setValue(-1); + assertEquals("0b11111111", getText()); + } + + @Test + public void testUnsignedBinarySetText() { + setFormat(U_BIN); + setText("0b101"); + assertEquals(5, getValue()); + setText(""); + assertEquals(0, getValue()); + } + + @Test + public void testSignedBinaryTypeText() { + setFormat(U_BIN); + typeText("mnp0b1210"); // the mnp and 2 are ignored + assertEquals("0b110", getText()); + assertEquals(6, getValue()); + setText(""); + typeText("-0b101"); // "-" will be ignored + assertEquals(5, getValue()); + } + + @Test + public void testChangingBitSizeSigned() { + setFormat(DEC); + setBitSize(8); + setValue(25); + assertEquals("25", getText()); + assertEquals(BigInteger.valueOf(-128), getMinValue()); + assertEquals(BigInteger.valueOf(127), getMaxValue()); + + setBitSize(16); + assertEquals("25", getText()); + assertEquals(BigInteger.valueOf(-32768), getMinValue()); + assertEquals(BigInteger.valueOf(32767), getMaxValue()); + + setValue(32000); + assertEquals("32000", getText()); + setBitSize(8); + assertEquals(BigInteger.valueOf(-128), getMinValue()); + assertEquals(BigInteger.valueOf(127), getMaxValue()); + assertEquals("", getText()); + } + + @Test + public void testChangingBitSizeUnsigned() { + setFormat(U_DEC); + setBitSize(8); + setValue(25); + assertEquals("25", getText()); + assertEquals(BigInteger.valueOf(0), getMinValue()); + assertEquals(BigInteger.valueOf(255), getMaxValue()); + + setBitSize(16); + assertEquals("25", getText()); + assertEquals(BigInteger.valueOf(0), getMinValue()); + assertEquals(BigInteger.valueOf(65535), getMaxValue()); + + setValue(65000); + assertEquals("65000", getText()); + setBitSize(8); + assertEquals(BigInteger.valueOf(0), getMinValue()); + assertEquals(BigInteger.valueOf(255), getMaxValue()); + assertEquals("", getText()); + } + + @Test + public void testChangingFromSignedToUnsignedUpperBitNotSet() { + setFormat(DEC); + setBitSize(8); + setValue(8); + setFormat(U_DEC); + assertEquals(8, getValue()); + } + + @Test + public void testChangingFromSignedToUnsingedUpperBitSet() { + setFormat(DEC); + setValue(-1); + assertEquals(-1, getValue()); + setFormat(U_DEC); + assertEquals(255, getValue()); + + } + + @Test + public void testChangingFromUnsignedToSignedUpperBitNotSet() { + setFormat(U_DEC); + setBitSize(8); + setValue(8); + setFormat(DEC); + assertEquals(8, getValue()); + } + + @Test + public void testChangingFromUnsignedToSingedUpperBitSet() { + setFormat(U_DEC); + setValue(255); + assertEquals(255, getValue()); + setFormat(DEC); + assertEquals(-1, getValue()); + } + + protected void setBitSize(int value) { + runSwing(() -> field.setBitSize(value)); + } +} diff --git a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/textfield/IntegerTextFieldTest.java b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/textfield/IntegerTextFieldTest.java index 512c32f27c..4b3e9f4d61 100644 --- a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/textfield/IntegerTextFieldTest.java +++ b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/textfield/IntegerTextFieldTest.java @@ -15,177 +15,156 @@ */ package docking.widgets.textfield; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static docking.widgets.textfield.integer.IntegerFormat.*; +import static org.junit.Assert.*; import java.math.BigInteger; -import java.util.concurrent.atomic.AtomicIntegerArray; -import javax.swing.JFrame; -import javax.swing.JTextField; -import javax.swing.UIManager; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -import org.junit.After; -import org.junit.Before; import org.junit.Test; -import docking.test.AbstractDockingTest; +public class IntegerTextFieldTest extends AbstractIntegerTextFieldTest { -public class IntegerTextFieldTest extends AbstractDockingTest { - - private JFrame frame; - private IntegerTextField field; - private JTextField textField; - - @Before - public void setUp() throws Exception { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - field = new IntegerTextField(10); - field.setShowNumberMode(true); - textField = (JTextField) field.getComponent(); - frame = new JFrame("Test"); - frame.getContentPane().add(field.getComponent()); - frame.pack(); - frame.setVisible(true); - } - - @After - public void tearDown() throws Exception { - frame.setVisible(false); + @Override + protected IntegerTextField createField() { + return new IntegerTextField(10); } @Test public void testDefaultState() { - assertNull(field.getValue());// no value - assertEquals(0, field.getIntValue());// the "int value" return for null is 0 - assertEquals(0, field.getLongValue()); - assertTrue(!field.isHexMode()); - assertNull(field.getMaxValue()); + assertNull(getBigIntegerValue());// no value + assertEquals(0, getIntValue()); // the "int value" return for null is 0 + assertEquals(0, getValue()); + assertEquals(DEC, getFormat()); + assertNull(getMaxValue()); } @Test public void testTypeValidDecimalNumber() { - triggerText(textField, "123"); - assertEquals(123, field.getIntValue()); + typeText("123"); + assertEquals(123, getIntValue()); } @Test public void testTypeValidHexNumber() { - triggerText(textField, "0x2abcdef"); - assertEquals(0x2abcdef, field.getIntValue()); + typeText("0x2abcdef"); + assertEquals(0x2abcdef, getValue()); } @Test public void testInvalidCharsIgnored() { - triggerText(textField, "123ghijklmnopqrstuvwxyz4"); - assertEquals(1234, field.getIntValue()); + typeText("123ghijklmnopqrstuvwxyz4"); + assertEquals(1234, getValue()); } @Test public void testHexCharsIgnoredInDecimalMode() { - assertTrue(!field.isHexMode()); - triggerText(textField, "123ghijklmnopqrstuvwxyz4"); - assertEquals(1234, field.getIntValue()); + setFormat(HEX); + typeText("123ghijklmnopqrstuvwxyz4"); + assertEquals(1234, getValue()); } @Test public void testXchangesHexMode() { - assertTrue(!field.isHexMode()); - triggerText(textField, "0"); - assertTrue(!field.isHexMode()); - triggerText(textField, "x"); - assertTrue(field.isHexMode()); + assertEquals(DEC, getFormat()); + typeText("0"); + assertEquals(DEC, getFormat()); + typeText("x"); + assertEquals(HEX, getFormat()); triggerBackspace(textField); - assertTrue(!field.isHexMode()); + assertEquals(HEX, getFormat()); } @Test public void testHexModeWithoutPrefix() { - triggerText(textField, "abc");// not allowed when using hex prefix, so expect empty - assertEquals(null, field.getValue()); + setFormat(HEX); + typeText("a"); + assertEquals(null, getBigIntegerValue()); - field.setAllowsHexPrefix(false); - field.setHexMode(); - triggerText(textField, "abc"); - assertEquals(0xabc, field.getIntValue()); + setUsePrefix(false); + typeText("abc"); + assertEquals(0xabc, getValue()); } @Test public void testNegative() { - triggerText(textField, "-123"); - assertEquals(-123, field.getIntValue()); + typeText("-123"); + assertEquals(-123, getValue()); } @Test public void testNegativeHex() { - triggerText(textField, "-0xa"); - assertEquals(-10, field.getIntValue()); + typeText("-0xa"); + assertEquals(-10, getValue()); } @Test public void testNegativeNotAllowed() { - field.setAllowNegativeValues(false); - triggerText(textField, "-123"); - assertEquals(123, field.getIntValue()); + setMinValue(BigInteger.ZERO); + typeText("-123"); + assertEquals(123, getValue()); } @Test public void testSetNegativeWithCurrentNegativeValue() { - field.setValue(-123); - field.setAllowNegativeValues(false); + setValue(-123); + setMinValue(BigInteger.ZERO); assertEquals(null, field.getValue()); } @Test public void testMax() { field.setMaxValue(BigInteger.valueOf(13l)); - triggerText(textField, "12"); - assertEquals(12, field.getIntValue()); + typeText("12"); + assertEquals(12, getValue()); - field.setValue(null); - triggerText(textField, "13"); - assertEquals(13, field.getIntValue()); + setText(""); + typeText("13"); + assertEquals(13, getValue()); - field.setValue(null); - triggerText(textField, "14");// four should be ignored - assertEquals(1, field.getIntValue()); + setText(""); + typeText("14");// four should be ignored + assertEquals(1, getValue()); } @Test public void testSetMaxToValueSmallerThanCurrent() { - field.setValue(500); + setValue(500); field.setMaxValue(BigInteger.valueOf(400)); - assertEquals(400, field.getIntValue()); + assertNull(field.getValue()); + } + + @Test + public void testMinSetTo1() { + field.setMinValue(BigInteger.ONE); + setValue(0); + assertEquals(1, getValue()); + } @Test public void testMaxInHex() { field.setMaxValue(BigInteger.valueOf(0xd)); - triggerText(textField, "0xc"); - assertEquals(12, field.getIntValue()); + typeText("0xc"); + assertEquals(12, getValue()); - field.setValue(null); - triggerText(textField, "0xd"); - assertEquals(13, field.getIntValue()); + setText(""); + typeText("0xd"); + assertEquals(13, getValue()); - field.setValue(null); - triggerText(textField, "0xe");// e should be ignored - assertEquals(0, field.getIntValue()); + setText(""); + typeText("0xe");// e should be ignored + assertEquals(0, getValue()); } @Test public void testSwitchingHexMode() { - field.setValue(255); + setValue(255); assertEquals("255", field.getText()); - field.setHexMode(); + setFormat(HEX); assertEquals("0xff", field.getText()); - field.setDecimalMode(); + setFormat(DEC); assertEquals("255", field.getText()); } @@ -194,7 +173,7 @@ public class IntegerTextFieldTest extends AbstractDockingTest { TestChangeListener listener = new TestChangeListener(); field.addChangeListener(listener); - triggerText(textField, "123"); + typeText("123"); assertEquals(3, listener.count); assertEquals(1, listener.values.get(0)); assertEquals(12, listener.values.get(1)); @@ -207,129 +186,149 @@ public class IntegerTextFieldTest extends AbstractDockingTest { @Test public void testChangeListenerAfterSwitchingModes() { - triggerText(textField, "123"); + setFormat(DEC); + typeText("12"); TestChangeListener listener = new TestChangeListener(); field.addChangeListener(listener); - setHexMode(); + setFormat(HEX); + assertEquals("0xc", getText()); assertEquals(2, listener.count); - assertEquals(123, listener.values.get(1)); + assertEquals(12, listener.values.get(1)); } @Test public void testNegativeHexFromValue() { - field.setValue(-255); - setHexMode(); + setValue(-255); + setFormat(HEX); assertEquals("-0xff", field.getText()); } @Test public void testNullValue() { - field.setValue(12); + setValue(12); assertEquals("12", field.getText()); - field.setValue(null); + setText(""); assertEquals("", field.getText()); - assertEquals(0, field.getIntValue()); - assertEquals(0l, field.getLongValue()); - assertEquals(null, field.getValue()); + assertEquals(0, getValue()); + assertEquals(null, getBigIntegerValue()); } @Test public void testHexValueInDontRequireHexPrefixMode() { - field.setAllowsHexPrefix(false); - field.setHexMode(); - field.setValue(255); + field.setUseNumberPrefix(false); + field.setFormat(HEX); + setValue(255); assertEquals("ff", field.getText()); } + @Test + public void testAutoModeSwitchingIsOffWhenPrefixNotUsed() { + field.setUseNumberPrefix(false); + field.setFormat(HEX); + typeText("15"); + assertEquals(HEX, getFormat()); + assertEquals(21, getValue()); + field.setFormat(DEC); + field.setText(""); + typeText("0x15"); + assertEquals(DEC, getFormat()); + assertEquals("015", getText()); + assertEquals(15, getValue()); // the 0x should have been ignored + } + @Test public void testSetNotAllowNegativeModeWhileCurrentValueIsNegative() { - field.setValue(-10); - field.setAllowNegativeValues(false); + setValue(-10); + setMinValue(BigInteger.ZERO); assertEquals("", field.getText()); - assertEquals(0, field.getIntValue()); + assertEquals(0, getValue()); } @Test public void testSetLongValue() { - field.setValue(100L); + setValue(100L); assertEquals(100L, field.getLongValue()); - assertEquals(100, field.getIntValue()); + assertEquals(100, getValue()); } @Test public void testSettingNegativeNumberWhenNegativesArentAllowed() { - field.setValue(10); - field.setAllowNegativeValues(false); - field.setValue(-10); + setValue(10); + setMinValue(BigInteger.ZERO); + setValue(-10); assertEquals("", field.getText()); } @Test public void testUseHexPrefixUpdatesTextField() { - field.setAllowsHexPrefix(false); - field.setHexMode(); - field.setValue(255); + field.setUseNumberPrefix(false); + setFormat(HEX); + setValue(255); assertEquals("ff", field.getText()); - field.setAllowsHexPrefix(true); + field.setUseNumberPrefix(true); assertEquals("0xff", field.getText()); } @Test public void testPastingBadText() { - field.setHexMode(); - field.setValue(0); + setFormat(HEX); + setValue(0); assertFalse(field.setText("asdf 0x azzz")); } @Test public void testSetText() { - field.setHexMode(); - field.setValue(0); + setFormat(HEX); + setValue(0); assertTrue(field.setText("0x15")); - assertEquals(0x15, field.getIntValue()); + assertEquals(0x15, getValue()); } @Test public void testSetTextWithInvalidValue() { - field.setHexMode(); - field.setValue(0); + setFormat(HEX); + setValue(0); assertFalse(field.setText("bad value")); - assertEquals(0, field.getIntValue()); - assertTrue(field.isHexMode()); + assertEquals(0, getValue()); + assertEquals(HEX, getFormat()); } @Test public void testSucessfulSetTextChangesHexMode() { - field.setHexMode(); - field.setValue(0); + setFormat(HEX); + setValue(0); assertTrue(field.setText("33")); - assertEquals(33, field.getIntValue()); - assertFalse(field.isHexMode()); + assertEquals(33, getValue()); + assertEquals(DEC, getFormat()); assertTrue(field.setText("0x33")); - assertEquals(0x33, field.getIntValue()); - assertTrue(field.isHexMode()); - + assertEquals(0x33, getValue()); + assertEquals(HEX, getFormat()); } - private void setHexMode() { - runSwing(() -> field.setHexMode()); - waitForSwing(); + @Test + public void testMinValueOfOneDecimalFormat() { + setFormat(DEC); + field.setMinValue(BigInteger.ONE); + typeText("0"); + assertEquals("", field.getText()); + typeText("1"); + assertEquals("1", field.getText()); } - class TestChangeListener implements ChangeListener { - volatile int count; - private AtomicIntegerArray values = new AtomicIntegerArray(10); - - @Override - public void stateChanged(ChangeEvent e) { - values.set(count++, field.getIntValue()); - } - + @Test + public void testMinValueOfOneHexFormat() { + setFormat(HEX); + field.setMinValue(BigInteger.ONE); + typeText("0x1"); + assertEquals("0x1", field.getText()); } + protected void setMinValue(BigInteger minValue) { + runSwing(() -> field.setMinValue(minValue)); + } }