2021-09-18 15:33:52 +00:00
|
|
|
package the.bytecode.club.bytecodeviewer.gui.hexviewer;
|
|
|
|
|
|
|
|
import java.awt.Component;
|
|
|
|
import java.awt.Container;
|
|
|
|
import java.awt.Dimension;
|
|
|
|
import java.awt.Insets;
|
|
|
|
import java.awt.LayoutManager;
|
|
|
|
import java.awt.event.FocusAdapter;
|
|
|
|
import java.awt.event.FocusEvent;
|
|
|
|
import java.beans.PropertyChangeEvent;
|
|
|
|
import java.beans.PropertyChangeListener;
|
|
|
|
import java.text.ParseException;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import javax.annotation.Nonnull;
|
|
|
|
import javax.annotation.ParametersAreNonnullByDefault;
|
|
|
|
import javax.swing.JMenuItem;
|
|
|
|
import javax.swing.JPanel;
|
|
|
|
import javax.swing.JSpinner;
|
|
|
|
import javax.swing.JTextField;
|
|
|
|
import javax.swing.SpinnerNumberModel;
|
|
|
|
import javax.swing.SwingUtilities;
|
|
|
|
import javax.swing.event.ChangeEvent;
|
|
|
|
import javax.swing.event.ChangeListener;
|
|
|
|
import javax.swing.event.DocumentEvent;
|
|
|
|
import javax.swing.event.DocumentListener;
|
|
|
|
import javax.swing.text.JTextComponent;
|
|
|
|
import org.exbin.bined.CodeAreaUtils;
|
|
|
|
import org.exbin.bined.CodeCharactersCase;
|
|
|
|
import org.exbin.bined.PositionCodeType;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Spinner supporting multiple bases.
|
|
|
|
*/
|
|
|
|
@ParametersAreNonnullByDefault
|
|
|
|
public class BaseSwitchableSpinnerPanel extends javax.swing.JPanel {
|
|
|
|
|
|
|
|
private boolean adjusting;
|
|
|
|
private final PositionSpinnerEditor spinnerEditor;
|
|
|
|
private static final String SPINNER_PROPERTY = "value";
|
|
|
|
|
|
|
|
public BaseSwitchableSpinnerPanel() {
|
|
|
|
initComponents();
|
|
|
|
spinnerEditor = new PositionSpinnerEditor(spinner);
|
|
|
|
spinner.setEditor(spinnerEditor);
|
|
|
|
init();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void init() {
|
|
|
|
// Spinner selection workaround from http://forums.sun.com/thread.jspa?threadID=409748&forumID=57
|
|
|
|
spinnerEditor.getTextField().addFocusListener(new FocusAdapter() {
|
|
|
|
@Override
|
|
|
|
public void focusGained(FocusEvent e) {
|
|
|
|
if (e.getSource() instanceof JTextComponent) {
|
|
|
|
final JTextComponent textComponent = ((JTextComponent) e.getSource());
|
|
|
|
SwingUtilities.invokeLater(textComponent::selectAll);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Dimension preferredSize = baseSwitchButton.getPreferredSize();
|
|
|
|
setPreferredSize(new Dimension(preferredSize.width * 4, preferredSize.height));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This method is called from within the constructor to initialize the form.
|
|
|
|
* WARNING: Do NOT modify this code. The content of this method is always
|
|
|
|
* regenerated by the Form Editor.
|
|
|
|
*/
|
|
|
|
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
|
|
|
private void initComponents() {
|
|
|
|
|
|
|
|
baseSwitchPopupMenu = new javax.swing.JPopupMenu();
|
|
|
|
octalMenuItem = new javax.swing.JMenuItem();
|
|
|
|
decimalMenuItem = new javax.swing.JMenuItem();
|
|
|
|
hexadecimalMenuItem = new javax.swing.JMenuItem();
|
|
|
|
baseSwitchButton = new javax.swing.JButton();
|
|
|
|
spinner = new javax.swing.JSpinner();
|
|
|
|
|
|
|
|
octalMenuItem.setText("OCT");
|
|
|
|
octalMenuItem.setToolTipText("Octal");
|
2021-12-19 23:24:17 +00:00
|
|
|
octalMenuItem.addActionListener(this::octalMenuItemActionPerformed);
|
2021-09-18 15:33:52 +00:00
|
|
|
baseSwitchPopupMenu.add(octalMenuItem);
|
|
|
|
|
|
|
|
decimalMenuItem.setText("DEC");
|
|
|
|
decimalMenuItem.setToolTipText("Decimal");
|
2021-12-19 23:24:17 +00:00
|
|
|
decimalMenuItem.addActionListener(this::decimalMenuItemActionPerformed);
|
2021-09-18 15:33:52 +00:00
|
|
|
baseSwitchPopupMenu.add(decimalMenuItem);
|
|
|
|
|
|
|
|
hexadecimalMenuItem.setText("HEX");
|
|
|
|
hexadecimalMenuItem.setToolTipText("Hexadecimal");
|
2021-12-19 23:24:17 +00:00
|
|
|
hexadecimalMenuItem.addActionListener(this::hexadecimalMenuItemActionPerformed);
|
2021-09-18 15:33:52 +00:00
|
|
|
baseSwitchPopupMenu.add(hexadecimalMenuItem);
|
|
|
|
|
|
|
|
setPreferredSize(new java.awt.Dimension(400, 300));
|
|
|
|
|
|
|
|
baseSwitchButton.setText("DEC");
|
|
|
|
baseSwitchButton.setToolTipText("Decimal");
|
|
|
|
baseSwitchButton.setComponentPopupMenu(baseSwitchPopupMenu);
|
2021-12-19 23:24:17 +00:00
|
|
|
baseSwitchButton.addActionListener(this::baseSwitchButtonActionPerformed);
|
2021-09-18 15:33:52 +00:00
|
|
|
|
|
|
|
spinner.setModel(new javax.swing.SpinnerNumberModel(0L, null, null, 1L));
|
|
|
|
|
|
|
|
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
|
|
|
this.setLayout(layout);
|
|
|
|
layout.setHorizontalGroup(
|
|
|
|
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
|
|
|
.addGroup(layout.createSequentialGroup()
|
|
|
|
.addComponent(baseSwitchButton, javax.swing.GroupLayout.PREFERRED_SIZE, 65, javax.swing.GroupLayout.PREFERRED_SIZE)
|
|
|
|
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
|
|
|
.addComponent(spinner, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))
|
|
|
|
);
|
|
|
|
layout.setVerticalGroup(
|
|
|
|
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
|
|
|
.addComponent(spinner)
|
|
|
|
.addComponent(baseSwitchButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
|
|
|
);
|
|
|
|
}// </editor-fold>//GEN-END:initComponents
|
|
|
|
|
|
|
|
private void baseSwitchButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_baseSwitchButtonActionPerformed
|
|
|
|
PositionCodeType positionCodeType = spinnerEditor.getPositionCodeType();
|
|
|
|
switch (positionCodeType) {
|
|
|
|
case OCTAL: {
|
|
|
|
switchNumBase(PositionCodeType.DECIMAL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case DECIMAL: {
|
|
|
|
switchNumBase(PositionCodeType.HEXADECIMAL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case HEXADECIMAL: {
|
|
|
|
switchNumBase(PositionCodeType.OCTAL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
throw CodeAreaUtils.getInvalidTypeException(positionCodeType);
|
|
|
|
}
|
|
|
|
}//GEN-LAST:event_baseSwitchButtonActionPerformed
|
|
|
|
|
|
|
|
private void octalMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_octalMenuItemActionPerformed
|
|
|
|
switchNumBase(PositionCodeType.OCTAL);
|
|
|
|
}//GEN-LAST:event_octalMenuItemActionPerformed
|
|
|
|
|
|
|
|
private void decimalMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_decimalMenuItemActionPerformed
|
|
|
|
switchNumBase(PositionCodeType.DECIMAL);
|
|
|
|
}//GEN-LAST:event_decimalMenuItemActionPerformed
|
|
|
|
|
|
|
|
private void hexadecimalMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_hexadecimalMenuItemActionPerformed
|
|
|
|
switchNumBase(PositionCodeType.HEXADECIMAL);
|
|
|
|
}//GEN-LAST:event_hexadecimalMenuItemActionPerformed
|
|
|
|
|
|
|
|
// Variables declaration - do not modify//GEN-BEGIN:variables
|
|
|
|
private javax.swing.JButton baseSwitchButton;
|
|
|
|
private javax.swing.JPopupMenu baseSwitchPopupMenu;
|
|
|
|
private javax.swing.JMenuItem decimalMenuItem;
|
|
|
|
private javax.swing.JMenuItem hexadecimalMenuItem;
|
|
|
|
private javax.swing.JMenuItem octalMenuItem;
|
|
|
|
private javax.swing.JSpinner spinner;
|
|
|
|
// End of variables declaration//GEN-END:variables
|
|
|
|
|
|
|
|
private void switchNumBase(PositionCodeType codeType) {
|
|
|
|
adjusting = true;
|
|
|
|
long value = getValue();
|
|
|
|
int position = codeType.ordinal();
|
|
|
|
baseSwitchButton.setText(codeType.name().substring(0, 3));
|
|
|
|
baseSwitchButton.setToolTipText(((JMenuItem) baseSwitchPopupMenu.getComponent(position)).getToolTipText());
|
|
|
|
spinnerEditor.setPositionCodeType(codeType);
|
|
|
|
setValue(value);
|
|
|
|
adjusting = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public long getValue() {
|
|
|
|
return (Long) spinner.getValue();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setValue(long value) {
|
|
|
|
spinnerEditor.setPositionValue(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void acceptInput() {
|
|
|
|
try {
|
|
|
|
spinner.commitEdit();
|
|
|
|
} catch (ParseException ex) {
|
|
|
|
// Ignore parse exception
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void initFocus() {
|
|
|
|
/* ((JSpinner.DefaultEditor) positionSpinner.getEditor()) */
|
|
|
|
spinnerEditor.getTextField().requestFocusInWindow();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setMinimum(long minimum) {
|
|
|
|
((SpinnerNumberModel) spinner.getModel()).setMinimum(minimum);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setMaximum(long maximum) {
|
|
|
|
((SpinnerNumberModel) spinner.getModel()).setMaximum(maximum);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void revalidateSpinner() {
|
|
|
|
spinner.revalidate();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void addChangeListener(ChangeListener changeListener) {
|
|
|
|
spinner.addChangeListener(changeListener);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void removeChangeListener(ChangeListener changeListener) {
|
|
|
|
spinner.removeChangeListener(changeListener);
|
|
|
|
}
|
|
|
|
|
|
|
|
@ParametersAreNonnullByDefault
|
|
|
|
private class PositionSpinnerEditor extends JPanel implements ChangeListener, PropertyChangeListener, LayoutManager {
|
|
|
|
|
|
|
|
private static final int LENGTH_LIMIT = 21;
|
|
|
|
|
|
|
|
private PositionCodeType positionCodeType = PositionCodeType.DECIMAL;
|
|
|
|
|
|
|
|
private final char[] cache = new char[LENGTH_LIMIT];
|
|
|
|
|
|
|
|
private final JTextField textField;
|
|
|
|
private final JSpinner spinner;
|
|
|
|
|
|
|
|
public PositionSpinnerEditor(JSpinner spinner) {
|
|
|
|
this.spinner = spinner;
|
|
|
|
textField = new JTextField();
|
|
|
|
|
|
|
|
init();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void init() {
|
|
|
|
textField.setName("Spinner.textField");
|
|
|
|
textField.setText(getPositionAsString((Long) spinner.getValue()));
|
|
|
|
textField.addPropertyChangeListener(this);
|
|
|
|
textField.getDocument().addDocumentListener(new DocumentListener() {
|
|
|
|
private final PropertyChangeEvent changeEvent = new PropertyChangeEvent(textField, SPINNER_PROPERTY, null, null);
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void changedUpdate(DocumentEvent e) {
|
|
|
|
notifyChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void removeUpdate(DocumentEvent e) {
|
|
|
|
notifyChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void insertUpdate(DocumentEvent e) {
|
|
|
|
notifyChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void notifyChanged() {
|
|
|
|
propertyChange(changeEvent);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
textField.setEditable(true);
|
|
|
|
textField.setInheritsPopupMenu(true);
|
|
|
|
|
|
|
|
String toolTipText = spinner.getToolTipText();
|
|
|
|
if (toolTipText != null) {
|
|
|
|
textField.setToolTipText(toolTipText);
|
|
|
|
}
|
|
|
|
|
|
|
|
add(textField);
|
|
|
|
|
|
|
|
setLayout(this);
|
|
|
|
spinner.addChangeListener(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
private JTextField getTextField() {
|
|
|
|
return textField;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
private JSpinner getSpinner() {
|
|
|
|
return spinner;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void stateChanged(ChangeEvent e) {
|
|
|
|
if (adjusting) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JSpinner sourceSpinner = (JSpinner) (e.getSource());
|
2021-12-19 23:24:17 +00:00
|
|
|
SwingUtilities.invokeLater(() -> textField.setText(getPositionAsString((Long) sourceSpinner.getValue())));
|
2021-09-18 15:33:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void propertyChange(PropertyChangeEvent e) {
|
|
|
|
if (adjusting) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JSpinner sourceSpinner = getSpinner();
|
|
|
|
|
|
|
|
Object source = e.getSource();
|
|
|
|
String name = e.getPropertyName();
|
|
|
|
if ((source instanceof JTextField) && SPINNER_PROPERTY.equals(name)) {
|
|
|
|
Long lastValue = (Long) sourceSpinner.getValue();
|
|
|
|
|
|
|
|
// Try to set the new value
|
|
|
|
try {
|
|
|
|
sourceSpinner.setValue(valueOfPosition(getTextField().getText()));
|
|
|
|
} catch (IllegalArgumentException iae) {
|
|
|
|
// SpinnerModel didn't like new value, reset
|
|
|
|
try {
|
|
|
|
sourceSpinner.setValue(lastValue);
|
|
|
|
} catch (IllegalArgumentException iae2) {
|
|
|
|
// Still bogus, nothing else we can do, the
|
|
|
|
// SpinnerModel and JFormattedTextField are now out
|
|
|
|
// of sync.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setPositionValue(long positionValue) {
|
|
|
|
textField.setText(getPositionAsString(positionValue));
|
|
|
|
spinner.setValue(positionValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void addLayoutComponent(String name, Component comp) {
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void removeLayoutComponent(Component comp) {
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the size of the parents insets.
|
|
|
|
*/
|
|
|
|
@Nonnull
|
|
|
|
private Dimension insetSize(Container parent) {
|
|
|
|
Insets insets = parent.getInsets();
|
|
|
|
int width = insets.left + insets.right;
|
|
|
|
int height = insets.top + insets.bottom;
|
|
|
|
return new Dimension(width, height);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
@Override
|
|
|
|
public Dimension preferredLayoutSize(Container parent) {
|
|
|
|
Dimension preferredSize = insetSize(parent);
|
|
|
|
if (parent.getComponentCount() > 0) {
|
|
|
|
Dimension childSize = getComponent(0).getPreferredSize();
|
|
|
|
preferredSize.width += childSize.width;
|
|
|
|
preferredSize.height += childSize.height;
|
|
|
|
}
|
|
|
|
return preferredSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
@Override
|
|
|
|
public Dimension minimumLayoutSize(Container parent) {
|
|
|
|
Dimension minimumSize = insetSize(parent);
|
|
|
|
if (parent.getComponentCount() > 0) {
|
|
|
|
Dimension childSize = getComponent(0).getMinimumSize();
|
|
|
|
minimumSize.width += childSize.width;
|
|
|
|
minimumSize.height += childSize.height;
|
|
|
|
}
|
|
|
|
return minimumSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void layoutContainer(Container parent) {
|
|
|
|
if (parent.getComponentCount() > 0) {
|
|
|
|
Insets insets = parent.getInsets();
|
|
|
|
int width = parent.getWidth() - (insets.left + insets.right);
|
|
|
|
int height = parent.getHeight() - (insets.top + insets.bottom);
|
|
|
|
getComponent(0).setBounds(insets.left, insets.top, width, height);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
public PositionCodeType getPositionCodeType() {
|
|
|
|
return positionCodeType;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setPositionCodeType(PositionCodeType positionCodeType) {
|
|
|
|
this.positionCodeType = positionCodeType;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
private String getPositionAsString(long position) {
|
|
|
|
if (position < 0) {
|
|
|
|
return "-" + getNonNegativePostionAsString(-position);
|
|
|
|
}
|
|
|
|
return getNonNegativePostionAsString(position);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nonnull
|
|
|
|
private String getNonNegativePostionAsString(long position) {
|
|
|
|
Arrays.fill(cache, ' ');
|
|
|
|
CodeAreaUtils.longToBaseCode(cache, 0, position, positionCodeType.getBase(), LENGTH_LIMIT, false, CodeCharactersCase.LOWER);
|
|
|
|
return new String(cache).trim();
|
|
|
|
}
|
|
|
|
|
|
|
|
private long valueOfPosition(String position) {
|
|
|
|
return Long.parseLong(position, positionCodeType.getBase());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|