GP-3049 Improved Register Manager provider

This commit is contained in:
ghidragon
2026-04-20 16:27:25 -04:00
parent 80bcf6dada
commit 220e9042d1
39 changed files with 2029 additions and 1094 deletions
@@ -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);
@@ -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());
@@ -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();
@@ -71,24 +71,27 @@
</BLOCKQUOTE>
<BLOCKQUOTE>
<H3><A name="tool_buttons"></A>Tool Buttons</H3>
</BLOCKQUOTE>
<H3><A name="tool_buttons"></A>Toolbar Buttons</H3>
<BLOCKQUOTE>
<BLOCKQUOTE>
<P><IMG src="images/locationIn.gif">Toggles whether or not to select the row in the
<P><IMG src="images/locationIn.gif">&nbsp;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.</P>
<P><IMG src="images/edit-delete.png">Deletes the register value associations for all the
<P><IMG src="Icons.ADD_ICON">&nbsp;Brings up the <I>Add Register Value Range</I> dialog for setting a
value over an address range for the selected register. See the
<A href="">Edit Address Range</A> section below for dialog details as this action shares the
same dialog as the edit range action.</P>
<P><IMG src="images/edit-delete.png">&nbsp;Deletes the register value associations for all the
selected ranges in the table.</P>
<P><IMG src="Icons.MAKE_SELECTION_ICON">Creates a selection in the browser for all the address
<P><IMG src="Icons.MAKE_SELECTION_ICON">&nbsp;Creates a selection in the browser for all the address
ranges selected in the register values table.</P>
<P><IMG src="images/view-filter.png">Filters out all registers in the register tree that
<P><IMG src="images/view-filter.png">&nbsp;Filters out all registers in the register tree that
don't have any associated values (default or otherwise).</P>
<H3><BR>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

