org.fife.ui.hex.swing.HexTable Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hexeditor Show documentation
Show all versions of hexeditor Show documentation
HexEditor from fifesoft.com.
The newest version!
/* * Copyright (c) 2008 Robert Futrell * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name "HexEditor" nor the names of its contributors may * be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.fife.ui.hex.swing; import java.awt.AWTEvent; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyEvent; import java.io.IOException; import java.io.InputStream; import javax.swing.*; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.text.AbstractDocument; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.DocumentFilter; import javax.swing.text.JTextComponent; /** * The table displaying the hex content of a file. This is the meat of * the hex viewer. * * @author Robert Futrell * @version 1.0 */ class HexTable extends JTable { private static final long serialVersionUID = 1L; private final HexEditor hexEditor; private HexTableModel model; int leadSelectionIndex; int anchorSelectionIndex; boolean isEditable = true; private static final Color ANTERNATING_CELL_COLOR = new Color(240,240,240); /** * Creates a new table to display hex data. * * @param hexEditor The parent hex editor component. * @param model The table model to use. */ public HexTable(HexEditor hexEditor, HexTableModel model) { super(model); this.hexEditor = hexEditor; this.model = model; enableEvents(AWTEvent.KEY_EVENT_MASK); setAutoResizeMode(JTable.AUTO_RESIZE_OFF); setFont(new Font("Monospaced", Font.PLAIN, 14)); //setRowHeight(28); setCellSelectionEnabled(true); setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); setDefaultEditor(Object.class, new CellEditor()); setDefaultRenderer(Object.class, new CellRenderer()); getTableHeader().setReorderingAllowed(false); setShowGrid(false); FontMetrics fm = getFontMetrics(getFont()); Font headerFont = UIManager.getFont("TableHeader.font"); FontMetrics headerFM = hexEditor.getFontMetrics(headerFont); int w = fm.stringWidth("wwww"); // cell contents, 0-255 w = Math.max(w, headerFM.stringWidth("+999")); for (int i=0; iif the * cell does not represent part of the byte array (such as the * tailing "ascii dump" column's cells). * @see #offsetToCell(int) */ public int cellToOffset(int row, int col) { // Check row and column individually to prevent them being invalid // values but still pointing to a valid offset in the buffer. if (row<0 || row>=getRowCount() || col<0 || col>15) { // Don't include last column (ascii dump) return -1; } int offs = row*16 + col; return (offs>=0 && offs-1 -1) { // Always true unless an error leadSelectionIndex = anchorSelectionIndex; } else { anchorSelectionIndex = leadSelectionIndex = 0; } repaintSelection(); } /** * Ensures the specified cell is visible. * * @param row The row of the cell. * @param col The column of the cell. */ private void ensureCellIsVisible(int row, int col) { Rectangle cellRect = getCellRect(row, col, false); if (cellRect != null) { scrollRectToVisible(cellRect); } } /** * Returns the byte at the specified offset. * * @param offset The offset. * @return The byte. */ public byte getByte(int offset) { return model.getByte(offset); } /** * Returns the number of bytes being edited. * * @return The number of bytes. */ public int getByteCount() { return model.getByteCount(); } /** * Returns the largest selection index. * * @return The largest selection index. * @see #getSmallestSelectionIndex() */ public int getLargestSelectionIndex() { return Math.max(leadSelectionIndex, anchorSelectionIndex); } /** * Returns the smallest selection index. * * @return The smallest selection index. * @see #getLargestSelectionIndex() */ public int getSmallestSelectionIndex() { return Math.min(leadSelectionIndex, anchorSelectionIndex); } public boolean isCellEditable(int row, int col) { if (this.isEditable) return cellToOffset(row, col)>-1; return false; } public boolean isCellSelected(int row, int col) { int offset = cellToOffset(row, col); if (offset==-1) { // "Ascii dump" column return false; } int start = getSmallestSelectionIndex(); int end = getLargestSelectionIndex(); return offset>=start && offset<=end; } /** * Returns the cell representing the specified offset into the hex * document. * * @param offset The offset into the document. * @return The cell, in the form (row, col). If the * specified offset is invalid,(-1, -1)is returned. * @see #cellToOffset(int, int) */ public Point offsetToCell(int offset) { if (offset<0 || offset>=model.getByteCount()) { return new Point(-1, -1); } int row = offset/16; int col = offset%16; return new Point(row, col); } /** * Sets the contents in the hex editor to the contents of the specified * file. * * @param fileName The name of the file to open. * @throws IOException If an IO error occurs. */ public void open(String fileName) throws IOException { model.setBytes(fileName); // Fires tableDataChanged event } /** * Sets the contents in the hex editor to the contents of the specified * input stream. * * @param in An input stream. * @throws IOException If an IO error occurs. */ public void open(InputStream in) throws IOException { model.setBytes(in); } public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { Object value = getValueAt(row, column); boolean isSelected = isCellSelected(row, column); boolean hasFocus = cellToOffset(row, column)==leadSelectionIndex; return renderer.getTableCellRendererComponent(this, value, isSelected, hasFocus, row, column); } protected void processKeyEvent (java.awt.event.KeyEvent e) { // TODO: Convert into Actions and put into InputMap/ActionMap? if (e.getID()==KeyEvent.KEY_PRESSED) { switch (e.getKeyCode()) { case KeyEvent.VK_LEFT: boolean extend = e.isShiftDown(); int offs = Math.max(leadSelectionIndex-1, 0); changeSelectionByOffset(offs, extend); e.consume(); break; case KeyEvent.VK_RIGHT: extend = e.isShiftDown(); offs = Math.min(leadSelectionIndex+1, model.getByteCount()-1); changeSelectionByOffset(offs, extend); e.consume(); break; case KeyEvent.VK_UP: extend = e.isShiftDown(); offs = Math.max(leadSelectionIndex-16, 0); changeSelectionByOffset(offs, extend); e.consume(); break; case KeyEvent.VK_DOWN: extend = e.isShiftDown(); offs = Math.min(leadSelectionIndex+16, model.getByteCount()-1); changeSelectionByOffset(offs, extend); e.consume(); break; case KeyEvent.VK_PAGE_DOWN: extend = e.isShiftDown(); int visibleRowCount = getVisibleRect().height/getRowHeight(); offs = Math.min(leadSelectionIndex+visibleRowCount*16, model.getByteCount()-1); changeSelectionByOffset(offs, extend); e.consume(); break; case KeyEvent.VK_PAGE_UP: extend = e.isShiftDown(); visibleRowCount = getVisibleRect().height/getRowHeight(); offs = Math.max(leadSelectionIndex-visibleRowCount*16, 0); changeSelectionByOffset(offs, extend); e.consume(); break; case KeyEvent.VK_HOME: extend = e.isShiftDown(); offs = (leadSelectionIndex/16)*16; changeSelectionByOffset(offs, extend); e.consume(); break; case KeyEvent.VK_END: extend = e.isShiftDown(); offs = (leadSelectionIndex/16)*16 + 15; offs = Math.min(offs, model.getByteCount()-1); changeSelectionByOffset(offs, extend); e.consume(); break; } } super.processKeyEvent(e); } /** * Tries to redo the last action undone. * * @return Whether there is another action to redo after this one. * @see #undo() */ public boolean redo() { return model.redo(); } /** * Removes a range of bytes. * * @param offs The offset of the range of bytes to remove. * @param len The number of bytes to remove. * @see #replaceBytes(int, int, byte[]) */ void removeBytes(int offs, int len) { model.removeBytes(offs, len); } private void repaintSelection() { // TODO: Repaint only selected lines. repaint(); } /** * Replaces a range of bytes. * * @param offset The offset of the range of bytes to replace. * @param len The number of bytes to replace. * @param bytes The bytes to replace the range with. * @see #removeBytes(int, int) */ public void replaceBytes(int offset, int len, byte[] bytes) { model.replaceBytes(offset, len, bytes); } public void setSelectedRows(int min, int max) { if (min<0 || min>=getRowCount() || max<0 || max>=getRowCount()) { throw new IllegalArgumentException(); } int startOffs = min*16; int endOffs = max*16+15; // TODO: Have a single call to change selection by a range. changeSelectionByOffset(startOffs, false); changeSelectionByOffset(endOffs, true); } /** * Selects the specified range of bytes in the table. * * @param startOffs The "anchor" byte of the selection. * @param endOffs The "lead" byte of the selection. * @see #changeSelection(int, int, boolean, boolean) * @see #changeSelectionByOffset(int, boolean) */ public void setSelectionByOffsets(int startOffs, int endOffs) { startOffs = Math.max(0, startOffs); startOffs= Math.min(startOffs, model.getByteCount()-1); // Clear the old selection (may not be necessary). repaintSelection(); anchorSelectionIndex = startOffs; leadSelectionIndex = endOffs; // Scroll after changing the selection as blit scrolling is // immediate, so that if we cause the repaint after the scroll we // end up painting everything! if (getAutoscrolls()) { int endRow = endOffs/16; int endCol = endOffs%16; // Don't allow the user to select the "ascii dump" or any // empty cells in the last row of the table. endCol = adjustColumn(endRow, endCol); if (endRow<0) { endRow = 0; } ensureCellIsVisible(endRow, endCol); } // Draw the new selection. repaintSelection(); } /** * Tries to undo the last action. * * @return Whether there is another action to undo after this one. * @see #redo() */ public boolean undo() { return model.undo(); } /** * Table cell editor that restricts input to byte values * (0 - 255). * * @author Robert Futrell * @version 1.0 */ private static class CellEditor extends DefaultCellEditor implements FocusListener { private static final long serialVersionUID = 1L; public CellEditor() { super(new JTextField()); AbstractDocument doc = (AbstractDocument) ((JTextComponent)editorComponent).getDocument(); doc.setDocumentFilter(new EditorDocumentFilter()); getComponent().addFocusListener(this); } public void focusGained(FocusEvent e) { JTextField textField = (JTextField)getComponent(); textField.selectAll(); } public void focusLost(FocusEvent e) { } public boolean stopCellEditing() { // Prevent the user from entering empty string as a value. String value = (String)getCellEditorValue(); if (value.length()==0) { UIManager.getLookAndFeel().provideErrorFeedback(null); return false; } return super.stopCellEditing(); } } /** * Custom cell renderer. This is primarily here for performance, * especially on 1.4 JVM's.DefaultTableCellRenderer's * performance was horrible on tables displaying large amounts of rows * and columns in 1.4, so this class helps to alleviate some of that pain. * 1.4 and 1.5 JRE's don't have as much of a performance problem, but * you still can see some lag at times. * * @author Robert Futrell * @version 1.0 */ private class CellRenderer extends DefaultTableCellRenderer { private static final long serialVersionUID = 1L; private Point highlight; public CellRenderer() { highlight = new Point(); } public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean focus, int row, int column) { super.getTableCellRendererComponent(table, value, selected, focus, row, column); highlight.setLocation(-1, -1); if (column==table.getColumnCount()-1 && // "Ascii dump" hexEditor.getHighlightSelectionInAsciiDump()) { int selStart = getSmallestSelectionIndex(); int selEnd = getLargestSelectionIndex(); int b1 = row*16; int b2 = b1 + 15; if (selStart<=b2 && selEnd>=b1) { int start = Math.max(selStart, b1) - b1; int end = Math.min(selEnd, b2) - b1; highlight.setLocation(start, end); } boolean colorBG = hexEditor.getAlternateRowBG() && (row&1)>0; setBackground(colorBG ? ANTERNATING_CELL_COLOR : table.getBackground()); } else { if (!selected) { if ((hexEditor.getAlternateRowBG() && (row&1)>0) ^ (hexEditor.getAlternateColumnBG() && (column&1)>0)){ setBackground(ANTERNATING_CELL_COLOR); } else { setBackground(table.getBackground()); } } } return this; } protected void paintComponent(Graphics g) { g.setColor(getBackground()); g.fillRect(0, 0, getWidth(),getHeight()); if (highlight.x>-1) { int w = getFontMetrics(HexTable.this.getFont()).charWidth('w'); g.setColor(hexEditor.getHighlightSelectionInAsciiDumpColor()); int x = getInsets().left + highlight.x*w; g.fillRect(x, 0, (highlight.y-highlight.x+1)*w, getRowHeight()); } g.setColor(getForeground()); int x = 2; String text = getText(); if (text.length()==1) { x += g.getFontMetrics().charWidth('w'); } g.drawString(text, x,11); } } /** * Filter that ensures the user only enters valid characters in a * byte's cell while editing. * * @author Robert Futrell * @version 1.0 */ private static class EditorDocumentFilter extends DocumentFilter { private boolean ensureByteRepresented(String str) { try { int i = Integer.parseInt(str, 16); if (i<0 || i>0xff) { throw new NumberFormatException(); } } catch (NumberFormatException nfe) { UIManager.getLookAndFeel().provideErrorFeedback(null); return false; } return true; } public void insertString(FilterBypass fb, int offs, String string, AttributeSet attr) throws BadLocationException { Document doc = fb.getDocument(); String temp = doc.getText(0, offs) + string + doc.getText(offs, doc.getLength()-offs); if (ensureByteRepresented(temp)) { fb.insertString(offs, temp, attr); } } public void replace(FilterBypass fb, int offs, int len, String text, AttributeSet attrs) throws BadLocationException { Document doc = fb.getDocument(); String temp = doc.getText(0, offs) + text + doc.getText(offs+len, doc.getLength()-(offs+len)); if (ensureByteRepresented(temp)) { fb.replace(offs, len, text, attrs); } } } public boolean isEditable() { return this.isEditable; } public void setEditable(boolean isEditable) { this.isEditable = isEditable; } }
© 2015 - 2025 Weber Informatics LLC | Privacy Policy