From 607b369cb2c72ea087119ac3bf58978c4238c7b0 Mon Sep 17 00:00:00 2001 From: "honfika@gmail.com" Date: Sat, 6 Aug 2016 21:08:12 +0200 Subject: [PATCH] search in dump view --- .../flash/gui/dumpview/DumpViewPanel.java | 553 +++++++++++------ .../decompiler/flash/gui/hexview/HexView.java | 567 +++++++++--------- 2 files changed, 654 insertions(+), 466 deletions(-) diff --git a/src/com/jpexs/decompiler/flash/gui/dumpview/DumpViewPanel.java b/src/com/jpexs/decompiler/flash/gui/dumpview/DumpViewPanel.java index 09fdb08b4..5afa7f416 100644 --- a/src/com/jpexs/decompiler/flash/gui/dumpview/DumpViewPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/dumpview/DumpViewPanel.java @@ -1,195 +1,358 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui.dumpview; - -import com.jpexs.decompiler.flash.dumpview.DumpInfo; -import com.jpexs.decompiler.flash.dumpview.DumpInfoSwfNode; -import com.jpexs.decompiler.flash.gui.hexview.HexView; -import com.jpexs.decompiler.flash.gui.hexview.HexViewListener; -import com.jpexs.helpers.Helper; -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.util.ArrayList; -import java.util.List; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.tree.TreeModel; -import javax.swing.tree.TreePath; - -/** - * - * @author JPEXS - */ -public class DumpViewPanel extends JPanel { - - private final JLabel selectedByteInfo; - - private final JLabel dumpViewLabel; - - private final HexView dumpViewHexTable; - - private final DumpTree dumpTree; - - private DumpInfo selectedDumpInfo; - - private boolean skipNextScroll; - - private boolean skipValueChange; - - public DumpViewPanel(final DumpTree dumpTree) { - super(new BorderLayout()); - - this.dumpTree = dumpTree; - - selectedByteInfo = new JLabel(); - selectedByteInfo.setMinimumSize(new Dimension(100, 20)); - selectedByteInfo.setText("-"); - add(selectedByteInfo, BorderLayout.NORTH); - - dumpViewLabel = new JLabel(); - dumpViewLabel.setMinimumSize(new Dimension(100, 20)); - dumpViewLabel.setText("-"); - add(dumpViewLabel, BorderLayout.SOUTH); - - dumpViewHexTable = new HexView(); - dumpViewHexTable.addListener(new HexViewListener() { - - private int lastAddressUnderCursor = -1; - - @Override - public void byteValueChanged(int address, byte b) { - if (skipValueChange) { - return; - } - - if (address != -1) { - TreeModel model = dumpTree.getModel(); - DumpInfo di = DumpInfoSwfNode.getSwfNode(selectedDumpInfo); - while (model.getChildCount(di) > 0) { - boolean found = false; - for (DumpInfo child : di.getChildInfos()) { - if (child.startByte > address) { - break; - } - if (child.getEndByte() >= address) { - di = child; - found = true; - } - } - if (!found) { - break; - } - } - List path = new ArrayList<>(); - while (di != null) { - path.add(0, di); - di = di.parent; - } - path.add(0, model.getRoot()); - TreePath tp = new TreePath(path.toArray()); - skipNextScroll = true; - dumpTree.setSelectionPath(tp); - dumpTree.scrollPathToVisible(tp); - } - - byte[] data = dumpViewHexTable.getData(); - byteMouseMoved(lastAddressUnderCursor, lastAddressUnderCursor == -1 ? 0 : data[lastAddressUnderCursor]); - } - - @Override - public void byteMouseMoved(int address, byte b) { - lastAddressUnderCursor = address; - if (address == -1) { - address = dumpViewHexTable.getFocusedByteIdx(); - if (address != -1) { - byte[] data = dumpViewHexTable.getData(); - b = data[address]; - } - } - - if (address != -1) { - int b2 = b & 0xff; - selectedByteInfo.setText("Addr: " + String.format("%08X", address) - + " Hex: " + String.format("%02X", b) - + " Dec: " + b2 - + " Bin: " + Helper.padZeros(Integer.toBinaryString(b2), 8) - + " Ascii: " + (char) b2 - ); - } else { - selectedByteInfo.setText("-"); - } - } - }); - - add(new JScrollPane(dumpViewHexTable), BorderLayout.CENTER); - } - - public void clear() { - selectedDumpInfo = null; - } - - public void setSelectedNode(DumpInfo dumpInfo) { - if (this.selectedDumpInfo == dumpInfo) { - skipNextScroll = false; - return; - } - - this.selectedDumpInfo = dumpInfo; - byte[] data = DumpInfoSwfNode.getSwfNode(dumpInfo).getSwf().originalUncompressedData; - List dumpInfos = new ArrayList<>(); - DumpInfo di = dumpInfo; - while (di.parent != null) { - dumpInfos.add(di); - di = di.parent; - } - long[] highlightStarts = new long[dumpInfos.size()]; - long[] highlightEnds = new long[dumpInfos.size()]; - for (int i = 0; i < dumpInfos.size(); i++) { - DumpInfo di2 = dumpInfos.get(highlightStarts.length - i - 1); - highlightStarts[i] = di2.startByte; - highlightEnds[i] = di2.getEndByte(); - } - dumpViewHexTable.setData(data, highlightStarts, highlightEnds); - dumpViewHexTable.revalidate(); - - if (dumpInfo.lengthBytes != 0 || dumpInfo.lengthBits != 0) { - int selectionStart = (int) dumpInfo.startByte; - int selectionEnd = (int) dumpInfo.getEndByte(); - - if (!skipNextScroll) { - skipValueChange = true; - dumpViewHexTable.scrollToByte(highlightStarts, highlightEnds); - skipValueChange = false; - } - - setLabelText("startByte: " + dumpInfo.startByte - + " startBit: " + dumpInfo.startBit - + " lengthBytes: " + dumpInfo.lengthBytes - + " lengthBits: " + dumpInfo.lengthBits - + " selectionStart: " + selectionStart - + " selectionEnd: " + selectionEnd); - } - - skipNextScroll = false; - repaint(); - } - - public void setLabelText(String text) { - dumpViewLabel.setText(text); - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.dumpview; + +import com.jpexs.decompiler.flash.dumpview.DumpInfo; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSwfNode; +import com.jpexs.decompiler.flash.gui.MyTextField; +import com.jpexs.decompiler.flash.gui.View; +import com.jpexs.decompiler.flash.gui.hexview.HexView; +import com.jpexs.decompiler.flash.gui.hexview.HexViewListener; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.utf8.Utf8Helper; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; + +/** + * + * @author JPEXS + */ +public class DumpViewPanel extends JPanel { + + private final JLabel selectedByteInfo; + + private final JLabel dumpViewLabel; + + private final HexView dumpViewHexTable; + + private JTextField filterField = new MyTextField(""); + + private JPanel searchPanel; + + private final DumpTree dumpTree; + + private DumpInfo selectedDumpInfo; + + private boolean skipNextScroll; + + private boolean skipValueChange; + + public DumpViewPanel(final DumpTree dumpTree) { + super(new BorderLayout()); + + this.dumpTree = dumpTree; + + selectedByteInfo = new JLabel(); + selectedByteInfo.setMinimumSize(new Dimension(100, 20)); + selectedByteInfo.setText("-"); + add(selectedByteInfo, BorderLayout.NORTH); + + dumpViewLabel = new JLabel(); + dumpViewLabel.setMinimumSize(new Dimension(100, 20)); + dumpViewLabel.setText("-"); + add(dumpViewLabel, BorderLayout.SOUTH); + + dumpViewHexTable = new HexView(); + dumpViewHexTable.addListener(new HexViewListener() { + private int lastAddressUnderCursor = -1; + + @Override + public void byteValueChanged(int address, byte b) { + if (skipValueChange) { + return; + } + + if (address != -1) { + TreeModel model = dumpTree.getModel(); + DumpInfo di = DumpInfoSwfNode.getSwfNode(selectedDumpInfo); + while (model.getChildCount(di) > 0) { + boolean found = false; + for (DumpInfo child : di.getChildInfos()) { + if (child.startByte > address) { + break; + } + if (child.getEndByte() >= address) { + di = child; + found = true; + } + } + if (!found) { + break; + } + } + List path = new ArrayList<>(); + while (di != null) { + path.add(0, di); + di = di.parent; + } + path.add(0, model.getRoot()); + TreePath tp = new TreePath(path.toArray()); + skipNextScroll = true; + dumpTree.setSelectionPath(tp); + dumpTree.scrollPathToVisible(tp); + } + + byte[] data = dumpViewHexTable.getData(); + byteMouseMoved(lastAddressUnderCursor, lastAddressUnderCursor == -1 ? 0 : data[lastAddressUnderCursor]); + } + + @Override + public void byteMouseMoved(int address, byte b) { + lastAddressUnderCursor = address; + if (address == -1) { + address = dumpViewHexTable.getFocusedByteIdx(); + if (address != -1) { + byte[] data = dumpViewHexTable.getData(); + b = data[address]; + } + } + + if (address != -1) { + int b2 = b & 0xff; + selectedByteInfo.setText("Addr: " + String.format("%08X", address) + + " Hex: " + String.format("%02X", b) + + " Dec: " + b2 + + " Bin: " + Helper.padZeros(Integer.toBinaryString(b2), 8) + + " Ascii: " + (char) b2 + ); + } else { + selectedByteInfo.setText("-"); + } + } + }); + + searchPanel = new JPanel(); + searchPanel.setLayout(new BorderLayout()); + searchPanel.add(filterField, BorderLayout.CENTER); + searchPanel.add(new JLabel(View.getIcon("search16")), BorderLayout.WEST); + JLabel closeSearchButton = new JLabel(View.getIcon("cancel16")); + closeSearchButton.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + closeDumpViewSearch(); + } + }); + searchPanel.add(closeSearchButton, BorderLayout.EAST); + searchPanel.setVisible(false); + + dumpViewHexTable.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if ((e.getKeyCode() == 'F') && (e.isControlDown())) { + searchPanel.setVisible(true); + filterField.requestFocusInWindow(); + } + } + }); + + filterField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void changedUpdate(DocumentEvent e) { + warn(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + warn(); + } + + @Override + public void insertUpdate(DocumentEvent e) { + warn(); + } + + public void warn() { + doSearch(); + } + }); + + add(new JScrollPane(dumpViewHexTable), BorderLayout.CENTER); + add(searchPanel, BorderLayout.SOUTH); + } + + public void closeDumpViewSearch() { + filterField.setText(""); + doSearch(); + searchPanel.setVisible(false); + } + + private void doSearch() { + filterField.setBackground(Color.white); + + String text = filterField.getText(); + if (text.length() == 0) { + dumpViewHexTable.clearSelectedBytes(); + return; + } + + byte[] data = dumpViewHexTable.getData(); + + byte[] textBytes = Utf8Helper.getBytes(text); + byte[] hex = getAsHex(text); + byte[] foundArray = textBytes; + + int pos = textBytes == null ? -1 : findHex(data, textBytes, 0, data.length); + int hexPos = hex == null ? -1 : findHex(data, hex, 0, data.length); + + if (pos == -1 || (hexPos != -1 && hexPos < pos)) { + pos = hexPos; + foundArray = hex; + } + + if (pos != -1) { + dumpViewHexTable.selectBytes(pos, foundArray.length); + } else { + dumpViewHexTable.clearSelectedBytes(); + filterField.setBackground(Color.red); + } + } + + private int findHex(byte[] data, byte[] searchData, int from, int to) { + for (int i = from; i < to; i++) { + if (isMatch(data, searchData, i)) { + return i; + } + } + + return -1; + } + + private boolean isMatch(byte[] data, byte[] searchData, int pos) { + if (pos + searchData.length > data.length) { + return false; + } + + for (int i = 0; i < searchData.length; i++) { + if (data[pos + i] != searchData[i]) { + return false; + } + } + + return true; + } + + private byte[] getAsHex(String text) { + int charCount = 0; + for (int i = 0; i < text.length(); i++) { + char ch = Character.toUpperCase(text.charAt(i)); + boolean whiteSpace = Character.isWhitespace(ch); + if (!((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || whiteSpace)) { + return null; + } + + if (!whiteSpace) { + charCount++; + } + } + + if (charCount % 2 == 1) { + // hex character count should be even + return null; + } + + byte[] result = new byte[charCount / 2]; + int cnt = 0; + int v0 = 0; + for (int i = 0; i < text.length(); i++) { + char ch = Character.toUpperCase(text.charAt(i)); + if (Character.isWhitespace(ch)) { + continue; + } + + int v = Integer.parseInt(Character.toString(ch), 16); + + if (cnt % 2 == 1) { + result[cnt / 2] = (byte) (v0 * 16 + v); + } else { + v0 = v; + } + + cnt++; + } + + return result; + } + + public void clear() { + selectedDumpInfo = null; + } + + public void setSelectedNode(DumpInfo dumpInfo) { + if (this.selectedDumpInfo == dumpInfo) { + skipNextScroll = false; + return; + } + + this.selectedDumpInfo = dumpInfo; + byte[] data = DumpInfoSwfNode.getSwfNode(dumpInfo).getSwf().originalUncompressedData; + List dumpInfos = new ArrayList<>(); + DumpInfo di = dumpInfo; + while (di.parent != null) { + dumpInfos.add(di); + di = di.parent; + } + long[] highlightStarts = new long[dumpInfos.size()]; + long[] highlightEnds = new long[dumpInfos.size()]; + for (int i = 0; i < dumpInfos.size(); i++) { + DumpInfo di2 = dumpInfos.get(highlightStarts.length - i - 1); + highlightStarts[i] = di2.startByte; + highlightEnds[i] = di2.getEndByte(); + } + dumpViewHexTable.setData(data, highlightStarts, highlightEnds); + dumpViewHexTable.revalidate(); + + if (dumpInfo.lengthBytes != 0 || dumpInfo.lengthBits != 0) { + int selectionStart = (int) dumpInfo.startByte; + int selectionEnd = (int) dumpInfo.getEndByte(); + + if (!skipNextScroll) { + skipValueChange = true; + dumpViewHexTable.scrollToByte(highlightStarts, highlightEnds); + skipValueChange = false; + } + + setLabelText("startByte: " + dumpInfo.startByte + + " startBit: " + dumpInfo.startBit + + " lengthBytes: " + dumpInfo.lengthBytes + + " lengthBits: " + dumpInfo.lengthBits + + " selectionStart: " + selectionStart + + " selectionEnd: " + selectionEnd); + } + + skipNextScroll = false; + repaint(); + } + + public void setLabelText(String text) { + dumpViewLabel.setText(text); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/hexview/HexView.java b/src/com/jpexs/decompiler/flash/gui/hexview/HexView.java index 704ef3d30..5ec5cdf87 100644 --- a/src/com/jpexs/decompiler/flash/gui/hexview/HexView.java +++ b/src/com/jpexs/decompiler/flash/gui/hexview/HexView.java @@ -1,271 +1,296 @@ -/* - * Copyright (C) 2010-2016 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui.hexview; - -import java.awt.Color; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionAdapter; -import javax.swing.JLabel; -import javax.swing.JTable; -import javax.swing.ListSelectionModel; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import javax.swing.table.DefaultTableCellRenderer; -import javax.swing.table.JTableHeader; -import javax.swing.table.TableColumn; -import javax.swing.table.TableModel; - -/** - * - * @author JPEXS - */ -public class HexView extends JTable { - - private static final int bytesInRow = 16; - - private long[] highlightStarts; - - private long[] highlightEnds; - - private final String[] highlightColorsStr = new String[]{/*"EEEEEE", */"29AEC2", "9AC88C", "DF5F80", "EEA32E", "FFD200", "5E9B4C", "D3E976", "A3AEC2"}; - - private final Color[] highlightColors; - - private final Color bgColor = Color.decode("#F7F7F7"); - - private final Color bgColorAlternate = Color.decode("#EDEDED"); - - private int mouseOverIdx = -1; - - private HexViewListener listener; - - private class HighlightCellRenderer extends DefaultTableCellRenderer { - - public int byteIndex; - - @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { - - JLabel l = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); - int level = -1; - int idx = getIdxByColAndRow(row, col); - if (highlightStarts != null) { - byteIndex = idx; - for (int i = 0; i < highlightStarts.length; i++) { - if (highlightStarts[i] <= idx && highlightEnds[i] >= idx) { - level++; - } else { - break; - } - } - } - - Color foreground; - Color background; - if (level > -1) { - foreground = Color.white; - background = highlightColors[level % highlightColors.length]; - } else { - foreground = Color.black; - background = row % 2 == 0 ? bgColor : bgColorAlternate; - } - - if (idx != -1 && idx == mouseOverIdx) { - foreground = new Color(255 - foreground.getRed(), 255 - foreground.getGreen(), 255 - foreground.getBlue()); - background = new Color(255 - background.getRed(), 255 - background.getGreen(), 255 - background.getBlue()); - } - l.setForeground(foreground); - l.setBackground(background); - - return l; - } - } - - private class HexViewSelectionListener implements ListSelectionListener { - - private final HexView table; - - public HexViewSelectionListener(HexView table) { - this.table = table; - } - - @Override - public void valueChanged(ListSelectionEvent e) { - - int col = table.getSelectedColumn(); - int row = table.getSelectedRow(); - - int idx = getIdxByColAndRow(row, col); - if (listener != null) { - listener.byteValueChanged(idx, idx == -1 ? 0 : getModel().getData()[idx]); - } - } - } - - private class HexViewMouseAdapter extends MouseAdapter { - - @Override - public void mouseExited(MouseEvent e) { - HexView table = (HexView) e.getSource(); - Point point = e.getPoint(); - int col = table.columnAtPoint(point); - int row = table.rowAtPoint(point); - mouseOverIdx = -1; - getModel().fireTableCellUpdated(row, col); - if (listener != null) { - listener.byteMouseMoved(-1, (byte) 0); - } - } - } - - private class HexViewMouseMotionAdapter extends MouseMotionAdapter { - - @Override - public void mouseMoved(MouseEvent e) { - HexView table = (HexView) e.getSource(); - Point point = e.getPoint(); - int col = table.columnAtPoint(point); - int row = table.rowAtPoint(point); - int idx = getIdxByColAndRow(row, col); - mouseOverIdx = idx; - getModel().fireTableCellUpdated(row, col); - - if (listener != null) { - listener.byteMouseMoved(idx, idx == -1 ? 0 : getModel().getData()[idx]); - } - } - } - - public HexView() { - super(new HexViewTableModel(bytesInRow)); - highlightColors = new Color[highlightColorsStr.length]; - for (int i = 0; i < highlightColors.length; i++) { - highlightColors[i] = Color.decode("#" + highlightColorsStr[i]); - } - - setBackground(Color.white); - setFont(new Font("Monospaced", Font.PLAIN, 12)); - setTableHeader(new JTableHeader()); - setMaximumSize(new Dimension(200, 200)); - - setShowHorizontalLines(false); - setShowVerticalLines(false); - setRowSelectionAllowed(false); - setColumnSelectionAllowed(false); - - HighlightCellRenderer cellRenderer = new HighlightCellRenderer(); - TableColumn column = columnModel.getColumn(0); - column.setMaxWidth(80); - for (int i = 0; i < bytesInRow; i++) { - column = columnModel.getColumn(i + 1); - column.setMaxWidth(25); - column.setCellRenderer(cellRenderer); - } - - column = columnModel.getColumn(bytesInRow + 1); - column.setMaxWidth(10); - - for (int i = 0; i < bytesInRow; i++) { - column = columnModel.getColumn(i + bytesInRow + 1 + 1); - column.setMaxWidth(10); - column.setCellRenderer(cellRenderer); - } - - addMouseListener(new HexViewMouseAdapter()); - addMouseMotionListener(new HexViewMouseMotionAdapter()); - ListSelectionModel rowSelModel = getSelectionModel(); - ListSelectionModel colSelModel = getColumnModel().getSelectionModel(); - ListSelectionListener selectionListener = new HexViewSelectionListener(this); - rowSelModel.addListSelectionListener(selectionListener); - colSelModel.addListSelectionListener(selectionListener); - } - - @Override - public HexViewTableModel getModel() { - TableModel model = super.getModel(); - return (HexViewTableModel) model; - } - - public void setData(byte[] data, long[] highlightStarts, long[] highlightEnds) { - - if ((highlightStarts == null) ^ (highlightEnds == null)) { - throw new Error("highlightStarts and highlightEnds should be both null or not null."); - } - - if (highlightStarts != null && highlightStarts.length != highlightEnds.length) { - throw new Error("highlightStarts and highlightEnds should have the same number of elements."); - } - - getModel().setData(data); - this.highlightStarts = highlightStarts; - this.highlightEnds = highlightEnds; - } - - public byte[] getData() { - return getModel().getData(); - } - - public void scrollToByte(long byteNum) { - - int row = (int) (byteNum / bytesInRow); - - //final int pageSize = (int) (getParent().getSize().getHeight() / getRowHeight()); - getSelectionModel().setSelectionInterval(row, row); - scrollRectToVisible(new Rectangle(getCellRect(row, 0, true))); - } - - private int getIdxByColAndRow(int row, int col) { - int idx = -1; - if (row < 0 || col < 0) { - return -1; - } - if (col > 0 && col != bytesInRow + 1) { - idx = row * bytesInRow + ((col > bytesInRow + 1) ? (col - bytesInRow - 2) : (col - 1)); - } - byte[] data = getModel().getData(); - if (idx >= data.length) { - idx = -1; - } - return idx; - } - - public int getFocusedByteIdx() { - int col = getSelectedColumn(); - int row = getSelectedRow(); - - int idx = getIdxByColAndRow(row, col); - return idx; - } - - public void scrollToByte(long[] byteNumStarts, long[] byteNumEnds) { - for (int i = 0; i < byteNumStarts.length; i++) { - scrollToByte(byteNumStarts[i]); - scrollToByte(byteNumEnds[i]); - scrollToByte(byteNumStarts[i]); - } - } - - public void addListener(HexViewListener listener) { - this.listener = listener; - } -} +/* + * Copyright (C) 2010-2016 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.hexview; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionAdapter; +import javax.swing.JLabel; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.JTableHeader; +import javax.swing.table.TableColumn; +import javax.swing.table.TableModel; + +/** + * + * @author JPEXS + */ +public class HexView extends JTable { + + private static final int bytesInRow = 16; + + private long[] highlightStarts; + + private long[] highlightEnds; + + private final String[] highlightColorsStr = new String[]{/*"EEEEEE", */"29AEC2", "9AC88C", "DF5F80", "EEA32E", "FFD200", "5E9B4C", "D3E976", "A3AEC2"}; + + private final Color[] highlightColors; + + private final Color bgColor = Color.decode("#F7F7F7"); + + private final Color bgColorAlternate = Color.decode("#EDEDED"); + + private int mouseOverIdx = -1; + + private int selectionStart = -1; + + private int selectionEnd = -1; + + private HexViewListener listener; + + private class HighlightCellRenderer extends DefaultTableCellRenderer { + + public int byteIndex; + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { + + JLabel l = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); + int level = -1; + int idx = getIdxByColAndRow(row, col); + if (highlightStarts != null) { + byteIndex = idx; + for (int i = 0; i < highlightStarts.length; i++) { + if (highlightStarts[i] <= idx && highlightEnds[i] >= idx) { + level++; + } else { + break; + } + } + } + + Color foreground; + Color background; + if (level > -1) { + foreground = Color.white; + background = highlightColors[level % highlightColors.length]; + } else { + foreground = Color.black; + background = row % 2 == 0 ? bgColor : bgColorAlternate; + } + + if (idx != -1 && (idx == mouseOverIdx + || (idx >= selectionStart && idx <= selectionEnd))) { + foreground = new Color(255 - foreground.getRed(), 255 - foreground.getGreen(), 255 - foreground.getBlue()); + background = new Color(255 - background.getRed(), 255 - background.getGreen(), 255 - background.getBlue()); + } + + l.setForeground(foreground); + l.setBackground(background); + + return l; + } + } + + private class HexViewSelectionListener implements ListSelectionListener { + + private final HexView table; + + public HexViewSelectionListener(HexView table) { + this.table = table; + } + + @Override + public void valueChanged(ListSelectionEvent e) { + + int col = table.getSelectedColumn(); + int row = table.getSelectedRow(); + + int idx = getIdxByColAndRow(row, col); + if (listener != null) { + listener.byteValueChanged(idx, idx == -1 ? 0 : getModel().getData()[idx]); + } + } + } + + private class HexViewMouseAdapter extends MouseAdapter { + + @Override + public void mouseExited(MouseEvent e) { + HexView table = (HexView) e.getSource(); + Point point = e.getPoint(); + int col = table.columnAtPoint(point); + int row = table.rowAtPoint(point); + mouseOverIdx = -1; + getModel().fireTableCellUpdated(row, col); + if (listener != null) { + listener.byteMouseMoved(-1, (byte) 0); + } + } + } + + private class HexViewMouseMotionAdapter extends MouseMotionAdapter { + + @Override + public void mouseMoved(MouseEvent e) { + HexView table = (HexView) e.getSource(); + Point point = e.getPoint(); + int col = table.columnAtPoint(point); + int row = table.rowAtPoint(point); + int idx = getIdxByColAndRow(row, col); + mouseOverIdx = idx; + getModel().fireTableCellUpdated(row, col); + + if (listener != null) { + listener.byteMouseMoved(idx, idx == -1 ? 0 : getModel().getData()[idx]); + } + } + } + + public HexView() { + super(new HexViewTableModel(bytesInRow)); + highlightColors = new Color[highlightColorsStr.length]; + for (int i = 0; i < highlightColors.length; i++) { + highlightColors[i] = Color.decode("#" + highlightColorsStr[i]); + } + + setBackground(Color.white); + setFont(new Font("Monospaced", Font.PLAIN, 12)); + setTableHeader(new JTableHeader()); + setMaximumSize(new Dimension(200, 200)); + + setShowHorizontalLines(false); + setShowVerticalLines(false); + setRowSelectionAllowed(false); + setColumnSelectionAllowed(false); + + HighlightCellRenderer cellRenderer = new HighlightCellRenderer(); + TableColumn column = columnModel.getColumn(0); + column.setMaxWidth(80); + for (int i = 0; i < bytesInRow; i++) { + column = columnModel.getColumn(i + 1); + column.setMaxWidth(25); + column.setCellRenderer(cellRenderer); + } + + column = columnModel.getColumn(bytesInRow + 1); + column.setMaxWidth(10); + + for (int i = 0; i < bytesInRow; i++) { + column = columnModel.getColumn(i + bytesInRow + 1 + 1); + column.setMaxWidth(10); + column.setCellRenderer(cellRenderer); + } + + addMouseListener(new HexViewMouseAdapter()); + addMouseMotionListener(new HexViewMouseMotionAdapter()); + ListSelectionModel rowSelModel = getSelectionModel(); + ListSelectionModel colSelModel = getColumnModel().getSelectionModel(); + ListSelectionListener selectionListener = new HexViewSelectionListener(this); + rowSelModel.addListSelectionListener(selectionListener); + colSelModel.addListSelectionListener(selectionListener); + } + + @Override + public HexViewTableModel getModel() { + TableModel model = super.getModel(); + return (HexViewTableModel) model; + } + + public void setData(byte[] data, long[] highlightStarts, long[] highlightEnds) { + + if ((highlightStarts == null) ^ (highlightEnds == null)) { + throw new Error("highlightStarts and highlightEnds should be both null or not null."); + } + + if (highlightStarts != null && highlightStarts.length != highlightEnds.length) { + throw new Error("highlightStarts and highlightEnds should have the same number of elements."); + } + + getModel().setData(data); + this.highlightStarts = highlightStarts; + this.highlightEnds = highlightEnds; + } + + public byte[] getData() { + return getModel().getData(); + } + + public void selectByte(long byteNum) { + scrollToByte(byteNum); + listener.byteValueChanged((int) byteNum, getData()[(int) byteNum]); + } + + public void selectBytes(long byteNum, int length) { + selectionStart = (int) byteNum; + selectionEnd = (int) (byteNum + length - 1); + scrollToByte(new long[]{byteNum}, new long[]{byteNum + length - 1}); + listener.byteValueChanged((int) byteNum, getData()[(int) byteNum]); + getModel().fireTableDataChanged(); + } + + public void clearSelectedBytes() { + selectionStart = -1; + selectionEnd = -1; + getModel().fireTableDataChanged(); + } + + public void scrollToByte(long byteNum) { + + int row = (int) (byteNum / bytesInRow); + + //final int pageSize = (int) (getParent().getSize().getHeight() / getRowHeight()); + getSelectionModel().setSelectionInterval(row, row); + scrollRectToVisible(new Rectangle(getCellRect(row, 0, true))); + } + + private int getIdxByColAndRow(int row, int col) { + int idx = -1; + if (row < 0 || col < 0) { + return -1; + } + if (col > 0 && col != bytesInRow + 1) { + idx = row * bytesInRow + ((col > bytesInRow + 1) ? (col - bytesInRow - 2) : (col - 1)); + } + byte[] data = getModel().getData(); + if (idx >= data.length) { + idx = -1; + } + return idx; + } + + public int getFocusedByteIdx() { + int col = getSelectedColumn(); + int row = getSelectedRow(); + + int idx = getIdxByColAndRow(row, col); + return idx; + } + + public void scrollToByte(long[] byteNumStarts, long[] byteNumEnds) { + for (int i = 0; i < byteNumStarts.length; i++) { + scrollToByte(byteNumStarts[i]); + scrollToByte(byteNumEnds[i]); + scrollToByte(byteNumStarts[i]); + } + } + + public void addListener(HexViewListener listener) { + this.listener = listener; + } +}