From c264e365cb7576d695dcb3e184bcf9fe6fafd24a Mon Sep 17 00:00:00 2001 From: hajdam Date: Fri, 3 Sep 2021 22:43:10 +0200 Subject: [PATCH] JHexEditor replaced with BinEd Hexadecimal viewer library --- pom.xml | 22 + .../gui/hexviewer/BinaryStatusApi.java | 46 + .../gui/hexviewer/BinaryStatusPanel.java | 672 +++++++++++ .../gui/hexviewer/HexViewer.java | 279 +++++ .../gui/hexviewer/JHexEditor.java | 346 ------ .../gui/hexviewer/JHexEditorASCII.java | 182 --- .../gui/hexviewer/JHexEditorHEX.java | 176 --- .../hexviewer/StatusCursorPositionFormat.java | 43 + .../hexviewer/StatusDocumentSizeFormat.java | 44 + .../gui/hexviewer/ValuesPanel.java | 1067 +++++++++++++++++ .../gui/resourceviewer/viewer/FileViewer.java | 6 +- .../gui/util/BytecodeViewPanelUpdater.java | 4 +- .../hexviewer/resources/bined-linewrap.png | Bin 0 -> 452 bytes 13 files changed, 2178 insertions(+), 709 deletions(-) create mode 100644 src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/BinaryStatusApi.java create mode 100644 src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/BinaryStatusPanel.java create mode 100644 src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/HexViewer.java delete mode 100644 src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/JHexEditor.java delete mode 100644 src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/JHexEditorASCII.java delete mode 100644 src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/JHexEditorHEX.java create mode 100644 src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/StatusCursorPositionFormat.java create mode 100644 src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/StatusDocumentSizeFormat.java create mode 100644 src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/ValuesPanel.java create mode 100644 src/main/resources/the/bytecode/club/bytecodeviewer/gui/hexviewer/resources/bined-linewrap.png diff --git a/pom.xml b/pom.xml index 68fd9985..0c36eaff 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,7 @@ 22.0.0 2.5.0bcv2 9.2 + 0.2.0 0.151 1.9.12 1.4 @@ -35,6 +36,7 @@ 1.0bcv 3.4.1.3 3.2 + 0.2.0 0.5.36 3.1.3 1.7.32 @@ -103,6 +105,21 @@ asm-util ${asm.version} + + org.exbin.bined + bined-core + ${bined.version} + + + org.exbin.bined + bined-swing + ${bined.version} + + + org.exbin.bined + bined-highlight-swing + ${bined.version} + org.benf cfr @@ -204,6 +221,11 @@ objenesis ${objenesis.version} + + org.exbin.auxiliary + paged_data + ${paged_data.version} + org.bitbucket.mstrobel procyon-core diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/BinaryStatusApi.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/BinaryStatusApi.java new file mode 100644 index 00000000..c8af44a1 --- /dev/null +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/BinaryStatusApi.java @@ -0,0 +1,46 @@ +package the.bytecode.club.bytecodeviewer.gui.hexviewer; + +import javax.annotation.ParametersAreNonnullByDefault; +import org.exbin.bined.CodeAreaCaretPosition; +import org.exbin.bined.EditMode; +import org.exbin.bined.EditOperation; +import org.exbin.bined.SelectionRange; + +/** + * Binary editor status interface. + * + * @author hajdam + */ +@ParametersAreNonnullByDefault +public interface BinaryStatusApi { + + /** + * Reports cursor position. + * + * @param cursorPosition cursor position + */ + void setCursorPosition(CodeAreaCaretPosition cursorPosition); + + /** + * Sets current selection. + * + * @param selectionRange current selection + */ + void setSelectionRange(SelectionRange selectionRange); + + /** + * Reports currently active edit mode. + * + * @param mode edit mode + * @param operation edit operation + */ + void setEditMode(EditMode mode, EditOperation operation); + + /** + * Sets current document size. + * + * @param documentSize document size + * @param initialDocumentSize document size when file was opened + */ + void setCurrentDocumentSize(long documentSize, long initialDocumentSize); +} diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/BinaryStatusPanel.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/BinaryStatusPanel.java new file mode 100644 index 00000000..bd8b7c6e --- /dev/null +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/BinaryStatusPanel.java @@ -0,0 +1,672 @@ +package the.bytecode.club.bytecodeviewer.gui.hexviewer; + +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.awt.event.MouseEvent; +import javax.annotation.Nonnull; +import javax.annotation.ParametersAreNonnullByDefault; +import javax.swing.JToolTip; +import org.exbin.bined.CodeAreaCaretPosition; +import org.exbin.bined.CodeAreaUtils; +import org.exbin.bined.EditMode; +import org.exbin.bined.EditOperation; +import org.exbin.bined.PositionCodeType; +import org.exbin.bined.SelectionRange; + +/** + * Binary editor status panel. + * + * @author hajdam + */ +@ParametersAreNonnullByDefault +public class BinaryStatusPanel extends javax.swing.JPanel implements BinaryStatusApi { + + public static int DEFAULT_OCTAL_SPACE_GROUP_SIZE = 4; + public static int DEFAULT_DECIMAL_SPACE_GROUP_SIZE = 3; + public static int DEFAULT_HEXADECIMAL_SPACE_GROUP_SIZE = 4; + + public static final String INSERT_EDIT_MODE_LABEL = "INS"; + public static final String OVERWRITE_EDIT_MODE_LABEL = "OVR"; + public static final String READONLY_EDIT_MODE_LABEL = "RO"; + public static final String INPLACE_EDIT_MODE_LABEL = "INP"; + + public static final String OCTAL_CODE_TYPE_LABEL = "OCT"; + public static final String DECIMAL_CODE_TYPE_LABEL = "DEC"; + public static final String HEXADECIMAL_CODE_TYPE_LABEL = "HEX"; + + private StatusCursorPositionFormat cursorPositionFormat = new StatusCursorPositionFormat(); + private StatusDocumentSizeFormat documentSizeFormat = new StatusDocumentSizeFormat(); + private int octalSpaceGroupSize = DEFAULT_OCTAL_SPACE_GROUP_SIZE; + private int decimalSpaceGroupSize = DEFAULT_DECIMAL_SPACE_GROUP_SIZE; + private int hexadecimalSpaceGroupSize = DEFAULT_HEXADECIMAL_SPACE_GROUP_SIZE; + + private EditOperation editOperation; + private CodeAreaCaretPosition caretPosition; + private SelectionRange selectionRange; + private long documentSize; + private long initialDocumentSize; + + private javax.swing.JMenu cursorPositionCodeTypeMenu; + private javax.swing.JLabel cursorPositionLabel; + private javax.swing.ButtonGroup cursorPositionModeButtonGroup; + private javax.swing.JCheckBoxMenuItem cursorPositionShowOffsetCheckBoxMenuItem; + private javax.swing.JRadioButtonMenuItem decimalCursorPositionModeRadioButtonMenuItem; + private javax.swing.JRadioButtonMenuItem decimalDocumentSizeModeRadioButtonMenuItem; + private javax.swing.JMenu documentSizeCodeTypeMenu; + private javax.swing.JMenuItem documentSizeCopyMenuItem; + private javax.swing.JLabel documentSizeLabel; + private javax.swing.ButtonGroup documentSizeModeButtonGroup; + private javax.swing.JPopupMenu documentSizePopupMenu; + private javax.swing.JCheckBoxMenuItem documentSizeShowRelativeCheckBoxMenuItem; + private javax.swing.JLabel editModeLabel; + private javax.swing.JLabel encodingLabel; + private javax.swing.JRadioButtonMenuItem hexadecimalCursorPositionModeRadioButtonMenuItem; + private javax.swing.JRadioButtonMenuItem hexadecimalDocumentSizeModeRadioButtonMenuItem; + private javax.swing.JPopupMenu.Separator jSeparator1; + private javax.swing.JPopupMenu.Separator jSeparator2; + private javax.swing.JRadioButtonMenuItem octalCursorPositionModeRadioButtonMenuItem; + private javax.swing.JRadioButtonMenuItem octalDocumentSizeModeRadioButtonMenuItem; + private javax.swing.JMenuItem positionCopyMenuItem; + private javax.swing.JMenuItem positionGoToMenuItem; + private javax.swing.JPopupMenu positionPopupMenu; + + public BinaryStatusPanel() { + initComponents(); + } + + public void updateStatus() { + updateCaretPosition(); + updateCursorPositionToolTip(); + updateDocumentSize(); + updateDocumentSizeToolTip(); + + switch (cursorPositionFormat.getCodeType()) { + case OCTAL: { + octalCursorPositionModeRadioButtonMenuItem.setSelected(true); + break; + } + case DECIMAL: { + decimalCursorPositionModeRadioButtonMenuItem.setSelected(true); + break; + } + case HEXADECIMAL: { + hexadecimalCursorPositionModeRadioButtonMenuItem.setSelected(true); + break; + } + default: + throw CodeAreaUtils.getInvalidTypeException(cursorPositionFormat.getCodeType()); + } + cursorPositionShowOffsetCheckBoxMenuItem.setSelected(cursorPositionFormat.isShowOffset()); + + switch (documentSizeFormat.getCodeType()) { + case OCTAL: { + octalDocumentSizeModeRadioButtonMenuItem.setSelected(true); + break; + } + case DECIMAL: { + decimalDocumentSizeModeRadioButtonMenuItem.setSelected(true); + break; + } + case HEXADECIMAL: { + hexadecimalDocumentSizeModeRadioButtonMenuItem.setSelected(true); + break; + } + default: + throw CodeAreaUtils.getInvalidTypeException(documentSizeFormat.getCodeType()); + } + documentSizeShowRelativeCheckBoxMenuItem.setSelected(documentSizeFormat.isShowRelative()); + } + + private void initComponents() { + + positionPopupMenu = new javax.swing.JPopupMenu(); + cursorPositionCodeTypeMenu = new javax.swing.JMenu(); + octalCursorPositionModeRadioButtonMenuItem = new javax.swing.JRadioButtonMenuItem(); + decimalCursorPositionModeRadioButtonMenuItem = new javax.swing.JRadioButtonMenuItem(); + hexadecimalCursorPositionModeRadioButtonMenuItem = new javax.swing.JRadioButtonMenuItem(); + cursorPositionShowOffsetCheckBoxMenuItem = new javax.swing.JCheckBoxMenuItem(); + jSeparator2 = new javax.swing.JPopupMenu.Separator(); + positionCopyMenuItem = new javax.swing.JMenuItem(); + positionGoToMenuItem = new javax.swing.JMenuItem(); + documentSizePopupMenu = new javax.swing.JPopupMenu(); + documentSizeCodeTypeMenu = new javax.swing.JMenu(); + octalDocumentSizeModeRadioButtonMenuItem = new javax.swing.JRadioButtonMenuItem(); + decimalDocumentSizeModeRadioButtonMenuItem = new javax.swing.JRadioButtonMenuItem(); + hexadecimalDocumentSizeModeRadioButtonMenuItem = new javax.swing.JRadioButtonMenuItem(); + documentSizeShowRelativeCheckBoxMenuItem = new javax.swing.JCheckBoxMenuItem(); + jSeparator1 = new javax.swing.JPopupMenu.Separator(); + documentSizeCopyMenuItem = new javax.swing.JMenuItem(); + documentSizeModeButtonGroup = new javax.swing.ButtonGroup(); + cursorPositionModeButtonGroup = new javax.swing.ButtonGroup(); + documentSizeLabel = new javax.swing.JLabel() { + @Override + public JToolTip createToolTip() { + updateDocumentSizeToolTip(); + return super.createToolTip(); + } + }; + cursorPositionLabel = new javax.swing.JLabel() { + @Override + public JToolTip createToolTip() { + updateCursorPositionToolTip(); + return super.createToolTip(); + } + }; + editModeLabel = new javax.swing.JLabel(); + encodingLabel = new javax.swing.JLabel(); + + positionPopupMenu.setName("positionPopupMenu"); + + cursorPositionCodeTypeMenu.setText("Code Type"); + cursorPositionCodeTypeMenu.setName("cursorPositionCodeTypeMenu"); + + cursorPositionModeButtonGroup.add(octalCursorPositionModeRadioButtonMenuItem); + octalCursorPositionModeRadioButtonMenuItem.setText("Show as octal"); + octalCursorPositionModeRadioButtonMenuItem.setName("octalCursorPositionModeRadioButtonMenuItem"); + octalCursorPositionModeRadioButtonMenuItem.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + octalCursorPositionModeRadioButtonMenuItemActionPerformed(evt); + } + }); + cursorPositionCodeTypeMenu.add(octalCursorPositionModeRadioButtonMenuItem); + + cursorPositionModeButtonGroup.add(decimalCursorPositionModeRadioButtonMenuItem); + decimalCursorPositionModeRadioButtonMenuItem.setSelected(true); + decimalCursorPositionModeRadioButtonMenuItem.setText("Show as decimal"); + decimalCursorPositionModeRadioButtonMenuItem.setName("decimalCursorPositionModeRadioButtonMenuItem"); + decimalCursorPositionModeRadioButtonMenuItem.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + decimalCursorPositionModeRadioButtonMenuItemActionPerformed(evt); + } + }); + cursorPositionCodeTypeMenu.add(decimalCursorPositionModeRadioButtonMenuItem); + + cursorPositionModeButtonGroup.add(hexadecimalCursorPositionModeRadioButtonMenuItem); + hexadecimalCursorPositionModeRadioButtonMenuItem.setText("Show as hexadecimal"); + hexadecimalCursorPositionModeRadioButtonMenuItem.setName("hexadecimalCursorPositionModeRadioButtonMenuItem"); + hexadecimalCursorPositionModeRadioButtonMenuItem.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + hexadecimalCursorPositionModeRadioButtonMenuItemActionPerformed(evt); + } + }); + cursorPositionCodeTypeMenu.add(hexadecimalCursorPositionModeRadioButtonMenuItem); + + positionPopupMenu.add(cursorPositionCodeTypeMenu); + + cursorPositionShowOffsetCheckBoxMenuItem.setSelected(true); + cursorPositionShowOffsetCheckBoxMenuItem.setText("Show offset"); + cursorPositionShowOffsetCheckBoxMenuItem.setName("cursorPositionShowOffsetCheckBoxMenuItem"); + cursorPositionShowOffsetCheckBoxMenuItem.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cursorPositionShowOffsetCheckBoxMenuItemActionPerformed(evt); + } + }); + positionPopupMenu.add(cursorPositionShowOffsetCheckBoxMenuItem); + + jSeparator2.setName("jSeparator2"); + positionPopupMenu.add(jSeparator2); + + positionCopyMenuItem.setText("Copy"); + positionCopyMenuItem.setName("positionCopyMenuItem"); + positionCopyMenuItem.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + positionCopyMenuItemActionPerformed(evt); + } + }); + positionPopupMenu.add(positionCopyMenuItem); + + positionGoToMenuItem.setText("Go To..."); + positionGoToMenuItem.setEnabled(false); + positionGoToMenuItem.setName("positionGoToMenuItem"); + positionGoToMenuItem.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + positionGoToMenuItemActionPerformed(evt); + } + }); + positionPopupMenu.add(positionGoToMenuItem); + + documentSizePopupMenu.setName("documentSizePopupMenu"); + + documentSizeCodeTypeMenu.setText("Code Type"); + documentSizeCodeTypeMenu.setName("documentSizeCodeTypeMenu"); + + documentSizeModeButtonGroup.add(octalDocumentSizeModeRadioButtonMenuItem); + octalDocumentSizeModeRadioButtonMenuItem.setText("Show as octal"); + octalDocumentSizeModeRadioButtonMenuItem.setName("octalDocumentSizeModeRadioButtonMenuItem"); + octalDocumentSizeModeRadioButtonMenuItem.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + octalDocumentSizeModeRadioButtonMenuItemActionPerformed(evt); + } + }); + documentSizeCodeTypeMenu.add(octalDocumentSizeModeRadioButtonMenuItem); + + documentSizeModeButtonGroup.add(decimalDocumentSizeModeRadioButtonMenuItem); + decimalDocumentSizeModeRadioButtonMenuItem.setText("Show as decimal"); + decimalDocumentSizeModeRadioButtonMenuItem.setName("decimalDocumentSizeModeRadioButtonMenuItem"); + decimalDocumentSizeModeRadioButtonMenuItem.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + decimalDocumentSizeModeRadioButtonMenuItemActionPerformed(evt); + } + }); + documentSizeCodeTypeMenu.add(decimalDocumentSizeModeRadioButtonMenuItem); + + documentSizeModeButtonGroup.add(hexadecimalDocumentSizeModeRadioButtonMenuItem); + hexadecimalDocumentSizeModeRadioButtonMenuItem.setText("Show as hexadecimal"); + hexadecimalDocumentSizeModeRadioButtonMenuItem.setName("hexadecimalDocumentSizeModeRadioButtonMenuItem"); + hexadecimalDocumentSizeModeRadioButtonMenuItem.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + hexadecimalDocumentSizeModeRadioButtonMenuItemActionPerformed(evt); + } + }); + documentSizeCodeTypeMenu.add(hexadecimalDocumentSizeModeRadioButtonMenuItem); + + documentSizePopupMenu.add(documentSizeCodeTypeMenu); + + documentSizeShowRelativeCheckBoxMenuItem.setSelected(true); + documentSizeShowRelativeCheckBoxMenuItem.setText("Show relative size"); + documentSizeShowRelativeCheckBoxMenuItem.setName("documentSizeShowRelativeCheckBoxMenuItem"); + documentSizeShowRelativeCheckBoxMenuItem.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + documentSizeShowRelativeCheckBoxMenuItemActionPerformed(evt); + } + }); + documentSizePopupMenu.add(documentSizeShowRelativeCheckBoxMenuItem); + + jSeparator1.setName("jSeparator1"); + documentSizePopupMenu.add(jSeparator1); + + documentSizeCopyMenuItem.setText("Copy"); + documentSizeCopyMenuItem.setName("documentSizeCopyMenuItem"); + documentSizeCopyMenuItem.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + documentSizeCopyMenuItemActionPerformed(evt); + } + }); + documentSizePopupMenu.add(documentSizeCopyMenuItem); + + setName("Form"); + + documentSizeLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + documentSizeLabel.setText("0 (0)"); + documentSizeLabel.setToolTipText("Document size"); + documentSizeLabel.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + documentSizeLabel.setComponentPopupMenu(documentSizePopupMenu); + documentSizeLabel.setName("documentSizeLabel"); + + cursorPositionLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + cursorPositionLabel.setText("0:0"); + cursorPositionLabel.setToolTipText("Cursor position"); + cursorPositionLabel.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + cursorPositionLabel.setComponentPopupMenu(positionPopupMenu); + cursorPositionLabel.setName("cursorPositionLabel"); + cursorPositionLabel.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + cursorPositionLabelMouseClicked(evt); + } + }); + + editModeLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + editModeLabel.setText("OVR"); + editModeLabel.setToolTipText("Edit mode"); + editModeLabel.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + editModeLabel.setName("editModeLabel"); + editModeLabel.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + editModeLabelMouseClicked(evt); + } + }); + + encodingLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + encodingLabel.setText("UTF-8"); + encodingLabel.setToolTipText("Active encoding"); + encodingLabel.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + encodingLabel.setName("encodingLabel"); + encodingLabel.addMouseListener(new java.awt.event.MouseAdapter() { + public void mousePressed(java.awt.event.MouseEvent evt) { + encodingLabelMousePressed(evt); + } + public void mouseReleased(java.awt.event.MouseEvent evt) { + encodingLabelMouseReleased(evt); + } + public void mouseClicked(java.awt.event.MouseEvent evt) { + encodingLabelMouseClicked(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(195, Short.MAX_VALUE) + .addComponent(encodingLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 148, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(documentSizeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 168, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(cursorPositionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 168, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(editModeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 35, javax.swing.GroupLayout.PREFERRED_SIZE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(editModeLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(documentSizeLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(cursorPositionLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(encodingLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + + private void editModeLabelMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_editModeLabelMouseClicked +// if (statusControlHandler != null && evt.getButton() == MouseEvent.BUTTON1) { +// if (editOperation == EditOperation.INSERT) { +// statusControlHandler.changeEditOperation(EditOperation.OVERWRITE); +// } else if (editOperation == EditOperation.OVERWRITE) { +// statusControlHandler.changeEditOperation(EditOperation.INSERT); +// } +// } + }//GEN-LAST:event_editModeLabelMouseClicked + + private void cursorPositionLabelMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_cursorPositionLabelMouseClicked + if (evt.getButton() == MouseEvent.BUTTON1 && evt.getClickCount() > 1) { + // statusControlHandler.changeCursorPosition(); + } + }//GEN-LAST:event_cursorPositionLabelMouseClicked + + private void positionGoToMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_positionGoToMenuItemActionPerformed + // statusControlHandler.changeCursorPosition(); + }//GEN-LAST:event_positionGoToMenuItemActionPerformed + + private void positionCopyMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_positionCopyMenuItemActionPerformed + try { + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(new StringSelection(cursorPositionLabel.getText()), null); + } catch (IllegalStateException ex) { + // ignore issues with clipboard + } + }//GEN-LAST:event_positionCopyMenuItemActionPerformed + + private void documentSizeCopyMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_documentSizeCopyMenuItemActionPerformed + try { + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(new StringSelection(documentSizeLabel.getText()), null); + } catch (IllegalStateException ex) { + // ignore issues with clipboard + } + }//GEN-LAST:event_documentSizeCopyMenuItemActionPerformed + + private void encodingLabelMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_encodingLabelMouseClicked + if (evt.getButton() == MouseEvent.BUTTON1) { + // Not supported + } else { + handleEncodingPopup(evt); + } + }//GEN-LAST:event_encodingLabelMouseClicked + + private void encodingLabelMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_encodingLabelMousePressed + handleEncodingPopup(evt); + }//GEN-LAST:event_encodingLabelMousePressed + + private void encodingLabelMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_encodingLabelMouseReleased + handleEncodingPopup(evt); + }//GEN-LAST:event_encodingLabelMouseReleased + + private void cursorPositionShowOffsetCheckBoxMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cursorPositionShowOffsetCheckBoxMenuItemActionPerformed + cursorPositionFormat.setShowOffset(cursorPositionShowOffsetCheckBoxMenuItem.isSelected()); + updateCaretPosition(); + }//GEN-LAST:event_cursorPositionShowOffsetCheckBoxMenuItemActionPerformed + + private void documentSizeShowRelativeCheckBoxMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_documentSizeShowRelativeCheckBoxMenuItemActionPerformed + documentSizeFormat.setShowRelative(documentSizeShowRelativeCheckBoxMenuItem.isSelected()); + updateDocumentSize(); + updateDocumentSizeToolTip(); + }//GEN-LAST:event_documentSizeShowRelativeCheckBoxMenuItemActionPerformed + + private void octalCursorPositionModeRadioButtonMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_octalCursorPositionModeRadioButtonMenuItemActionPerformed + cursorPositionFormat.setCodeType(PositionCodeType.OCTAL); + updateCaretPosition(); + }//GEN-LAST:event_octalCursorPositionModeRadioButtonMenuItemActionPerformed + + private void decimalCursorPositionModeRadioButtonMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_decimalCursorPositionModeRadioButtonMenuItemActionPerformed + cursorPositionFormat.setCodeType(PositionCodeType.DECIMAL); + updateCaretPosition(); + }//GEN-LAST:event_decimalCursorPositionModeRadioButtonMenuItemActionPerformed + + private void hexadecimalCursorPositionModeRadioButtonMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_hexadecimalCursorPositionModeRadioButtonMenuItemActionPerformed + cursorPositionFormat.setCodeType(PositionCodeType.HEXADECIMAL); + updateCaretPosition(); + }//GEN-LAST:event_hexadecimalCursorPositionModeRadioButtonMenuItemActionPerformed + + private void octalDocumentSizeModeRadioButtonMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_octalDocumentSizeModeRadioButtonMenuItemActionPerformed + documentSizeFormat.setCodeType(PositionCodeType.OCTAL); + updateDocumentSize(); + }//GEN-LAST:event_octalDocumentSizeModeRadioButtonMenuItemActionPerformed + + private void decimalDocumentSizeModeRadioButtonMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_decimalDocumentSizeModeRadioButtonMenuItemActionPerformed + documentSizeFormat.setCodeType(PositionCodeType.DECIMAL); + updateDocumentSize(); + }//GEN-LAST:event_decimalDocumentSizeModeRadioButtonMenuItemActionPerformed + + private void hexadecimalDocumentSizeModeRadioButtonMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_hexadecimalDocumentSizeModeRadioButtonMenuItemActionPerformed + documentSizeFormat.setCodeType(PositionCodeType.HEXADECIMAL); + updateDocumentSize(); + }//GEN-LAST:event_hexadecimalDocumentSizeModeRadioButtonMenuItemActionPerformed + + private void handleEncodingPopup(java.awt.event.MouseEvent evt) { + if (evt.isPopupTrigger()) { + // Not supported + } + } + + @Override + public void setCursorPosition(CodeAreaCaretPosition caretPosition) { + this.caretPosition = caretPosition; + updateCaretPosition(); + updateCursorPositionToolTip(); + } + + @Override + public void setSelectionRange(SelectionRange selectionRange) { + this.selectionRange = selectionRange; + updateCaretPosition(); + updateCursorPositionToolTip(); + updateDocumentSize(); + updateDocumentSizeToolTip(); + } + + @Override + public void setCurrentDocumentSize(long documentSize, long initialDocumentSize) { + this.documentSize = documentSize; + this.initialDocumentSize = initialDocumentSize; + updateDocumentSize(); + updateDocumentSizeToolTip(); + } + + @Nonnull + public String getEncoding() { + return encodingLabel.getText(); + } + + public void setEncoding(String encodingName) { + encodingLabel.setText(encodingName + " ^"); + } + + @Override + public void setEditMode(EditMode editMode, EditOperation editOperation) { + this.editOperation = editOperation; + switch (editMode) { + case READ_ONLY: { + editModeLabel.setText(READONLY_EDIT_MODE_LABEL); + break; + } + case EXPANDING: + case CAPPED: { + switch (editOperation) { + case INSERT: { + editModeLabel.setText(INSERT_EDIT_MODE_LABEL); + break; + } + case OVERWRITE: { + editModeLabel.setText(OVERWRITE_EDIT_MODE_LABEL); + break; + } + default: + throw CodeAreaUtils.getInvalidTypeException(editOperation); + } + break; + } + case INPLACE: { + editModeLabel.setText(INPLACE_EDIT_MODE_LABEL); + break; + } + default: + throw CodeAreaUtils.getInvalidTypeException(editMode); + } + } + + private void updateCaretPosition() { + if (caretPosition == null) { + cursorPositionLabel.setText("-"); + } else { + StringBuilder labelBuilder = new StringBuilder(); + if (selectionRange != null && !selectionRange.isEmpty()) { + long first = selectionRange.getFirst(); + long last = selectionRange.getLast(); + labelBuilder.append(numberToPosition(first, cursorPositionFormat.getCodeType())); + labelBuilder.append(" to "); + labelBuilder.append(numberToPosition(last, cursorPositionFormat.getCodeType())); + } else { + labelBuilder.append(numberToPosition(caretPosition.getDataPosition(), cursorPositionFormat.getCodeType())); + if (cursorPositionFormat.isShowOffset()) { + labelBuilder.append(":"); + labelBuilder.append(caretPosition.getCodeOffset()); + } + } + cursorPositionLabel.setText(labelBuilder.toString()); + } + } + + private void updateCursorPositionToolTip() { + StringBuilder builder = new StringBuilder(); + builder.append(""); + if (caretPosition == null) { + builder.append("Cursor position"); + } else { + if (selectionRange != null && !selectionRange.isEmpty()) { + long first = selectionRange.getFirst(); + long last = selectionRange.getLast(); + builder.append("Selection from
"); + builder.append(OCTAL_CODE_TYPE_LABEL + ": ").append(numberToPosition(first, PositionCodeType.OCTAL)).append("
"); + builder.append(DECIMAL_CODE_TYPE_LABEL + ": ").append(numberToPosition(first, PositionCodeType.DECIMAL)).append("
"); + builder.append(HEXADECIMAL_CODE_TYPE_LABEL + ": ").append(numberToPosition(first, PositionCodeType.HEXADECIMAL)).append("
"); + builder.append("
"); + builder.append("Selection to
"); + builder.append(OCTAL_CODE_TYPE_LABEL + ": ").append(numberToPosition(last, PositionCodeType.OCTAL)).append("
"); + builder.append(DECIMAL_CODE_TYPE_LABEL + ": ").append(numberToPosition(last, PositionCodeType.DECIMAL)).append("
"); + builder.append(HEXADECIMAL_CODE_TYPE_LABEL + ": ").append(numberToPosition(first, PositionCodeType.HEXADECIMAL)).append("
"); + } else { + long dataPosition = caretPosition.getDataPosition(); + builder.append("Cursor position
"); + builder.append(OCTAL_CODE_TYPE_LABEL + ": ").append(numberToPosition(dataPosition, PositionCodeType.OCTAL)).append("
"); + builder.append(DECIMAL_CODE_TYPE_LABEL + ": ").append(numberToPosition(dataPosition, PositionCodeType.DECIMAL)).append("
"); + builder.append(HEXADECIMAL_CODE_TYPE_LABEL + ": ").append(numberToPosition(dataPosition, PositionCodeType.HEXADECIMAL)); + builder.append(""); + } + } + + cursorPositionLabel.setToolTipText(builder.toString()); + } + + private void updateDocumentSize() { + if (documentSize == -1) { + documentSizeLabel.setText(documentSizeFormat.isShowRelative() ? "0 (0)" : "0"); + } else { + StringBuilder labelBuilder = new StringBuilder(); + if (selectionRange != null && !selectionRange.isEmpty()) { + labelBuilder.append(numberToPosition(selectionRange.getLength(), documentSizeFormat.getCodeType())); + labelBuilder.append(" of "); + labelBuilder.append(numberToPosition(documentSize, documentSizeFormat.getCodeType())); + } else { + labelBuilder.append(numberToPosition(documentSize, documentSizeFormat.getCodeType())); + if (documentSizeFormat.isShowRelative()) { + long difference = documentSize - initialDocumentSize; + labelBuilder.append(difference > 0 ? " (+" : " ("); + labelBuilder.append(numberToPosition(difference, documentSizeFormat.getCodeType())); + labelBuilder.append(")"); + + } + } + + documentSizeLabel.setText(labelBuilder.toString()); + } + } + + private void updateDocumentSizeToolTip() { + StringBuilder builder = new StringBuilder(); + builder.append(""); + if (selectionRange != null && !selectionRange.isEmpty()) { + long length = selectionRange.getLength(); + builder.append("Selection length
"); + builder.append(OCTAL_CODE_TYPE_LABEL + ": ").append(numberToPosition(length, PositionCodeType.OCTAL)).append("
"); + builder.append(DECIMAL_CODE_TYPE_LABEL + ": ").append(numberToPosition(length, PositionCodeType.DECIMAL)).append("
"); + builder.append(HEXADECIMAL_CODE_TYPE_LABEL + ": ").append(numberToPosition(length, PositionCodeType.HEXADECIMAL)).append("
"); + builder.append("
"); + } + + builder.append("Document size
"); + builder.append(OCTAL_CODE_TYPE_LABEL + ": ").append(numberToPosition(documentSize, PositionCodeType.OCTAL)).append("
"); + builder.append(DECIMAL_CODE_TYPE_LABEL + ": ").append(numberToPosition(documentSize, PositionCodeType.DECIMAL)).append("
"); + builder.append(HEXADECIMAL_CODE_TYPE_LABEL + ": ").append(numberToPosition(documentSize, PositionCodeType.HEXADECIMAL)); + builder.append(""); + documentSizeLabel.setToolTipText(builder.toString()); + } + + @Nonnull + private String numberToPosition(long value, PositionCodeType codeType) { + if (value == 0) { + return "0"; + } + + int spaceGroupSize = 0; + switch (codeType) { + case OCTAL: { + spaceGroupSize = octalSpaceGroupSize; + break; + } + case DECIMAL: { + spaceGroupSize = decimalSpaceGroupSize; + break; + } + case HEXADECIMAL: { + spaceGroupSize = hexadecimalSpaceGroupSize; + break; + } + default: + throw CodeAreaUtils.getInvalidTypeException(codeType); + } + + long remainder = value > 0 ? value : -value; + StringBuilder builder = new StringBuilder(); + int base = codeType.getBase(); + int groupSize = spaceGroupSize == 0 ? -1 : spaceGroupSize; + while (remainder > 0) { + if (groupSize >= 0) { + if (groupSize == 0) { + builder.insert(0, ' '); + groupSize = spaceGroupSize - 1; + } else { + groupSize--; + } + } + + int digit = (int) (remainder % base); + remainder = remainder / base; + builder.insert(0, CodeAreaUtils.UPPER_HEX_CODES[digit]); + } + + if (value < 0) { + builder.insert(0, "-"); + } + return builder.toString(); + } +} 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 new file mode 100644 index 00000000..acc5bf92 --- /dev/null +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/HexViewer.java @@ -0,0 +1,279 @@ +package the.bytecode.club.bytecodeviewer.gui.hexviewer; + +import org.exbin.auxiliary.paged_data.ByteArrayData; +import org.exbin.bined.CodeAreaCaretPosition; +import org.exbin.bined.CodeType; +import org.exbin.bined.EditMode; +import org.exbin.bined.RowWrappingMode; +import org.exbin.bined.highlight.swing.HighlightNonAsciiCodeAreaPainter; +import org.exbin.bined.swing.basic.CodeArea; + +import javax.annotation.Nonnull; +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; + +/** + * Binary/hexadecimal viewer based on BinEd library. + * + * @author hajdam + */ +public class HexViewer extends JPanel { + + private final CodeArea codeArea; + private final JToolBar toolBar; + private final BinaryStatusPanel statusPanel; + private final ValuesPanel valuesPanel; + private JPanel codeAreaPanel; + private JScrollPane valuesPanelScrollBar; + private boolean valuesPanelVisible = false; + + private final AbstractAction cycleCodeTypesAction; + private javax.swing.JToggleButton lineWrappingToggleButton; + private JButton cycleCodeTypeButton; + private BinaryStatusApi binaryStatus; + + public HexViewer(byte[] contentData) { + super(new BorderLayout()); + codeArea = new CodeArea(); + codeArea.setPainter(new HighlightNonAsciiCodeAreaPainter(codeArea)); + toolBar = new JToolBar(); + statusPanel = new BinaryStatusPanel(); + valuesPanel = new ValuesPanel(); + codeArea.setContentData(new ByteArrayData(contentData)); + codeArea.setEditMode(EditMode.READ_ONLY); + + cycleCodeTypesAction = new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + int codeTypePos = codeArea.getCodeType().ordinal(); + CodeType[] values = CodeType.values(); + CodeType next = codeTypePos + 1 >= values.length ? values[0] : values[codeTypePos + 1]; + codeArea.setCodeType(next); + updateCycleButtonState(); + } + }; + + init(); + } + + private void init() { + cycleCodeTypesAction.putValue(Action.SHORT_DESCRIPTION, "Cycle through code types"); + + cycleCodeTypeButton = new JButton(); + cycleCodeTypeButton.setAction(cycleCodeTypesAction); + updateCycleButtonState(); + toolBar.add(cycleCodeTypeButton); + lineWrappingToggleButton = new javax.swing.JToggleButton(); + lineWrappingToggleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/the/bytecode/club/bytecodeviewer/gui/hexviewer/resources/bined-linewrap.png"))); + lineWrappingToggleButton.setToolTipText("Toggle line wrapping"); + lineWrappingToggleButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + if (codeArea.getRowWrapping() == RowWrappingMode.WRAPPING) { + codeArea.setMaxBytesPerRow(16); + codeArea.setRowWrapping(RowWrappingMode.NO_WRAPPING); + } else { + codeArea.setMaxBytesPerRow(0); + codeArea.setRowWrapping(RowWrappingMode.WRAPPING); + } + } + }); + toolBar.add(lineWrappingToggleButton); + + add(toolBar, BorderLayout.NORTH); + + codeAreaPanel = new JPanel(new BorderLayout()); + codeAreaPanel.add(codeArea, BorderLayout.CENTER); + codeArea.setComponentPopupMenu(new JPopupMenu() { + @Override + public void show(Component invoker, int x, int y) { + int clickedX = x; + int clickedY = y; + if (invoker instanceof JViewport) { + clickedX += ((JViewport) invoker).getParent().getX(); + clickedY += ((JViewport) invoker).getParent().getY(); + } + + removeAll(); + final JPopupMenu menu = createPopupMenu(clickedX, clickedY); + menu.show(invoker, x, y); + } + }); + + valuesPanelScrollBar = new JScrollPane(); + valuesPanel.setCodeArea(codeArea); + valuesPanel.updateValues(); + valuesPanelScrollBar.setViewportView(valuesPanel); + valuesPanelScrollBar.setMinimumSize(new Dimension(10, valuesPanel.getMinimumSize().height)); + setShowValuesPanel(true); + add(codeAreaPanel, BorderLayout.CENTER); + + registerBinaryStatus(statusPanel); + add(statusPanel, BorderLayout.SOUTH); + invalidate(); + } + + private void setShowValuesPanel(boolean show) { + if (valuesPanelVisible != show) { + if (show) { + codeAreaPanel.add(valuesPanelScrollBar, BorderLayout.SOUTH); + codeAreaPanel.revalidate(); + codeAreaPanel.repaint(); + valuesPanelVisible = true; + valuesPanel.enableUpdate(); + } else { + valuesPanel.disableUpdate(); + codeAreaPanel.remove(valuesPanelScrollBar); + codeAreaPanel.revalidate(); + codeAreaPanel.repaint(); + valuesPanelVisible = false; + } + } + } + + public void registerBinaryStatus(BinaryStatusApi binaryStatusApi) { + this.binaryStatus = binaryStatusApi; + codeArea.addCaretMovedListener((CodeAreaCaretPosition caretPosition) -> { + binaryStatus.setCursorPosition(caretPosition); + }); + codeArea.addSelectionChangedListener(() -> { + binaryStatus.setSelectionRange(codeArea.getSelection()); + }); + codeArea.addDataChangedListener(() -> { + binaryStatus.setCurrentDocumentSize(codeArea.getDataSize(), codeArea.getDataSize()); + }); + binaryStatus.setCurrentDocumentSize(codeArea.getDataSize(), codeArea.getDataSize()); + + codeArea.addEditModeChangedListener(binaryStatus::setEditMode); + binaryStatus.setEditMode(codeArea.getEditMode(), codeArea.getActiveOperation()); + } + + /** + * Returns platform specific down mask filter. + * + * @return down mask for meta keys + */ + @SuppressWarnings("deprecation") + public static int getMetaMask() { + try { + switch (java.awt.Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()) { + case java.awt.Event.META_MASK: + return KeyEvent.META_DOWN_MASK; + case java.awt.Event.SHIFT_MASK: + return KeyEvent.SHIFT_DOWN_MASK; + case java.awt.Event.ALT_MASK: + return KeyEvent.ALT_DOWN_MASK; + default: + return KeyEvent.CTRL_DOWN_MASK; + } + } catch (java.awt.HeadlessException ex) { + return KeyEvent.CTRL_DOWN_MASK; + } + } + + @Nonnull + private JPopupMenu createPopupMenu(int x, int y) { + JPopupMenu menu = new JPopupMenu(); + + JMenu viewMenu = new JMenu("View"); + JMenu codeTypeMenu = new JMenu("Code Type"); + ButtonGroup codeTypeButtonGroup = new ButtonGroup(); + JRadioButtonMenuItem binaryCodeTypeMenuItem = new JRadioButtonMenuItem(new AbstractAction("Binary") { + @Override + public void actionPerformed(ActionEvent e) { + codeArea.setCodeType(CodeType.BINARY); + updateCycleButtonState(); + menu.setVisible(false); + } + }); + codeTypeButtonGroup.add(binaryCodeTypeMenuItem); + JRadioButtonMenuItem octalCodeTypeMenuItem = new JRadioButtonMenuItem(new AbstractAction("Octal") { + @Override + public void actionPerformed(ActionEvent e) { + codeArea.setCodeType(CodeType.OCTAL); + updateCycleButtonState(); + menu.setVisible(false); + } + }); + codeTypeButtonGroup.add(octalCodeTypeMenuItem); + JRadioButtonMenuItem decimalCodeTypeMenuItem = new JRadioButtonMenuItem(new AbstractAction("Decimal") { + @Override + public void actionPerformed(ActionEvent e) { + codeArea.setCodeType(CodeType.DECIMAL); + updateCycleButtonState(); + menu.setVisible(false); + } + }); + codeTypeButtonGroup.add(decimalCodeTypeMenuItem); + JRadioButtonMenuItem hexadecimalCodeTypeMenuItem = new JRadioButtonMenuItem(new AbstractAction("Hexadecimal") { + @Override + public void actionPerformed(ActionEvent e) { + codeArea.setCodeType(CodeType.HEXADECIMAL); + updateCycleButtonState(); + menu.setVisible(false); + } + }); + codeTypeButtonGroup.add(hexadecimalCodeTypeMenuItem); + codeTypeMenu.add(binaryCodeTypeMenuItem); + codeTypeMenu.add(octalCodeTypeMenuItem); + codeTypeMenu.add(decimalCodeTypeMenuItem); + codeTypeMenu.add(hexadecimalCodeTypeMenuItem); + switch (codeArea.getCodeType()) { + case BINARY: { + binaryCodeTypeMenuItem.setSelected(true); + break; + } + case OCTAL: { + octalCodeTypeMenuItem.setSelected(true); + break; + } + case DECIMAL: { + decimalCodeTypeMenuItem.setSelected(true); + break; + } + case HEXADECIMAL: { + hexadecimalCodeTypeMenuItem.setSelected(true); + break; + } + } + + viewMenu.add(codeTypeMenu); + JCheckBoxMenuItem showValuesPanelMenuItem = new JCheckBoxMenuItem("Show values panel"); + showValuesPanelMenuItem.setSelected(valuesPanelVisible); + showValuesPanelMenuItem.addActionListener((event) -> { + setShowValuesPanel(showValuesPanelMenuItem.isSelected()); + menu.setVisible(false); + }); + viewMenu.add(showValuesPanelMenuItem); + JCheckBoxMenuItem codeColorizationMenuItem = new JCheckBoxMenuItem("Code Colorization"); + codeColorizationMenuItem.setSelected(((HighlightNonAsciiCodeAreaPainter) codeArea.getPainter()).isNonAsciiHighlightingEnabled()); + codeColorizationMenuItem.addActionListener((event) -> { + ((HighlightNonAsciiCodeAreaPainter) codeArea.getPainter()).setNonAsciiHighlightingEnabled(codeColorizationMenuItem.isSelected()); + menu.setVisible(false); + }); + viewMenu.add(codeColorizationMenuItem); + menu.add(viewMenu); + + final JMenuItem copyMenuItem = new JMenuItem("Copy"); + copyMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, HexViewer.getMetaMask())); + copyMenuItem.setEnabled(codeArea.hasSelection()); + copyMenuItem.addActionListener((ActionEvent e) -> { + codeArea.copy(); + }); + menu.add(copyMenuItem); + + final JMenuItem selectAllMenuItem = new JMenuItem("Select All"); + selectAllMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, HexViewer.getMetaMask())); + selectAllMenuItem.addActionListener((ActionEvent e) -> { + codeArea.selectAll(); + }); + menu.add(selectAllMenuItem); + return menu; + } + + private void updateCycleButtonState() { + CodeType codeType = codeArea.getCodeType(); + cycleCodeTypeButton.setText(codeType.name().substring(0, 3)); + } +} diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/JHexEditor.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/JHexEditor.java deleted file mode 100644 index 73d63c50..00000000 --- a/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/JHexEditor.java +++ /dev/null @@ -1,346 +0,0 @@ -package the.bytecode.club.bytecodeviewer.gui.hexviewer; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.event.AdjustmentEvent; -import java.awt.event.AdjustmentListener; -import java.awt.event.FocusEvent; -import java.awt.event.FocusListener; -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; -import java.awt.event.MouseWheelEvent; -import java.awt.event.MouseWheelListener; -import javax.swing.JPanel; -import javax.swing.JScrollBar; -import javax.swing.SwingUtilities; -import the.bytecode.club.bytecodeviewer.BytecodeViewer; -import the.bytecode.club.bytecodeviewer.Configuration; - -/** - * @author laullon - * @since 08/04/2003 - */ - -public class JHexEditor extends JPanel implements FocusListener, AdjustmentListener, MouseWheelListener -{ - protected int textLength = 16; - protected int lastWidth; - - byte[] buf; - public int cursor; - public Font font; - protected int border = 2; - public boolean DEBUG = false; - private final JScrollBar sb; - private int begin = 0; - private int lines = 10; - private final JHexEditorHEX hex; - private final JHexEditorASCII ascii; - - public JHexEditor(byte[] buff) - { - super(); - - this.buf = buff; - this.font = new Font(Font.MONOSPACED, Font.PLAIN, BytecodeViewer.viewer.getFontSize()); - - checkSize(); - - this.addMouseWheelListener(this); - - sb = new JScrollBar(JScrollBar.VERTICAL); - sb.addAdjustmentListener(this); - sb.setMinimum(0); - sb.setMaximum(buff.length / getLines()); - - JPanel p1, p2, p3; - // HEX Editor - hex = new JHexEditorHEX(this); - p1 = new JPanel(new BorderLayout(1, 1)); - p1.add(hex, BorderLayout.CENTER); - p1.add(new Column(), BorderLayout.NORTH); - - p2 = new JPanel(new BorderLayout(1, 1)); - p2.add(new Row(), BorderLayout.CENTER); - p2.add(new Cell(), BorderLayout.NORTH); - - // ASCII Editor - ascii = new JHexEditorASCII(this); - p3 = new JPanel(new BorderLayout(1, 1)); - p3.add(sb, BorderLayout.EAST); - p3.add(ascii, BorderLayout.CENTER); - p3.add(new Cell(), BorderLayout.NORTH); - - JPanel panel = new JPanel(); - panel.setLayout(new BorderLayout(1, 1)); - panel.add(p1, BorderLayout.CENTER); - panel.add(p2, BorderLayout.WEST); - panel.add(p3, BorderLayout.EAST); - - this.setLayout(new BorderLayout(1, 1)); - this.add(panel, BorderLayout.CENTER); - - //attach CTRL + Mouse Wheel Zoom - SwingUtilities.invokeLater(this::attachCtrlMouseWheelZoom); - } - - @Override - public void paint(Graphics g) - { - checkSize(); - - FontMetrics fn = getFontMetrics(font); - - Rectangle rec = this.getBounds(); - lines = (rec.height / fn.getHeight()) - 1; - int n = (buf.length / textLength) - 1; - if (lines > n) { - lines = n; - begin = 0; - } - - sb.setValues(getBegin(), +getLines(), 0, buf.length / textLength); - sb.setValueIsAdjusting(true); - super.paint(g); - } - - public void attachCtrlMouseWheelZoom() - { - //get the existing scroll event - MouseWheelListener ogListener = getMouseWheelListeners().length > 0 ? - getMouseWheelListeners()[0] : null; - - //remove the existing event - if(ogListener != null) - removeMouseWheelListener(ogListener); - - //add a new event - addMouseWheelListener(e -> - { - if ((e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0) - { - int size = font.getSize(); - - Font newFont; - if (e.getWheelRotation() > 0) //Up - newFont = new Font(font.getName(), font.getStyle(), --size >= 2 ? --size : 2); - else //Down - newFont = new Font(font.getName(), font.getStyle(), ++size); - - setFont(newFont); - hex.setFont(newFont); - ascii.setFont(newFont); - font = newFont; - - e.consume(); - } - else if(ogListener != null) - { - ogListener.mouseWheelMoved(e); - } - }); - } - - protected int getBegin() { - return begin; - } - - protected int getLines() { - return lines; - } - - protected void background(Graphics g, int x, int y, int s) { - FontMetrics fn = getFontMetrics(font); - g.fillRect(((fn.stringWidth(" ") + 1) * x) + border, - (fn.getHeight() * y) + border, ((fn.stringWidth(" ") + 1) * s), - fn.getHeight() + 1); - } - - protected void border(Graphics g, int x, int y, int s) { - FontMetrics fn = getFontMetrics(font); - g.drawRect(((fn.stringWidth(" ") + 1) * x) + border, - (fn.getHeight() * y) + border, ((fn.stringWidth(" ") + 1) * s), - fn.getHeight() + 1); - } - - protected void printString(Graphics graphics, String s, int x, int y) { - Graphics2D g = (Graphics2D) graphics; - FontMetrics fn = getFontMetrics(font); - g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, - RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - g.drawString(s, ((fn.stringWidth(" ") + 1) * x) + border, - ((fn.getHeight() * (y + 1)) - fn.getMaxDescent()) + border); - } - - @Override - public void focusGained(FocusEvent e) { - this.repaint(); - } - - @Override - public void focusLost(FocusEvent e) { - this.repaint(); - } - - @Override - public void adjustmentValueChanged(AdjustmentEvent e) { - begin = e.getValue(); - if (begin < 0) - begin = 0; - repaint(); - } - - @Override - public void mouseWheelMoved(MouseWheelEvent e) { - begin += (e.getUnitsToScroll()); - if ((begin + lines) >= buf.length / textLength) - begin = (buf.length / textLength) - lines; - if (begin < 0) - begin = 0; - repaint(); - } - - public void keyPressed(KeyEvent e) { - /* switch(e.getKeyCode()) { case 33: // rep if(cursor>=(16*lines)) - cursor-=(16*lines); refreshCursor(); break; case 34: // fin - if(cursor<(buff.length-(16*lines))) cursor+=(16*lines); - refreshCursor(); break; case 35: // fin cursor=buff.length-1; - refreshCursor(); break; case 36: // ini cursor=0; - refreshCursor(); break; case 37: // <-- if(cursor!=0) cursor--; - refreshCursor(); break; case 38: // <-- if(cursor>15) cursor-=16; - refreshCursor(); break; case 39: // --> if(cursor!=(buff.length-1)) - cursor++; refreshCursor(); break; case 40: // --> - if(cursor<(buff.length-16)) cursor+=16; refreshCursor(); break; } */ - } - - private class Column extends JPanel { - private static final long serialVersionUID = -1734199617526339842L; - - public Column() { - this.setLayout(new BorderLayout(1, 1)); - } - - @Override - public Dimension getPreferredSize() { - return getMinimumSize(); - } - - @Override - public Dimension getMinimumSize() { - Dimension d = new Dimension(); - FontMetrics fn = getFontMetrics(font); - int h = fn.getHeight(); - int nl = 1; - d.setSize(((fn.stringWidth(" ") + 1) * ((textLength * 3) - 1)) - + (border * 2) + 1, h * nl + (border * 2) + 1); - return d; - } - - @Override - public void paint(Graphics g) { - Dimension d = getMinimumSize(); - g.setColor(Configuration.lafTheme.isDark() ? Color.darkGray : Color.white); - g.fillRect(0, 0, d.width, d.height); - g.setColor(Configuration.lafTheme.isDark() ? Color.white : Color.black); - g.setFont(font); - - for (int n = 0; n < textLength; n++) { - if (n == (cursor % textLength)) - border(g, n * 3, 0, 2); - String s = "00" + Integer.toHexString(n); - s = s.substring(s.length() - 2); - printString(g, s, n * 3, 0); - } - } - } - - private class Cell extends JPanel { - private static final long serialVersionUID = -6124062720565016834L; - - @Override - public Dimension getPreferredSize() { - return getMinimumSize(); - } - - @Override - public Dimension getMinimumSize() { - Dimension d = new Dimension(); - FontMetrics fn = getFontMetrics(font); - int h = fn.getHeight(); - d.setSize((fn.stringWidth(" ") + 1) + (border * 2) + 1, h - + (border * 2) + 1); - return d; - } - - } - - private class Row extends JPanel { - private static final long serialVersionUID = 8797347523486018051L; - - public Row() { - this.setLayout(new BorderLayout(1, 1)); - } - - @Override - public Dimension getPreferredSize() { - return getMinimumSize(); - } - - @Override - public Dimension getMinimumSize() { - Dimension d = new Dimension(); - FontMetrics fn = getFontMetrics(font); - int h = fn.getHeight(); - int nl = getLines(); - d.setSize((fn.stringWidth(" ") + 1) * (8) + (border * 2) + 1, h - * nl + (border * 2) + 1); - return d; - } - - @Override - public void paint(Graphics g) - { - Dimension d = getMinimumSize(); - g.setColor(Configuration.lafTheme.isDark() ? Color.darkGray : Color.white); - g.fillRect(0, 0, d.width, d.height); - g.setColor(Configuration.lafTheme.isDark() ? Color.white : Color.black); - g.setFont(font); - - int ini = getBegin(); - int fin = ini + getLines(); - int y = 0; - for (int n = ini; n < fin; n++) - { - if (n == (cursor / textLength)) - border(g, 0, y, 8); - String s = "0000000000000" + Integer.toHexString(n); - s = s.substring(s.length() - 8); - printString(g, s, 0, y++); - } - } - } - - public void checkSize() - { - int width = getWidth(); - - if(lastWidth != width) - { - double spacer = 1.5; - textLength = (int) ((int) (width / 28.4)/spacer); - lastWidth = width; - hex.revalidate(); - ascii.revalidate(); - revalidate(); - } - } - - private static final long serialVersionUID = 2289328616534802372L; -} diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/JHexEditorASCII.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/JHexEditorASCII.java deleted file mode 100644 index 28c28904..00000000 --- a/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/JHexEditorASCII.java +++ /dev/null @@ -1,182 +0,0 @@ -package the.bytecode.club.bytecodeviewer.gui.hexviewer; - -import java.awt.Color; -import java.awt.Dimension; -import java.awt.FontMetrics; -import java.awt.Graphics; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import javax.swing.JComponent; -import the.bytecode.club.bytecodeviewer.Configuration; - -/** - * @author laullon - * @since 09/04/2003 - */ - -public class JHexEditorASCII extends JComponent implements MouseListener, KeyListener -{ - private final JHexEditor he; - - public JHexEditorASCII(JHexEditor he) { - this.he = he; - addMouseListener(this); - addKeyListener(this); - addFocusListener(he); - } - - @Override - public Dimension getPreferredSize() { - debug("getPreferredSize()"); - return getMinimumSize(); - } - - @Override - public Dimension getMinimumSize() - { - debug("getMinimumSize()"); - - Dimension d = new Dimension(); - FontMetrics fn = getFontMetrics(he.font); - int w = fn.stringWidth(" "); - int h = fn.getHeight(); - int nl = he.getLines(); - int len = he.textLength + 1; - - int width = (len * w) + (he.border * 2) + 5; - - //trim inaccuracy - if(len > 16) - { - int diff = 16-len; - width += ((len * w) / (diff) * diff); - } - - //System.out.println("Values: " + w + " and " + nl + " vs " + len + " ["+width+"]"); - - d.setSize(width, h * nl + (he.border * 2) + 1); - - return d; - } - - @Override - public void paint(Graphics g) { - debug("paint(" + g + ")"); - debug("cursor=" + he.cursor + " buff.length=" + he.buf.length); - Dimension d = getMinimumSize(); - g.setColor(Configuration.lafTheme.isDark() ? Color.darkGray : Color.white); - g.fillRect(0, 0, d.width, d.height); - g.setColor(Configuration.lafTheme.isDark() ? Color.white : Color.black); - - g.setFont(he.font); - - // ASCII data - int start = he.getBegin() * he.textLength; - int stop = start + (he.getLines() * he.textLength); - if (stop > he.buf.length) - stop = he.buf.length; - - int x = 0; - int y = 0; - for (int n = start; n < stop; n++) { - if (n == he.cursor) { - g.setColor(Color.blue); - if (hasFocus()) - he.background(g, x, y, 1); - else - he.border(g, x, y, 1); - if (hasFocus()) - g.setColor(Configuration.lafTheme.isDark() ? Color.black : Color.white); - else - g.setColor(Configuration.lafTheme.isDark() ? Color.white : Color.black); - } else { - g.setColor(Configuration.lafTheme.isDark() ? Color.white : Color.black); - } - - String s = String.valueOf((char) (he.buf[n] & 0xFF));//"" + new Character((char) he.buff[n]); - if ((he.buf[n] < 20) || (he.buf[n] > 126)) - s = ".";//"" + (char) 16; - he.printString(g, s, (x++), y); - if (x == he.textLength) { - x = 0; - y++; - } - } - - } - - private void debug(String s) { - if (he.DEBUG) - System.out.println("JHexEditorASCII ==> " + s); - } - - public int calculateMousePosition(int x, int y) { - FontMetrics fn = getFontMetrics(he.font); - x = x / (fn.stringWidth(" ") + 1); - y = y / fn.getHeight(); - debug("x=" + x + " ,y=" + y); - return x + ((y + he.getBegin()) * he.textLength); - } - - @Override - public void mouseClicked(MouseEvent e) { - debug("mouseClicked(" + e + ")"); - he.cursor = calculateMousePosition(e.getX(), e.getY()); - this.requestFocus(); - he.repaint(); - } - - @Override - public void mousePressed(MouseEvent e) { - } - - @Override - public void mouseReleased(MouseEvent e) { - } - - @Override - public void mouseEntered(MouseEvent e) { - } - - @Override - public void mouseExited(MouseEvent e) { - } - - // KeyListener - @Override - public void keyTyped(KeyEvent e) { - /* - * debug("keyTyped("+e+")"); - * - * he.buff[he.cursor]=(byte)e.getKeyChar(); - * - * if(he.cursor!=(he.buff.length-1)) he.cursor++; he.repaint(); - */ - } - - @Override - public void keyPressed(KeyEvent e) { - debug("keyPressed(" + e + ")"); - he.keyPressed(e); - } - - @Override - public void keyReleased(KeyEvent e) { - debug("keyReleased(" + e + ")"); - } - - @SuppressWarnings("deprecation") - @Override - public boolean isFocusTraversable() { - return true; - } - - @Override - public boolean isFocusable() { - return true; - } - - private static final long serialVersionUID = 5505374841731053461L; -} diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/JHexEditorHEX.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/JHexEditorHEX.java deleted file mode 100644 index 79511e5c..00000000 --- a/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/JHexEditorHEX.java +++ /dev/null @@ -1,176 +0,0 @@ -package the.bytecode.club.bytecodeviewer.gui.hexviewer; - -import java.awt.Color; -import java.awt.Dimension; -import java.awt.FontMetrics; -import java.awt.Graphics; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import javax.swing.JComponent; -import the.bytecode.club.bytecodeviewer.Configuration; - -/** - * @author laullon - * @since 09/04/2003 - */ - -public class JHexEditorHEX extends JComponent implements MouseListener, KeyListener -{ - private final JHexEditor he; - - public JHexEditorHEX(JHexEditor he) - { - this.he = he; - addMouseListener(this); - addKeyListener(this); - addFocusListener(he); - } - - @Override - public Dimension getMaximumSize() - { - debug("getMaximumSize()"); - return getMinimumSize(); - } - - @Override - public void paint(Graphics g) - { - debug("paint(" + g + ")"); - debug("cursor=" + he.cursor + " buff.length=" + he.buf.length); - - if(!Configuration.lafTheme.isDark()) - { - //TODO if you want a background for the hex-text uncomment this - //g.setColor(Color.white); - //g.fillRect(0, 0, getWidth(), getHeight()); - g.setColor(Color.black); - } - else - { - g.setColor(Color.white); - } - - g.setFont(he.font); - - int start = he.getBegin() * he.textLength; - int stop = start + (he.getLines() * he.textLength); - if (stop > he.buf.length) - stop = he.buf.length; - - // HEX data - int x = 0; - int y = 0; - for (int n = start; n < stop; n++) { - if (n == he.cursor) { - if (hasFocus()) { - g.setColor(Color.black); - he.background(g, (x * 3), y, 2); - g.setColor(Color.blue); - int cursor = 0; - he.background(g, (x * 3) + cursor, y, 1); - } else { - g.setColor(Color.blue); - he.border(g, (x * 3), y, 2); - } - - if (hasFocus()) - g.setColor(Color.white); - else - g.setColor(Configuration.lafTheme.isDark() ? Color.white : Color.black); - } else { - g.setColor(Configuration.lafTheme.isDark() ? Color.white : Color.black); - } - - String s = ("0" + Integer.toHexString(he.buf[n])); - s = s.substring(s.length() - 2); - he.printString(g, s, ((x++) * 3), y); - if (x == he.textLength) { - x = 0; - y++; - } - } - } - - private void debug(String s) { - if (he.DEBUG) - System.out.println("JHexEditorHEX ==> " + s); - } - - public int calculateMousePosition(int x, int y) { - FontMetrics fn = getFontMetrics(he.font); - x = x / ((fn.stringWidth(" ") + 1) * 3); - y = y / fn.getHeight(); - debug("x=" + x + " ,y=" + y); - return x + ((y + he.getBegin()) * he.textLength); - } - - @Override - public void mouseClicked(MouseEvent e) { - debug("mouseClicked(" + e + ")"); - he.cursor = calculateMousePosition(e.getX(), e.getY()); - this.requestFocus(); - he.repaint(); - } - - @Override - public void mousePressed(MouseEvent e) { - } - - @Override - public void mouseReleased(MouseEvent e) { - } - - @Override - public void mouseEntered(MouseEvent e) { - } - - @Override - public void mouseExited(MouseEvent e) { - } - - // KeyListener - @Override - public void keyTyped(KeyEvent e) { - debug("keyTyped(" + e + ")"); - - /* - * char c=e.getKeyChar(); - * if(((c>='0')&&(c<='9'))||((c>='A')&&(c<='F'))||((c>='a')&&(c<='f'))) - * { char[] str=new char[2]; String - * n="00"+Integer.toHexString((int)he.buff[he.cursor]); if(n.length()>2) - * n=n.substring(n.length()-2); str[1-cursor]=n.charAt(1-cursor); - * str[cursor]=e.getKeyChar(); - * he.buff[he.cursor]=(byte)Integer.parseInt(new String(str),16); - * - * if(cursor!=1) cursor=1; else if(he.cursor!=(he.buff.length-1)){ - * he.cursor++; cursor=0;} he.refreshCursor(); } - */ - } - - @Override - public void keyPressed(KeyEvent e) { - debug("keyPressed(" + e + ")"); - he.keyPressed(e); - } - - @Override - public void keyReleased(KeyEvent e) { - debug("keyReleased(" + e + ")"); - } - - @SuppressWarnings("deprecation") - @Override - public boolean isFocusTraversable() { - return true; - } - - @Override - public boolean isFocusable() { - return true; - } - - private static final long serialVersionUID = 1481995655372014571L; -} diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/StatusCursorPositionFormat.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/StatusCursorPositionFormat.java new file mode 100644 index 00000000..8c633a4f --- /dev/null +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/StatusCursorPositionFormat.java @@ -0,0 +1,43 @@ +package the.bytecode.club.bytecodeviewer.gui.hexviewer; + +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.ParametersAreNonnullByDefault; +import org.exbin.bined.PositionCodeType; + +/** + * Cursor position format for status. + * + * @author hajdam + */ +@ParametersAreNonnullByDefault +public class StatusCursorPositionFormat { + + private PositionCodeType positionCodeType = PositionCodeType.DECIMAL; + private boolean showOffset = true; + + public StatusCursorPositionFormat() { + } + + public StatusCursorPositionFormat(PositionCodeType positionCodeType, boolean showOffset) { + this.positionCodeType = positionCodeType; + this.showOffset = showOffset; + } + + @Nonnull + public PositionCodeType getCodeType() { + return positionCodeType; + } + + public void setCodeType(PositionCodeType positionCodeType) { + this.positionCodeType = Objects.requireNonNull(positionCodeType); + } + + public boolean isShowOffset() { + return showOffset; + } + + public void setShowOffset(boolean showOffset) { + this.showOffset = showOffset; + } +} diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/StatusDocumentSizeFormat.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/StatusDocumentSizeFormat.java new file mode 100644 index 00000000..46780b57 --- /dev/null +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/StatusDocumentSizeFormat.java @@ -0,0 +1,44 @@ +package the.bytecode.club.bytecodeviewer.gui.hexviewer; + +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.ParametersAreNonnullByDefault; +import org.exbin.bined.PositionCodeType; + +/** + * Document size format for status. + * + * @author hajdam + */ +@ParametersAreNonnullByDefault +public class StatusDocumentSizeFormat { + + private PositionCodeType positionCodeType = PositionCodeType.DECIMAL; + private boolean showRelative = true; + + public StatusDocumentSizeFormat() { + + } + + public StatusDocumentSizeFormat(PositionCodeType positionCodeType, boolean showRelative) { + this.positionCodeType = positionCodeType; + this.showRelative = showRelative; + } + + @Nonnull + public PositionCodeType getCodeType() { + return positionCodeType; + } + + public void setCodeType(PositionCodeType positionCodeType) { + this.positionCodeType = Objects.requireNonNull(positionCodeType); + } + + public boolean isShowRelative() { + return showRelative; + } + + public void setShowRelative(boolean showRelativeSize) { + this.showRelative = showRelativeSize; + } +} diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/ValuesPanel.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/ValuesPanel.java new file mode 100644 index 00000000..5e9af864 --- /dev/null +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/hexviewer/ValuesPanel.java @@ -0,0 +1,1067 @@ +package the.bytecode.club.bytecodeviewer.gui.hexviewer; + +import java.awt.event.KeyEvent; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.InputMismatchException; +import java.util.Objects; +import javax.annotation.ParametersAreNonnullByDefault; +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; +import org.exbin.bined.CaretMovedListener; +import org.exbin.bined.CodeAreaCaretPosition; +import org.exbin.bined.DataChangedListener; +import org.exbin.bined.swing.basic.CodeArea; +import org.exbin.auxiliary.paged_data.BinaryData; +import org.exbin.bined.capability.EditModeCapable; + +/** + * Values side panel. + * + * @author hajdam + */ +@ParametersAreNonnullByDefault +public class ValuesPanel extends javax.swing.JPanel { + + public static final int UBYTE_MAX_VALUE = 255; + public static final int SWORD_MIN_VALUE = -32768; + public static final int SWORD_MAX_VALUE = 32767; + public static final int UWORD_MAX_VALUE = 65535; + public static final long UINT_MAX_VALUE = 4294967295l; + public static final BigInteger ULONG_MAX_VALUE = new BigInteger("4294967295"); + public static final BigInteger BIG_INTEGER_BYTE_MASK = BigInteger.valueOf(255); + public static final String VALUE_OUT_OF_RANGE = "Value is out of range"; + public static int CACHE_SIZE = 250; + + private CodeArea codeArea; + private long dataPosition; + private DataChangedListener dataChangedListener; + private CaretMovedListener caretMovedListener; + + private final byte[] valuesCache = new byte[CACHE_SIZE]; + private final ByteBuffer byteBuffer = ByteBuffer.wrap(valuesCache); + private final ValuesUpdater valuesUpdater = new ValuesUpdater(); + + private javax.swing.JRadioButton bigEndianRadioButton; + private javax.swing.JCheckBox binaryCheckBox0; + private javax.swing.JCheckBox binaryCheckBox1; + private javax.swing.JCheckBox binaryCheckBox2; + private javax.swing.JCheckBox binaryCheckBox3; + private javax.swing.JCheckBox binaryCheckBox4; + private javax.swing.JCheckBox binaryCheckBox5; + private javax.swing.JCheckBox binaryCheckBox6; + private javax.swing.JCheckBox binaryCheckBox7; + private javax.swing.JLabel binaryLabel; + private javax.swing.JLabel byteLabel; + private javax.swing.JTextField byteTextField; + private javax.swing.JLabel characterLabel; + private javax.swing.JTextField characterTextField; + private javax.swing.JLabel doubleLabel; + private javax.swing.JTextField doubleTextField; + private javax.swing.ButtonGroup endianButtonGroup; + private javax.swing.JLabel floatLabel; + private javax.swing.JTextField floatTextField; + private javax.swing.JLabel intLabel; + private javax.swing.JTextField intTextField; + private javax.swing.ButtonGroup integerSignButtonGroup; + private javax.swing.JSeparator jSeparator1; + private javax.swing.JRadioButton littleEndianRadioButton; + private javax.swing.JLabel longLabel; + private javax.swing.JTextField longTextField; + private javax.swing.JRadioButton signedRadioButton; + private javax.swing.JLabel stringLabel; + private javax.swing.JTextField stringTextField; + private javax.swing.JRadioButton unsignedRadioButton; + private javax.swing.JLabel wordLabel; + private javax.swing.JTextField wordTextField; + + public ValuesPanel() { + 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() { + + endianButtonGroup = new javax.swing.ButtonGroup(); + integerSignButtonGroup = new javax.swing.ButtonGroup(); + binaryLabel = new javax.swing.JLabel(); + binaryCheckBox0 = new javax.swing.JCheckBox(); + binaryCheckBox1 = new javax.swing.JCheckBox(); + binaryCheckBox2 = new javax.swing.JCheckBox(); + binaryCheckBox3 = new javax.swing.JCheckBox(); + binaryCheckBox4 = new javax.swing.JCheckBox(); + binaryCheckBox5 = new javax.swing.JCheckBox(); + binaryCheckBox6 = new javax.swing.JCheckBox(); + binaryCheckBox7 = new javax.swing.JCheckBox(); + byteLabel = new javax.swing.JLabel(); + byteTextField = new javax.swing.JTextField(); + wordLabel = new javax.swing.JLabel(); + wordTextField = new javax.swing.JTextField(); + intLabel = new javax.swing.JLabel(); + intTextField = new javax.swing.JTextField(); + longLabel = new javax.swing.JLabel(); + longTextField = new javax.swing.JTextField(); + floatLabel = new javax.swing.JLabel(); + floatTextField = new javax.swing.JTextField(); + doubleLabel = new javax.swing.JLabel(); + doubleTextField = new javax.swing.JTextField(); + characterLabel = new javax.swing.JLabel(); + characterTextField = new javax.swing.JTextField(); + stringLabel = new javax.swing.JLabel(); + stringTextField = new javax.swing.JTextField(); + jSeparator1 = new javax.swing.JSeparator(); + bigEndianRadioButton = new javax.swing.JRadioButton(); + littleEndianRadioButton = new javax.swing.JRadioButton(); + signedRadioButton = new javax.swing.JRadioButton(); + unsignedRadioButton = new javax.swing.JRadioButton(); + + setMaximumSize(new java.awt.Dimension(246, 447)); + setMinimumSize(new java.awt.Dimension(246, 447)); + + binaryLabel.setText("Binary"); + + binaryCheckBox0.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + binaryCheckBox0ActionPerformed(evt); + } + }); + + binaryCheckBox1.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + binaryCheckBox1ActionPerformed(evt); + } + }); + + binaryCheckBox2.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + binaryCheckBox2ActionPerformed(evt); + } + }); + + binaryCheckBox3.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + binaryCheckBox3ActionPerformed(evt); + } + }); + + binaryCheckBox4.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + binaryCheckBox4ActionPerformed(evt); + } + }); + + binaryCheckBox5.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + binaryCheckBox5ActionPerformed(evt); + } + }); + + binaryCheckBox6.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + binaryCheckBox6ActionPerformed(evt); + } + }); + + binaryCheckBox7.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + binaryCheckBox7ActionPerformed(evt); + } + }); + + byteLabel.setText("Byte"); + + byteTextField.setEditable(false); + byteTextField.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + byteTextFieldKeyReleased(evt); + } + }); + + wordLabel.setText("Word"); + + wordTextField.setEditable(false); + wordTextField.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + wordTextFieldKeyReleased(evt); + } + }); + + intLabel.setText("Integer"); + + intTextField.setEditable(false); + intTextField.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + intTextFieldKeyReleased(evt); + } + }); + + longLabel.setText("Long"); + + longTextField.setEditable(false); + longTextField.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + longTextFieldKeyReleased(evt); + } + }); + + floatLabel.setText("Float"); + + floatTextField.setEditable(false); + floatTextField.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + floatTextFieldKeyReleased(evt); + } + }); + + doubleLabel.setText("Double"); + + doubleTextField.setEditable(false); + doubleTextField.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + doubleTextFieldKeyReleased(evt); + } + }); + + characterLabel.setText("Character"); + + characterTextField.setEditable(false); + characterTextField.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + characterTextFieldKeyReleased(evt); + } + }); + + stringLabel.setText("String"); + + stringTextField.setEditable(false); + stringTextField.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + stringTextFieldKeyReleased(evt); + } + }); + + jSeparator1.setOrientation(javax.swing.SwingConstants.VERTICAL); + + endianButtonGroup.add(bigEndianRadioButton); + bigEndianRadioButton.setSelected(true); + bigEndianRadioButton.setText("BE"); + bigEndianRadioButton.setToolTipText("Big Endian"); + bigEndianRadioButton.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + bigEndianRadioButtonStateChanged(evt); + } + }); + + endianButtonGroup.add(littleEndianRadioButton); + littleEndianRadioButton.setText("LE"); + littleEndianRadioButton.setToolTipText("Little Endian"); + littleEndianRadioButton.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + littleEndianRadioButtonStateChanged(evt); + } + }); + + integerSignButtonGroup.add(signedRadioButton); + signedRadioButton.setSelected(true); + signedRadioButton.setText("Sig"); + signedRadioButton.setToolTipText("Signed Integers"); + signedRadioButton.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + signedRadioButtonStateChanged(evt); + } + }); + + integerSignButtonGroup.add(unsignedRadioButton); + unsignedRadioButton.setText("Uns"); + unsignedRadioButton.setToolTipText("Unsigned Integers"); + unsignedRadioButton.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + unsignedRadioButtonStateChanged(evt); + } + }); + + 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) + .addGroup(layout.createSequentialGroup() + .addComponent(bigEndianRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(littleEndianRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 2, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(signedRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(unsignedRadioButton)) + .addComponent(binaryLabel) + .addGroup(layout.createSequentialGroup() + .addComponent(binaryCheckBox0) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(binaryCheckBox1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(binaryCheckBox2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(binaryCheckBox3) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(binaryCheckBox4) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(binaryCheckBox5) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(binaryCheckBox6) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(binaryCheckBox7)) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(byteTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 234, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(wordTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 234, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(intTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 234, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(longTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 234, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(byteLabel) + .addComponent(wordLabel) + .addComponent(intLabel) + .addComponent(longLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(characterTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 234, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(floatTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 234, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(floatLabel) + .addComponent(doubleLabel) + .addComponent(doubleTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 234, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(characterLabel) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(stringTextField, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 234, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(stringLabel))))) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(binaryLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(binaryCheckBox0) + .addComponent(binaryCheckBox1) + .addComponent(binaryCheckBox2) + .addComponent(binaryCheckBox3) + .addComponent(binaryCheckBox4) + .addComponent(binaryCheckBox5) + .addComponent(binaryCheckBox6) + .addComponent(binaryCheckBox7)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(layout.createSequentialGroup() + .addComponent(byteLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(byteTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(wordLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(wordTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(intLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(intTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(longLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(longTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(layout.createSequentialGroup() + .addComponent(floatLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(floatTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(doubleLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(doubleTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(characterLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(characterTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(stringLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(stringTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(bigEndianRadioButton) + .addComponent(littleEndianRadioButton)) + .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 28, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(signedRadioButton) + .addComponent(unsignedRadioButton))) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + private void littleEndianRadioButtonStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_littleEndianRadioButtonStateChanged + updateValues(); + }//GEN-LAST:event_littleEndianRadioButtonStateChanged + + private void bigEndianRadioButtonStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_bigEndianRadioButtonStateChanged + updateValues(); + }//GEN-LAST:event_bigEndianRadioButtonStateChanged + + private void signedRadioButtonStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_signedRadioButtonStateChanged + updateValues(); + }//GEN-LAST:event_signedRadioButtonStateChanged + + private void unsignedRadioButtonStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_unsignedRadioButtonStateChanged + updateValues(); + }//GEN-LAST:event_unsignedRadioButtonStateChanged + + private void binaryCheckBox0ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_binaryCheckBox0ActionPerformed + if (!valuesUpdater.isUpdateInProgress() && ((valuesCache[0] & 0x80) > 0 != binaryCheckBox0.isSelected())) { + valuesCache[0] = (byte) (valuesCache[0] ^ 0x80); + modifyValues(1); + } + }//GEN-LAST:event_binaryCheckBox0ActionPerformed + + private void binaryCheckBox1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_binaryCheckBox1ActionPerformed + if (!valuesUpdater.isUpdateInProgress() && ((valuesCache[0] & 0x40) > 0 != binaryCheckBox1.isSelected())) { + valuesCache[0] = (byte) (valuesCache[0] ^ 0x40); + modifyValues(1); + } + }//GEN-LAST:event_binaryCheckBox1ActionPerformed + + private void binaryCheckBox2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_binaryCheckBox2ActionPerformed + if (!valuesUpdater.isUpdateInProgress() && ((valuesCache[0] & 0x20) > 0 != binaryCheckBox2.isSelected())) { + valuesCache[0] = (byte) (valuesCache[0] ^ 0x20); + modifyValues(1); + } + }//GEN-LAST:event_binaryCheckBox2ActionPerformed + + private void binaryCheckBox3ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_binaryCheckBox3ActionPerformed + if (!valuesUpdater.isUpdateInProgress() && ((valuesCache[0] & 0x10) > 0 != binaryCheckBox3.isSelected())) { + valuesCache[0] = (byte) (valuesCache[0] ^ 0x10); + modifyValues(1); + } + }//GEN-LAST:event_binaryCheckBox3ActionPerformed + + private void binaryCheckBox4ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_binaryCheckBox4ActionPerformed + if (!valuesUpdater.isUpdateInProgress() && ((valuesCache[0] & 0x8) > 0 != binaryCheckBox4.isSelected())) { + valuesCache[0] = (byte) (valuesCache[0] ^ 0x8); + modifyValues(1); + } + }//GEN-LAST:event_binaryCheckBox4ActionPerformed + + private void binaryCheckBox5ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_binaryCheckBox5ActionPerformed + if (!valuesUpdater.isUpdateInProgress() && ((valuesCache[0] & 0x4) > 0 != binaryCheckBox5.isSelected())) { + valuesCache[0] = (byte) (valuesCache[0] ^ 0x4); + modifyValues(1); + } + }//GEN-LAST:event_binaryCheckBox5ActionPerformed + + private void binaryCheckBox6ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_binaryCheckBox6ActionPerformed + if (!valuesUpdater.isUpdateInProgress() && ((valuesCache[0] & 0x2) > 0 != binaryCheckBox6.isSelected())) { + valuesCache[0] = (byte) (valuesCache[0] ^ 0x2); + modifyValues(1); + } + }//GEN-LAST:event_binaryCheckBox6ActionPerformed + + private void binaryCheckBox7ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_binaryCheckBox7ActionPerformed + if (!valuesUpdater.isUpdateInProgress() && ((valuesCache[0] & 0x1) > 0 != binaryCheckBox7.isSelected())) { + valuesCache[0] = (byte) (valuesCache[0] ^ 0x1); + modifyValues(1); + } + }//GEN-LAST:event_binaryCheckBox7ActionPerformed + + private void byteTextFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_byteTextFieldKeyReleased + if (evt.getKeyCode() == KeyEvent.VK_ENTER && isEditable()) { + try { + Integer intValue = Integer.valueOf(byteTextField.getText()); + if (isSigned()) { + if (intValue < Byte.MIN_VALUE || intValue > Byte.MAX_VALUE) { + throw new NumberFormatException(VALUE_OUT_OF_RANGE); + } + } else { + if (intValue < 0 || intValue > UBYTE_MAX_VALUE) { + throw new NumberFormatException(VALUE_OUT_OF_RANGE); + } + } + + valuesCache[0] = intValue.byteValue(); + modifyValues(1); + updateValues(); + } catch (NumberFormatException ex) { + showException(ex); + } + } + }//GEN-LAST:event_byteTextFieldKeyReleased + + private void wordTextFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_wordTextFieldKeyReleased + if (evt.getKeyCode() == KeyEvent.VK_ENTER && isEditable()) { + try { + Integer intValue = Integer.valueOf(wordTextField.getText()); + if (isSigned()) { + if (intValue < SWORD_MIN_VALUE || intValue > SWORD_MAX_VALUE) { + throw new NumberFormatException(VALUE_OUT_OF_RANGE); + } + } else { + if (intValue < 0 || intValue > UWORD_MAX_VALUE) { + throw new NumberFormatException(VALUE_OUT_OF_RANGE); + } + } + + if (getByteOrder() == ByteOrder.LITTLE_ENDIAN) { + valuesCache[0] = (byte) (intValue & 0xff); + valuesCache[1] = (byte) ((intValue >> 8) & 0xff); + } else { + valuesCache[0] = (byte) ((intValue >> 8) & 0xff); + valuesCache[1] = (byte) (intValue & 0xff); + } + modifyValues(2); + updateValues(); + } catch (NumberFormatException ex) { + showException(ex); + } + } + }//GEN-LAST:event_wordTextFieldKeyReleased + + private void intTextFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_intTextFieldKeyReleased + if (evt.getKeyCode() == KeyEvent.VK_ENTER && isEditable()) { + try { + Long longValue = Long.valueOf(intTextField.getText()); + if (isSigned()) { + if (longValue < Integer.MIN_VALUE || longValue > Integer.MAX_VALUE) { + throw new NumberFormatException(VALUE_OUT_OF_RANGE); + } + } else { + if (longValue < 0 || longValue > UINT_MAX_VALUE) { + throw new NumberFormatException(VALUE_OUT_OF_RANGE); + } + } + + if (getByteOrder() == ByteOrder.LITTLE_ENDIAN) { + valuesCache[0] = (byte) (longValue & 0xff); + valuesCache[1] = (byte) ((longValue >> 8) & 0xff); + valuesCache[2] = (byte) ((longValue >> 16) & 0xff); + valuesCache[3] = (byte) ((longValue >> 24) & 0xff); + } else { + valuesCache[0] = (byte) ((longValue >> 24) & 0xff); + valuesCache[1] = (byte) ((longValue >> 16) & 0xff); + valuesCache[2] = (byte) ((longValue >> 8) & 0xff); + valuesCache[3] = (byte) (longValue & 0xff); + } + modifyValues(4); + updateValues(); + } catch (NumberFormatException ex) { + showException(ex); + } + } + }//GEN-LAST:event_intTextFieldKeyReleased + + private void longTextFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_longTextFieldKeyReleased + if (evt.getKeyCode() == KeyEvent.VK_ENTER && isEditable()) { + try { + ByteOrder byteOrder = getByteOrder(); + if (isSigned()) { + Long longValue = Long.valueOf(longTextField.getText()); + + byteBuffer.rewind(); + if (byteBuffer.order() != byteOrder) { + byteBuffer.order(byteOrder); + } + + byteBuffer.putLong(longValue); + } else { + BigInteger bigInteger = new BigInteger(longTextField.getText()); + if (bigInteger.compareTo(BigInteger.ZERO) == -1 || bigInteger.compareTo(ULONG_MAX_VALUE) == 1) { + throw new NumberFormatException(VALUE_OUT_OF_RANGE); + } + + if (byteOrder == ByteOrder.LITTLE_ENDIAN) { + for (int i = 0; i < 7; i++) { + BigInteger nextByte = bigInteger.and(BIG_INTEGER_BYTE_MASK); + valuesCache[7 - i] = nextByte.byteValue(); + bigInteger = bigInteger.shiftRight(8); + } + } else { + for (int i = 0; i < 7; i++) { + BigInteger nextByte = bigInteger.and(BIG_INTEGER_BYTE_MASK); + valuesCache[i] = nextByte.byteValue(); + bigInteger = bigInteger.shiftRight(8); + } + } + } + + modifyValues(8); + updateValues(); + } catch (NumberFormatException ex) { + showException(ex); + } + } + }//GEN-LAST:event_longTextFieldKeyReleased + + private void floatTextFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_floatTextFieldKeyReleased + if (evt.getKeyCode() == KeyEvent.VK_ENTER && isEditable()) { + try { + ByteOrder byteOrder = getByteOrder(); + Float floatValue = Float.valueOf(floatTextField.getText()); + + byteBuffer.rewind(); + if (byteBuffer.order() != byteOrder) { + byteBuffer.order(byteOrder); + } + + byteBuffer.putFloat(floatValue); + + modifyValues(4); + updateValues(); + } catch (NumberFormatException ex) { + showException(ex); + } + } + }//GEN-LAST:event_floatTextFieldKeyReleased + + private void doubleTextFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_doubleTextFieldKeyReleased + if (evt.getKeyCode() == KeyEvent.VK_ENTER && isEditable()) { + try { + ByteOrder byteOrder = getByteOrder(); + Double doubleValue = Double.valueOf(doubleTextField.getText()); + + byteBuffer.rewind(); + if (byteBuffer.order() != byteOrder) { + byteBuffer.order(byteOrder); + } + + byteBuffer.putDouble(doubleValue); + + modifyValues(8); + updateValues(); + } catch (NumberFormatException ex) { + showException(ex); + } + } + }//GEN-LAST:event_doubleTextFieldKeyReleased + + private void characterTextFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_characterTextFieldKeyReleased + if (evt.getKeyCode() == KeyEvent.VK_ENTER && isEditable()) { + try { + String characterText = characterTextField.getText(); + if (characterText.length() == 0) { + throw new InputMismatchException("Empty value not valid"); + } + + if (characterText.length() > 1) { + throw new InputMismatchException("Only single character allowed"); + } + + byte[] bytes = characterText.getBytes(codeArea.getCharset()); + System.arraycopy(bytes, 0, valuesCache, 0, bytes.length); + + modifyValues(bytes.length); + updateValues(); + } catch (InputMismatchException ex) { + showException(ex); + } + } + }//GEN-LAST:event_characterTextFieldKeyReleased + + private void stringTextFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_stringTextFieldKeyReleased + if (evt.getKeyCode() == KeyEvent.VK_ENTER && isEditable()) { + try { + String characterText = stringTextField.getText(); + if (characterText.length() == 0) { + throw new InputMismatchException("Empty value not valid"); + } + + byte[] bytes = characterText.getBytes(codeArea.getCharset()); + if (bytes.length > CACHE_SIZE) { + throw new InputMismatchException("String is too long"); + } + System.arraycopy(bytes, 0, valuesCache, 0, bytes.length); + + modifyValues(bytes.length); + updateValues(); + } catch (InputMismatchException ex) { + showException(ex); + } + } + }//GEN-LAST:event_stringTextFieldKeyReleased + + public void setCodeArea(CodeArea codeArea) { + this.codeArea = codeArea; + } + + public void enableUpdate() { + dataChangedListener = () -> { + updateEditMode(); + updateValues(); + }; + codeArea.addDataChangedListener(dataChangedListener); + caretMovedListener = (CodeAreaCaretPosition caretPosition) -> { + updateValues(); + }; + codeArea.addCaretMovedListener(caretMovedListener); + updateEditMode(); + updateValues(); + } + + public void disableUpdate() { + codeArea.removeDataChangedListener(dataChangedListener); + codeArea.removeCaretMovedListener(caretMovedListener); + } + + public void updateEditMode() { + boolean editable = isEditable(); + binaryCheckBox0.setEnabled(editable); + binaryCheckBox1.setEnabled(editable); + binaryCheckBox2.setEnabled(editable); + binaryCheckBox3.setEnabled(editable); + binaryCheckBox4.setEnabled(editable); + binaryCheckBox5.setEnabled(editable); + binaryCheckBox6.setEnabled(editable); + binaryCheckBox7.setEnabled(editable); + byteTextField.setEditable(editable); + wordTextField.setEditable(editable); + intTextField.setEditable(editable); + longTextField.setEditable(editable); + floatTextField.setEditable(editable); + doubleTextField.setEditable(editable); + characterTextField.setEditable(editable); + stringTextField.setEditable(editable); + } + + public void updateValues() { + CodeAreaCaretPosition caretPosition = codeArea.getCaretPosition(); + dataPosition = caretPosition.getDataPosition(); + long dataSize = codeArea.getDataSize(); + + if (dataPosition < dataSize) { + int availableData = dataSize - dataPosition >= CACHE_SIZE ? CACHE_SIZE : (int) (dataSize - dataPosition); + BinaryData contentData = Objects.requireNonNull(codeArea.getContentData()); + contentData.copyToArray(dataPosition, valuesCache, 0, availableData); + if (availableData < CACHE_SIZE) { + Arrays.fill(valuesCache, availableData, CACHE_SIZE, (byte) 0); + } + } + + valuesUpdater.schedule(); + } + + private void modifyValues(int bytesCount) { + // Unsupported in this version + } + + private boolean isSigned() { + return signedRadioButton.isSelected(); + } + + private boolean isEditable() { + return ((EditModeCapable) codeArea).isEditable(); + } + + private ByteOrder getByteOrder() { + return littleEndianRadioButton.isSelected() ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; + } + + private void showException(Exception ex) { + JOptionPane.showMessageDialog(this, ex.getMessage(), "Invalid Input", JOptionPane.ERROR_MESSAGE); + } + + public enum ValuesPanelField { + BINARY0, + BINARY1, + BINARY2, + BINARY3, + BINARY4, + BINARY5, + BINARY6, + BINARY7, + BYTE, + WORD, + INTEGER, + LONG, + FLOAT, + DOUBLE, + CHARACTER, + STRING + } + + @ParametersAreNonnullByDefault + private class ValuesUpdater { + + private boolean updateInProgress = false; + private boolean updateTerminated = false; + private boolean scheduleUpdate = false; + private boolean clearFields = true; + + private boolean signed; + private ByteOrder byteOrder; + private byte[] values; + + private synchronized void schedule() { + if (updateInProgress) { + updateTerminated = true; + } + if (!scheduleUpdate) { + scheduleUpdate = true; + scheduleNextStep(ValuesPanelField.values()[0]); + } + } + + private void scheduleNextStep(final ValuesPanelField valuesPanelField) { + SwingUtilities.invokeLater(() -> { + updateValue(valuesPanelField); + }); + } + + public boolean isUpdateInProgress() { + return updateInProgress; + } + + private void updateValue(final ValuesPanelField valuesPanelField) { + if (valuesPanelField.ordinal() == 0) { + long dataSize = codeArea.getDataSize(); + clearFields = dataPosition >= dataSize; + byteOrder = littleEndianRadioButton.isSelected() ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; + byteOrder = getByteOrder(); + signed = isSigned(); + values = valuesCache; + if (clearFields) { + values[0] = 0; + } + updateStarted(); + } + + if (updateTerminated) { + stopUpdate(); + return; + } + + if (clearFields) { + clearField(valuesPanelField); + } else { + updateField(valuesPanelField); + } + + final ValuesPanelField[] panelFields = ValuesPanelField.values(); + ValuesPanelField lastValue = panelFields[panelFields.length - 1]; + if (valuesPanelField == lastValue) { + stopUpdate(); + } else { + SwingUtilities.invokeLater(() -> { + ValuesPanelField nextValue = panelFields[valuesPanelField.ordinal() + 1]; + updateValue(nextValue); + }); + } + } + + private void updateField(ValuesPanelField valuesPanelField) { + switch (valuesPanelField) { + case BINARY0: { + binaryCheckBox0.setSelected((values[0] & 0x80) > 0); + break; + } + case BINARY1: { + binaryCheckBox1.setSelected((values[0] & 0x40) > 0); + break; + } + case BINARY2: { + binaryCheckBox2.setSelected((values[0] & 0x20) > 0); + break; + } + case BINARY3: { + binaryCheckBox3.setSelected((values[0] & 0x10) > 0); + break; + } + case BINARY4: { + binaryCheckBox4.setSelected((values[0] & 0x8) > 0); + break; + } + case BINARY5: { + binaryCheckBox5.setSelected((values[0] & 0x4) > 0); + break; + } + case BINARY6: { + binaryCheckBox6.setSelected((values[0] & 0x2) > 0); + break; + } + case BINARY7: { + binaryCheckBox7.setSelected((values[0] & 0x1) > 0); + break; + } + case BYTE: { + byteTextField.setText(String.valueOf(signed ? values[0] : values[0] & 0xff)); + break; + } + case WORD: { + int wordValue = signed + ? (byteOrder == ByteOrder.LITTLE_ENDIAN + ? (values[0] & 0xff) | (values[1] << 8) + : (values[1] & 0xff) | (values[0] << 8)) + : (byteOrder == ByteOrder.LITTLE_ENDIAN + ? (values[0] & 0xff) | ((values[1] & 0xff) << 8) + : (values[1] & 0xff) | ((values[0] & 0xff) << 8)); + wordTextField.setText(String.valueOf(wordValue)); + break; + } + case INTEGER: { + long intValue = signed + ? (byteOrder == ByteOrder.LITTLE_ENDIAN + ? (values[0] & 0xffl) | ((values[1] & 0xffl) << 8) | ((values[2] & 0xffl) << 16) | (values[3] << 24) + : (values[3] & 0xffl) | ((values[2] & 0xffl) << 8) | ((values[1] & 0xffl) << 16) | (values[0] << 24)) + : (byteOrder == ByteOrder.LITTLE_ENDIAN + ? (values[0] & 0xffl) | ((values[1] & 0xffl) << 8) | ((values[2] & 0xffl) << 16) | ((values[3] & 0xffl) << 24) + : (values[3] & 0xffl) | ((values[2] & 0xffl) << 8) | ((values[1] & 0xffl) << 16) | ((values[0] & 0xffl) << 24)); + intTextField.setText(String.valueOf(intValue)); + break; + } + case LONG: { + if (signed) { + byteBuffer.rewind(); + if (byteBuffer.order() != byteOrder) { + byteBuffer.order(byteOrder); + } + + longTextField.setText(String.valueOf(byteBuffer.getLong())); + } else { + long longValue = byteOrder == ByteOrder.LITTLE_ENDIAN + ? (values[0] & 0xffl) | ((values[1] & 0xffl) << 8) | ((values[2] & 0xffl) << 16) | ((values[3] & 0xffl) << 24) + | ((values[4] & 0xffl) << 32) | ((values[5] & 0xffl) << 40) | ((values[6] & 0xffl) << 48) + : (values[7] & 0xffl) | ((values[6] & 0xffl) << 8) | ((values[5] & 0xffl) << 16) | ((values[4] & 0xffl) << 24) + | ((values[3] & 0xffl) << 32) | ((values[2] & 0xffl) << 40) | ((values[1] & 0xffl) << 48); + BigInteger bigInt1 = BigInteger.valueOf(values[byteOrder == ByteOrder.LITTLE_ENDIAN ? 7 : 0] & 0xffl); + BigInteger bigInt2 = bigInt1.shiftLeft(56); + BigInteger bigInt3 = bigInt2.add(BigInteger.valueOf(longValue)); + longTextField.setText(bigInt3.toString()); + } + break; + } + case FLOAT: { + byteBuffer.rewind(); + if (byteBuffer.order() != byteOrder) { + byteBuffer.order(byteOrder); + } + + floatTextField.setText(String.valueOf(byteBuffer.getFloat())); + break; + } + case DOUBLE: { + byteBuffer.rewind(); + if (byteBuffer.order() != byteOrder) { + byteBuffer.order(byteOrder); + } + + doubleTextField.setText(String.valueOf(byteBuffer.getDouble())); + break; + } + case CHARACTER: { + String strValue = new String(values, codeArea.getCharset()); + if (strValue.length() > 0) { + characterTextField.setText(strValue.substring(0, 1)); + } else { + characterTextField.setText(""); + } + break; + } + case STRING: { + String strValue = new String(values, codeArea.getCharset()); + for (int i = 0; i < strValue.length(); i++) { + char charAt = strValue.charAt(i); + if (charAt == '\r' || charAt == '\n' || charAt == 0) { + strValue = strValue.substring(0, i); + break; + } + } + stringTextField.setText(strValue); + stringTextField.setCaretPosition(0); + break; + } + } + } + + private void clearField(ValuesPanelField valuesPanelField) { + switch (valuesPanelField) { + case BINARY0: { + binaryCheckBox0.setSelected(false); + break; + } + case BINARY1: { + binaryCheckBox1.setSelected(false); + break; + } + case BINARY2: { + binaryCheckBox2.setSelected(false); + break; + } + case BINARY3: { + binaryCheckBox3.setSelected(false); + break; + } + case BINARY4: { + binaryCheckBox4.setSelected(false); + break; + } + case BINARY5: { + binaryCheckBox5.setSelected(false); + break; + } + case BINARY6: { + binaryCheckBox6.setSelected(false); + break; + } + case BINARY7: { + binaryCheckBox7.setSelected(false); + break; + } + case BYTE: { + byteTextField.setText(""); + break; + } + case WORD: { + wordTextField.setText(""); + break; + } + case INTEGER: { + intTextField.setText(""); + break; + } + case LONG: { + longTextField.setText(""); + break; + } + case FLOAT: { + floatTextField.setText(""); + break; + } + case DOUBLE: { + doubleTextField.setText(""); + break; + } + case CHARACTER: { + characterTextField.setText(""); + break; + } + case STRING: { + stringTextField.setText(""); + break; + } + } + } + + private synchronized void updateStarted() { + updateInProgress = true; + scheduleUpdate = false; + } + + private synchronized void stopUpdate() { + updateInProgress = false; + updateTerminated = false; + } + } +} diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourceviewer/viewer/FileViewer.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourceviewer/viewer/FileViewer.java index 8bafe48a..f4fb5afc 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourceviewer/viewer/FileViewer.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/resourceviewer/viewer/FileViewer.java @@ -14,7 +14,7 @@ import the.bytecode.club.bytecodeviewer.Configuration; import the.bytecode.club.bytecodeviewer.decompilers.Decompiler; import the.bytecode.club.bytecodeviewer.gui.components.ImageJLabel; import the.bytecode.club.bytecodeviewer.gui.components.SearchableRSyntaxTextArea; -import the.bytecode.club.bytecodeviewer.gui.hexviewer.JHexEditor; +import the.bytecode.club.bytecodeviewer.gui.hexviewer.HexViewer; import the.bytecode.club.bytecodeviewer.resources.Resource; import the.bytecode.club.bytecodeviewer.resources.ResourceContainer; import the.bytecode.club.bytecodeviewer.resources.ResourceType; @@ -97,7 +97,7 @@ public class FileViewer extends ResourceViewer image = MiscUtils.loadImage(image, contents); if (image == null) { - JHexEditor hex = new JHexEditor(contents); + HexViewer hex = new HexViewer(contents); mainPanel.add(hex); return; } @@ -136,7 +136,7 @@ public class FileViewer extends ResourceViewer //hex viewer else if (BytecodeViewer.viewer.forcePureAsciiAsText.isSelected() || hexViewerOnly) { - JHexEditor hex = new JHexEditor(contents); + HexViewer hex = new HexViewer(contents); mainPanel.add(hex); return; } diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/util/BytecodeViewPanelUpdater.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/util/BytecodeViewPanelUpdater.java index f19694b1..59be7b9f 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/gui/util/BytecodeViewPanelUpdater.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/util/BytecodeViewPanelUpdater.java @@ -21,7 +21,7 @@ import the.bytecode.club.bytecodeviewer.compilers.Compiler; import the.bytecode.club.bytecodeviewer.decompilers.Decompiler; import the.bytecode.club.bytecodeviewer.gui.components.MethodsRenderer; import the.bytecode.club.bytecodeviewer.gui.components.SearchableRSyntaxTextArea; -import the.bytecode.club.bytecodeviewer.gui.hexviewer.JHexEditor; +import the.bytecode.club.bytecodeviewer.gui.hexviewer.HexViewer; import the.bytecode.club.bytecodeviewer.gui.resourceviewer.BytecodeViewPanel; import the.bytecode.club.bytecodeviewer.gui.resourceviewer.viewer.ClassViewer; import the.bytecode.club.bytecodeviewer.util.MethodParser; @@ -96,7 +96,7 @@ public class BytecodeViewPanelUpdater implements Runnable SwingUtilities.invokeLater(() -> { - final JHexEditor hex = new JHexEditor(cw.toByteArray()); + final HexViewer hex = new HexViewer(cw.toByteArray()); bytecodeViewPanel.add(hex); }); } diff --git a/src/main/resources/the/bytecode/club/bytecodeviewer/gui/hexviewer/resources/bined-linewrap.png b/src/main/resources/the/bytecode/club/bytecodeviewer/gui/hexviewer/resources/bined-linewrap.png new file mode 100644 index 0000000000000000000000000000000000000000..2d7ab71064679e3febe8790041a5d197eee52d16 GIT binary patch literal 452 zcmV;#0XzPQP)w2Sgw-gbfSup9ZJ8ziD-RfYTF;;mS! z$S+q;2(^XaIAIi?3&=h^rLu`!?8}2s%$`jj3G49j)ak%a1BX`nM#<+3Cdp?Lxt^LS u2+N$XoYYDnwUbUV$)&LL|4QMX|H22f-qI?pwwZMR0000