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); + } +}