@@ -31,6 +31,7 @@ import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.dialogs.StringChoices;
import docking.widgets.table.*;
import docking.widgets.textfield.IntegerTextField;
import docking.widgets.textfield.integer.IntegerFormat;
import ghidra.docking.settings.*;
import ghidra.framework.preferences.Preferences;
import ghidra.util.BigEndianDataConverter;
@@ -734,7 +735,8 @@ public abstract class AbstractSettingsDialog extends DialogComponentProvider {
}
private void updateHexMode() {
intHexModeMap.put(rowobject.definition.getName(), intTextField.isHexMode());
intHexModeMap.put(rowobject.definition.getName(),
intTextField.getFormat() == IntegerFormat.HEX);
}
private Number getNumber() {
@@ -805,14 +807,14 @@ public abstract class AbstractSettingsDialog extends DialogComponentProvider {
mode = NUMBER;
NumberSettingsDefinition def = (NumberSettingsDefinition) rowobject.definition;
if (def.isHexModePreferred() || isHexModeEnabled(def)) {
intTextField.setHexMode();
intTextField.setFormat(IntegerFormat.HEX);
}
else {
intTextField.setDecimalMode();
intTextField.setFormat(IntegerFormat.DEC);
}
intTextField.setMaxValue(def.getMaxValue());
intTextField.setAllowNegativeValues(def.allowNegativeValue());
intTextField.setMinValue(def.allowNegativeValue() ? null : BigInteger.ZERO);
if (value == null) {
intTextField.setValue(null);
@@ -31,6 +31,7 @@ import org.apache.commons.lang3.StringUtils;
import docking.widgets.DropDownSelectionTextField;
import docking.widgets.table.FocusableEditor;
import docking.widgets.textfield.IntegerTextField;
import docking.widgets.textfield.integer.IntegerFormat;
import generic.theme.GThemeDefaults.Colors.Palette;
import ghidra.app.util.AddressInput;
import ghidra.program.model.address.Address;
@@ -163,7 +164,7 @@ class VarnodeLocationCellEditor extends AbstractCellEditor
private Component createStackOffsetEditor(VarnodeInfo varnode) {
offsetInput = new IntegerTextField();
offsetInput.setHexMode();
offsetInput.setFormat(IntegerFormat.HEX);
Address address = varnode.getAddress();
if (address != null) {
offsetInput.setValue(address.getOffset());
@@ -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 javax.swing.*;
import javax.swing.table.TableCellEditor;
import docking.widgets.textfield.IntegerTextField;
import docking.widgets.textfield.integer.IntegerFormat;
import generic.theme.GThemeDefaults.Colors.Palette;
class VarnodeSizeCellEditor extends AbstractCellEditor implements TableCellEditor {
@@ -56,8 +57,8 @@ class VarnodeSizeCellEditor extends AbstractCellEditor implements TableCellEdito
int row, int column) {
input = new IntegerTextField();
input.setAllowNegativeValues(false);
input.setDecimalMode();
input.setMinValue(BigInteger.ZERO);
input.setFormat(IntegerFormat.DEC);
Integer size = (Integer) value;
if (size != null) {
input.setValue(size.longValue());
@@ -16,6 +16,7 @@
package ghidra.app.plugin.core.memory;
import java.awt.*;
import java.math.BigInteger;
import java.util.List;
import javax.swing.*;
@@ -28,6 +29,7 @@ import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.label.GDLabel;
import docking.widgets.label.GLabel;
import docking.widgets.textfield.IntegerTextField;
import docking.widgets.textfield.integer.IntegerFormat;
import ghidra.app.plugin.core.memory.AddBlockModel.InitializedType;
import ghidra.app.plugin.core.misc.RegisterField;
import ghidra.app.util.AddressInput;
@@ -474,16 +476,16 @@ class AddBlockDialog extends DialogComponentProvider implements ChangeListener {
JPanel schemePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
schemeDestByteCountField = new IntegerTextField(4, 1);
schemeDestByteCountField.setAllowNegativeValues(false);
schemeDestByteCountField.setAllowsHexPrefix(false);
schemeDestByteCountField.setDecimalMode();
schemeDestByteCountField.setMinValue(BigInteger.ZERO);
schemeDestByteCountField.setUseNumberPrefix(false);
schemeDestByteCountField.setFormat(IntegerFormat.DEC);
schemeDestByteCountField.addChangeListener(ev -> 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");
@@ -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;
}
@@ -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<Program> 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;
}
}
}
@@ -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());
@@ -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) {
@@ -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<RegisterWrapper> registerComboBox;
private FixedBitSizeValueField registerValueField;
private JList addressRangeList;
private FixedSizeIntegerTextField registerValueField;
private JList<String> 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<String>();
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<RegisterWrapper> {
@Override
public Component getListCellRendererComponent(JList<? extends RegisterWrapper> 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<RegisterWrapper> {
@@ -240,14 +251,24 @@ class RegisterWrapper implements Comparable<RegisterWrapper> {
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<String> 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();
}
@@ -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;
@@ -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);
@@ -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<AddressSpace> ALL_MEMORY_SPACES = s -> s.isMemorySpace();
public final static Predicate<AddressSpace> LOADED_MEMORY_SPACES = s -> s.isLoadedMemorySpace();
private HexDecimalModeTextField textField;
private MultiFormatTextField textField;
private AddressSpaceField addressSpaceField;
AddressEvaluator addressEvaluator;
private Predicate<AddressSpace> 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();
}
@@ -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<HexLong> {
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()));
@@ -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);
@@ -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()) {
@@ -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();
}
@@ -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;
}
@@ -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<T extends Number>
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);
@@ -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());
@@ -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());
}
@@ -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.
*
* <P>
* This field does continuous checking, so you can't enter a bad value.
*<P>
* 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).
* <P>
* 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.
*
* <P>
* There are several configuration options as follows:
* <UL>
* <LI>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.</LI>
* <LI>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.</LI>
* <LI>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)}</LI>
* </UL>
*
*/
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);
}
}
@@ -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<Boolean> 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);
}
}
@@ -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;
}
}
@@ -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<IntegerFormat> formats;
private int currentFormatIndex;
public MultiFormatTextField(int columns, List<IntegerFormat> formats,
Consumer<IntegerFormat> 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);
}
}
@@ -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<Integer> {
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);
}
}
@@ -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<Long> {
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);
}
}
@@ -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<T extends AbstractIntegerTextField>
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());
}
}
}
@@ -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<FixedSizeIntegerTextField> {
@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));
}
}
@@ -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<IntegerTextField> {
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));
}
}