diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/BaseSwitchableSpinnerPanel.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/BaseSwitchableSpinnerPanel.java
new file mode 100644
index 00000000..f37eea4a
--- /dev/null
+++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/BaseSwitchableSpinnerPanel.java
@@ -0,0 +1,425 @@
+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.
+ */
+ @SuppressWarnings("unchecked")
+ // //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");
+ octalMenuItem.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ octalMenuItemActionPerformed(evt);
+ }
+ });
+ baseSwitchPopupMenu.add(octalMenuItem);
+
+ decimalMenuItem.setText("DEC");
+ decimalMenuItem.setToolTipText("Decimal");
+ decimalMenuItem.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ decimalMenuItemActionPerformed(evt);
+ }
+ });
+ baseSwitchPopupMenu.add(decimalMenuItem);
+
+ hexadecimalMenuItem.setText("HEX");
+ hexadecimalMenuItem.setToolTipText("Hexadecimal");
+ hexadecimalMenuItem.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ hexadecimalMenuItemActionPerformed(evt);
+ }
+ });
+ baseSwitchPopupMenu.add(hexadecimalMenuItem);
+
+ setPreferredSize(new java.awt.Dimension(400, 300));
+
+ baseSwitchButton.setText("DEC");
+ baseSwitchButton.setToolTipText("Decimal");
+ baseSwitchButton.setComponentPopupMenu(baseSwitchPopupMenu);
+ baseSwitchButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ baseSwitchButtonActionPerformed(evt);
+ }
+ });
+
+ 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)
+ );
+ }// //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());
+ SwingUtilities.invokeLater(() -> {
+ textField.setText(getPositionAsString((Long) sourceSpinner.getValue()));
+ });
+ }
+
+ @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());
+ }
+ }
+}
diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/GoToBinaryPanel.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/GoToBinaryPanel.java
new file mode 100644
index 00000000..1565135b
--- /dev/null
+++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/GoToBinaryPanel.java
@@ -0,0 +1,309 @@
+package the.bytecode.club.bytecodeviewer.gui.hexviewer;
+
+import java.util.ResourceBundle;
+import javax.annotation.Nonnull;
+import javax.annotation.ParametersAreNonnullByDefault;
+import org.exbin.bined.CodeAreaUtils;
+
+/**
+ * Go-to position panel for binary editor.
+ */
+@ParametersAreNonnullByDefault
+public class GoToBinaryPanel extends javax.swing.JPanel {
+
+ private long cursorPosition;
+ private long maxPosition;
+ private GoToBinaryPositionMode goToMode = GoToBinaryPositionMode.FROM_START;
+
+ public GoToBinaryPanel() {
+ initComponents();
+
+ baseSwitchableSpinnerPanel.setMinimum(0l);
+ baseSwitchableSpinnerPanel.addChangeListener((javax.swing.event.ChangeEvent evt) -> {
+ updateTargetPosition();
+ });
+ }
+
+ /**
+ * 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.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ positionTypeButtonGroup = new javax.swing.ButtonGroup();
+ currentPositionLabel = new javax.swing.JLabel();
+ currentPositionTextField = new javax.swing.JTextField();
+ goToPanel = new javax.swing.JPanel();
+ fromStartRadioButton = new javax.swing.JRadioButton();
+ fromEndRadioButton = new javax.swing.JRadioButton();
+ fromCursorRadioButton = new javax.swing.JRadioButton();
+ positionLabel = new javax.swing.JLabel();
+ baseSwitchableSpinnerPanel = new BaseSwitchableSpinnerPanel();
+ targetPositionLabel = new javax.swing.JLabel();
+ targetPositionTextField = new javax.swing.JTextField();
+
+ currentPositionLabel.setText("Current Position");
+
+ currentPositionTextField.setEditable(false);
+ currentPositionTextField.setText("0");
+
+ goToPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Go To Position"));
+
+ positionTypeButtonGroup.add(fromStartRadioButton);
+ fromStartRadioButton.setSelected(true);
+ fromStartRadioButton.setText("Position from start");
+ fromStartRadioButton.addItemListener(new java.awt.event.ItemListener() {
+ public void itemStateChanged(java.awt.event.ItemEvent evt) {
+ fromStartRadioButtonItemStateChanged(evt);
+ }
+ });
+
+ positionTypeButtonGroup.add(fromEndRadioButton);
+ fromEndRadioButton.setText("Position from end");
+ fromEndRadioButton.addItemListener(new java.awt.event.ItemListener() {
+ public void itemStateChanged(java.awt.event.ItemEvent evt) {
+ fromEndRadioButtonItemStateChanged(evt);
+ }
+ });
+
+ positionTypeButtonGroup.add(fromCursorRadioButton);
+ fromCursorRadioButton.setText("Position relative to cursor");
+ fromCursorRadioButton.addItemListener(new java.awt.event.ItemListener() {
+ public void itemStateChanged(java.awt.event.ItemEvent evt) {
+ fromCursorRadioButtonItemStateChanged(evt);
+ }
+ });
+
+ positionLabel.setText("Position");
+
+ javax.swing.GroupLayout goToPanelLayout = new javax.swing.GroupLayout(goToPanel);
+ goToPanel.setLayout(goToPanelLayout);
+ goToPanelLayout.setHorizontalGroup(
+ goToPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(fromStartRadioButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(fromCursorRadioButton, javax.swing.GroupLayout.DEFAULT_SIZE, 412, Short.MAX_VALUE)
+ .addComponent(fromEndRadioButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addGroup(goToPanelLayout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(goToPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(baseSwitchableSpinnerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addGroup(goToPanelLayout.createSequentialGroup()
+ .addComponent(positionLabel)
+ .addGap(0, 0, Short.MAX_VALUE)))
+ .addContainerGap())
+ );
+ goToPanelLayout.setVerticalGroup(
+ goToPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(goToPanelLayout.createSequentialGroup()
+ .addComponent(fromStartRadioButton, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(fromEndRadioButton, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(fromCursorRadioButton, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(positionLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(baseSwitchableSpinnerPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+
+ targetPositionLabel.setText("Target Position");
+
+ targetPositionTextField.setEditable(false);
+ targetPositionTextField.setText("0");
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(currentPositionTextField)
+ .addComponent(goToPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(targetPositionTextField)
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(currentPositionLabel)
+ .addComponent(targetPositionLabel))
+ .addGap(0, 0, Short.MAX_VALUE)))
+ .addContainerGap())
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(currentPositionLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(currentPositionTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(goToPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(targetPositionLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(targetPositionTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+ }// //GEN-END:initComponents
+
+ private void fromStartRadioButtonItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_fromStartRadioButtonItemStateChanged
+ if (fromStartRadioButton.isSelected()) {
+ switchGoToMode(GoToBinaryPositionMode.FROM_START);
+ }
+ }//GEN-LAST:event_fromStartRadioButtonItemStateChanged
+
+ private void fromEndRadioButtonItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_fromEndRadioButtonItemStateChanged
+ if (fromEndRadioButton.isSelected()) {
+ switchGoToMode(GoToBinaryPositionMode.FROM_END);
+ }
+ }//GEN-LAST:event_fromEndRadioButtonItemStateChanged
+
+ private void fromCursorRadioButtonItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_fromCursorRadioButtonItemStateChanged
+ if (fromCursorRadioButton.isSelected()) {
+ switchGoToMode(GoToBinaryPositionMode.FROM_CURSOR);
+ }
+ }//GEN-LAST:event_fromCursorRadioButtonItemStateChanged
+
+ private void updateTargetPosition() {
+ targetPositionTextField.setText(String.valueOf(getTargetPosition()));
+ }
+
+ public void initFocus() {
+ baseSwitchableSpinnerPanel.initFocus();
+ }
+
+ public long getTargetPosition() {
+ long absolutePosition;
+ long position = getPositionValue();
+ switch (goToMode) {
+ case FROM_START:
+ absolutePosition = position;
+ break;
+ case FROM_END:
+ absolutePosition = maxPosition - position;
+ break;
+ case FROM_CURSOR:
+ absolutePosition = cursorPosition + position;
+ break;
+ default:
+ throw CodeAreaUtils.getInvalidTypeException(goToMode);
+ }
+
+ if (absolutePosition < 0) {
+ absolutePosition = 0;
+ } else if (absolutePosition > maxPosition) {
+ absolutePosition = maxPosition;
+ }
+ return absolutePosition;
+ }
+
+ public void setTargetPosition(long absolutePosition) {
+ if (absolutePosition < 0) {
+ absolutePosition = 0;
+ } else if (absolutePosition > maxPosition) {
+ absolutePosition = maxPosition;
+ }
+ switch (goToMode) {
+ case FROM_START:
+ setPositionValue(absolutePosition);
+ break;
+ case FROM_END:
+ setPositionValue(maxPosition - absolutePosition);
+ break;
+ case FROM_CURSOR:
+ setPositionValue(absolutePosition - cursorPosition);
+ break;
+ default:
+ throw CodeAreaUtils.getInvalidTypeException(goToMode);
+ }
+ updateTargetPosition();
+ }
+
+ public long getCursorPosition() {
+ return cursorPosition;
+ }
+
+ public void setCursorPosition(long cursorPosition) {
+ this.cursorPosition = cursorPosition;
+ setPositionValue(cursorPosition);
+ currentPositionTextField.setText(String.valueOf(cursorPosition));
+ }
+
+ public void setMaxPosition(long maxPosition) {
+ this.maxPosition = maxPosition;
+ baseSwitchableSpinnerPanel.setMaximum(maxPosition);
+ updateTargetPosition();
+ }
+
+ public void setSelected() {
+ baseSwitchableSpinnerPanel.requestFocusInWindow();
+ }
+
+ private void switchGoToMode(GoToBinaryPositionMode goToMode) {
+ if (this.goToMode == goToMode) {
+ return;
+ }
+
+ long absolutePosition = getTargetPosition();
+ this.goToMode = goToMode;
+ switch (goToMode) {
+ case FROM_START: {
+ setPositionValue(0l);
+ baseSwitchableSpinnerPanel.setMinimum(0l);
+ baseSwitchableSpinnerPanel.setMaximum(maxPosition);
+ baseSwitchableSpinnerPanel.revalidateSpinner();
+ break;
+ }
+ case FROM_END: {
+ setPositionValue(0l);
+ baseSwitchableSpinnerPanel.setMinimum(0l);
+ baseSwitchableSpinnerPanel.setMaximum(maxPosition);
+ baseSwitchableSpinnerPanel.revalidateSpinner();
+ break;
+ }
+ case FROM_CURSOR: {
+ setPositionValue(0l);
+ baseSwitchableSpinnerPanel.setMinimum(-cursorPosition);
+ baseSwitchableSpinnerPanel.setMaximum(maxPosition - cursorPosition);
+ baseSwitchableSpinnerPanel.revalidateSpinner();
+ break;
+ }
+ default:
+ throw CodeAreaUtils.getInvalidTypeException(goToMode);
+ }
+ setTargetPosition(absolutePosition);
+ }
+
+ private long getPositionValue() {
+ return (Long) baseSwitchableSpinnerPanel.getValue();
+ }
+
+ private void setPositionValue(long value) {
+ baseSwitchableSpinnerPanel.setValue(value);
+ updateTargetPosition();
+// positionSpinner.setValue(value);
+// positionSpinner.firePropertyChange(SPINNER_PROPERTY, value, value);
+ }
+
+ public void acceptInput() {
+ baseSwitchableSpinnerPanel.acceptInput();
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private BaseSwitchableSpinnerPanel baseSwitchableSpinnerPanel;
+ private javax.swing.JLabel currentPositionLabel;
+ private javax.swing.JTextField currentPositionTextField;
+ private javax.swing.JRadioButton fromCursorRadioButton;
+ private javax.swing.JRadioButton fromEndRadioButton;
+ private javax.swing.JRadioButton fromStartRadioButton;
+ private javax.swing.JPanel goToPanel;
+ private javax.swing.JLabel positionLabel;
+ private javax.swing.ButtonGroup positionTypeButtonGroup;
+ private javax.swing.JLabel targetPositionLabel;
+ private javax.swing.JTextField targetPositionTextField;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/GoToBinaryPositionMode.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/GoToBinaryPositionMode.java
new file mode 100644
index 00000000..e780afce
--- /dev/null
+++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/GoToBinaryPositionMode.java
@@ -0,0 +1,19 @@
+package the.bytecode.club.bytecodeviewer.gui.hexviewer;
+
+/**
+ * Mode for calculation of the go-to position in binary document.
+ */
+public enum GoToBinaryPositionMode {
+ /**
+ * Count from start of the document.
+ */
+ FROM_START,
+ /**
+ * Count from end of the document.
+ */
+ FROM_END,
+ /**
+ * Count from current position of the cursor in the document.
+ */
+ FROM_CURSOR
+}
diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/HexViewer.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/HexViewer.java
index acc5bf92..e0cdb6f8 100644
--- a/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/HexViewer.java
+++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/HexViewer.java
@@ -16,7 +16,7 @@ import java.awt.event.KeyEvent;
/**
* Binary/hexadecimal viewer based on BinEd library.
- *
+ *
* @author hajdam
*/
public class HexViewer extends JPanel {
@@ -33,13 +33,19 @@ public class HexViewer extends JPanel {
private javax.swing.JToggleButton lineWrappingToggleButton;
private JButton cycleCodeTypeButton;
private BinaryStatusApi binaryStatus;
+ private final AbstractAction goToAction;
public HexViewer(byte[] contentData) {
super(new BorderLayout());
codeArea = new CodeArea();
codeArea.setPainter(new HighlightNonAsciiCodeAreaPainter(codeArea));
toolBar = new JToolBar();
- statusPanel = new BinaryStatusPanel();
+ statusPanel = new BinaryStatusPanel() {
+ @Override
+ public Dimension getMinimumSize() {
+ return new Dimension(0, super.getMinimumSize().height);
+ }
+ };
valuesPanel = new ValuesPanel();
codeArea.setContentData(new ByteArrayData(contentData));
codeArea.setEditMode(EditMode.READ_ONLY);
@@ -55,6 +61,49 @@ public class HexViewer extends JPanel {
}
};
+ goToAction = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ final GoToBinaryPanel goToPanel = new GoToBinaryPanel();
+ goToPanel.setCursorPosition(codeArea.getCaret().getCaretPosition().getDataPosition());
+ goToPanel.setMaxPosition(codeArea.getDataSize());
+ final JDialog dialog = new JDialog((JFrame) SwingUtilities.getRoot(HexViewer.this), Dialog.ModalityType.APPLICATION_MODAL);
+ OkCancelPanel okCancelPanel = new OkCancelPanel() {
+ @Override
+ protected void okAction() {
+ goToPanel.acceptInput();
+ codeArea.setCaretPosition(goToPanel.getTargetPosition());
+ codeArea.revealCursor();
+ dialog.setVisible(false);
+ dialog.dispose();
+ codeArea.requestFocus();
+ }
+
+ @Override
+ protected void cancelAction() {
+ dialog.setVisible(false);
+ dialog.dispose();
+ }
+ };
+
+ final String ESC_CANCEL = "esc-cancel";
+ dialog.getRootPane().getActionMap().put(ESC_CANCEL, new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ okCancelPanel.cancelAction();
+ }
+ });
+ dialog.getRootPane().getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), ESC_CANCEL);
+ okCancelPanel.setOkButtonText("Go To");
+ dialog.setTitle("Go To Position");
+ dialog.add(goToPanel, BorderLayout.CENTER);
+ dialog.add(okCancelPanel, BorderLayout.SOUTH);
+ dialog.pack();
+ dialog.setLocationByPlatform(true);
+ dialog.setVisible(true);
+ }
+ };
+
init();
}
@@ -111,6 +160,10 @@ public class HexViewer extends JPanel {
registerBinaryStatus(statusPanel);
add(statusPanel, BorderLayout.SOUTH);
+
+ final String GO_TO_ACTION = "goToAction";
+ codeArea.getActionMap().put(GO_TO_ACTION, goToAction);
+ codeArea.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_G, HexViewer.getMetaMask()), GO_TO_ACTION);
invalidate();
}
@@ -269,6 +322,13 @@ public class HexViewer extends JPanel {
codeArea.selectAll();
});
menu.add(selectAllMenuItem);
+ menu.addSeparator();
+
+ final JMenuItem goToMenuItem = new JMenuItem("Go To...");
+ goToMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_G, HexViewer.getMetaMask()));
+ goToMenuItem.addActionListener(goToAction::actionPerformed);
+ menu.add(goToMenuItem);
+
return menu;
}
diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/OkCancelPanel.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/OkCancelPanel.java
new file mode 100644
index 00000000..f2037113
--- /dev/null
+++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/OkCancelPanel.java
@@ -0,0 +1,83 @@
+package the.bytecode.club.bytecodeviewer.gui.hexviewer;
+
+/**
+ * OK/Cancel Panel.
+ */
+public class OkCancelPanel extends javax.swing.JPanel {
+
+ public OkCancelPanel() {
+ initComponents();
+ }
+
+ /**
+ * 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.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ cancelButton = new javax.swing.JButton();
+ okButton = new javax.swing.JButton();
+
+ cancelButton.setText("Cancel");
+ cancelButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ cancelButtonActionPerformed(evt);
+ }
+ });
+
+ okButton.setText("Ok");
+ okButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ okButtonActionPerformed(evt);
+ }
+ });
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(okButton)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(cancelButton)
+ .addContainerGap())
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(cancelButton)
+ .addComponent(okButton))
+ .addContainerGap())
+ );
+ }// //GEN-END:initComponents
+
+ private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed
+ cancelAction();
+ }//GEN-LAST:event_cancelButtonActionPerformed
+
+ private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
+ okAction();
+ }//GEN-LAST:event_okButtonActionPerformed
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JButton cancelButton;
+ private javax.swing.JButton okButton;
+ // End of variables declaration//GEN-END:variables
+
+ protected void okAction() {
+ }
+
+ protected void cancelAction() {
+ }
+
+ public void setOkButtonText(String text) {
+ okButton.setText(text);
+ }
+}