// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity.gui.hexview; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Font; import java.nio.charset.Charset; import javax.swing.JPanel; import org.infinity.NearInfinity; import org.infinity.gui.StatusBar; import org.infinity.resource.Closeable; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.Misc; import tv.porst.jhexview.DataChangedEvent; import tv.porst.jhexview.HexViewEvent; import tv.porst.jhexview.IDataChangedListener; import tv.porst.jhexview.IHexViewListener; import tv.porst.jhexview.IMenuCreator; import tv.porst.jhexview.JHexView; /** * A generic hex viewer for all kinds of resources. */ public class GenericHexViewer extends JPanel implements IHexViewListener, Closeable, IDataChangedListener { private static final String FMT_OFFSET = "%1$Xh (%1$d)"; private final JHexView hexView; private final IMenuCreator menuCreator; private final VariableDataProvider dataProvider; private FindDataDialog findData; private boolean isModified; public GenericHexViewer() { this(new byte[0]); } public GenericHexViewer(ResourceEntry entry) throws Exception { this(entry.getResourceBuffer().array()); } public GenericHexViewer(byte[] data) { super(); if (data == null) { data = new byte[0]; } this.hexView = new JHexView(); this.dataProvider = new VariableDataProvider(data); this.dataProvider.addListener(this); this.menuCreator = new MenuCreator(this.hexView); this.isModified = false; initGui(); } //--------------------- Begin Interface IHexViewListener --------------------- @Override public void stateChanged(HexViewEvent event) { if (event.getSource() instanceof JHexView && event.getCause() == HexViewEvent.Cause.SelectionChanged) { JHexView hv = (JHexView)event.getSource(); int offset = (int)hv.getCurrentOffset(); // updating statusbar updateStatusBar(offset); } } //--------------------- End Interface IHexViewListener --------------------- //--------------------- Begin Interface IDataChangedListener --------------------- @Override public void dataChanged(DataChangedEvent event) { if (event.getSource() == dataProvider) { setModified(true); } } //--------------------- End Interface IDataChangedListener --------------------- //--------------------- Begin Interface Closeable --------------------- @Override public void close() throws Exception { hexView.setVisible(false); hexView.dispose(); if (findData != null) { findData.dispose(); findData = null; } } //--------------------- End Interface Closeable --------------------- @Override public boolean requestFocusInWindow() { return hexView.requestFocusInWindow(); } /** Returns data as byte array. */ public byte[] getData() { int len = Math.max(0, dataProvider.getDataLength()); byte[] retVal; if (len > 0) { retVal = dataProvider.getData(0L, len); } else { retVal = new byte[0]; } return retVal; } /** * Returns data as String with the specified character encoding. * Specify {@code null} to use a default ANSI charset. */ public String getText(Charset cs) { if (cs == null) { cs = Misc.CHARSET_DEFAULT; } byte[] data = getData(); return new String(data, cs); } /** Sets new data. Attempts to retain the current cursor position. */ public void setData(byte[] data) { long ofs = hexView.getCurrentOffset(); if (data != null) { dataProvider.setDataLength(data.length); dataProvider.setData(0L, data); } else { dataProvider.setDataLength(0); dataProvider.setData(0L, new byte[0]); } hexView.setCurrentOffset(Math.min(dataProvider.getDataLength(), ofs)); hexView.repaint(); } /** * Sets new data by converting the string into byte data using the specified charset. * @param text Text to set. * @param cs Character encoding of the text. Specify {@code null} to use a default ANSI charset. */ public void setText(String text, Charset cs) { long ofs = hexView.getCurrentOffset(); if (cs == null) { cs = Misc.CHARSET_DEFAULT; } if (text != null) { byte[] data = text.getBytes(cs); dataProvider.setDataLength(data.length); dataProvider.setData(0L, data); } else { dataProvider.setDataLength(0); dataProvider.setData(0L, new byte[0]); } hexView.setCurrentOffset(Math.min(dataProvider.getDataLength(), ofs)); hexView.repaint(); } /** Returns the offset at the current caret position. */ public long getCurrentOffset() { return hexView.getCurrentOffset(); } /** Sets the caret to a new offset. */ public void setCurrentOffset(long offset) { hexView.setCurrentOffset(offset); } /** Returns whether data has been modified. */ public boolean isModified() { return isModified; } public void clearModified() { setModified(false); hexView.clearModified(); } /** Updates the offset information in NI's statusbar. */ public void updateStatusBar() { updateStatusBar((int)hexView.getCurrentOffset()); } public void addDataChangedListener(IDataChangedListener l) { if (l != null ) { dataProvider.addListener(l); } } public void removeDataChangedListener(IDataChangedListener l) { if (l != null) { dataProvider.removeListener(l); } } public void addHexViewListener(IHexViewListener l) { if (l != null) { hexView.addHexListener(l); } } public void removeHexViewListener(IHexViewListener l) { if (l != null) { hexView.removeHexListener(l); } } private void initGui() { setLayout(new BorderLayout()); // configuring hexview Color textColor = dataProvider.isEditable() ? Color.BLACK: Color.GRAY; hexView.setEnabled(false); hexView.setDefinitionStatus(JHexView.DefinitionStatus.UNDEFINED); hexView.setAddressMode(JHexView.AddressMode.BIT32); hexView.setSeparatorsVisible(false); hexView.setBytesPerColumn(1); hexView.setBytesPerRow(16); hexView.setColumnSpacing(8); hexView.setMouseOverHighlighted(false); hexView.setShowModified(true); hexView.setCaretColor(Color.BLACK); hexView.setFontSize(13); hexView.setHeaderFontStyle(Font.BOLD); hexView.setFontColorHeader(new Color(0x0000c0)); hexView.setBackgroundColorOffsetView(hexView.getBackground()); hexView.setFontColorOffsetView(new Color(0x0000c0)); hexView.setBackgroundColorHexView(hexView.getBackground()); hexView.setFontColorHexView1(textColor); hexView.setFontColorHexView2(textColor); hexView.setBackgroundColorAsciiView(hexView.getBackground()); hexView.setFontColorAsciiView(textColor); hexView.setFontColorModified(Color.RED); hexView.setSelectionColor(new Color(0xc0c0c0)); hexView.setMenuCreator(menuCreator); hexView.setEnabled(true); hexView.addHexListener(this); hexView.setData(dataProvider); hexView.setDefinitionStatus(hexView.getData().getDataLength() > 0 ? JHexView.DefinitionStatus.DEFINED : JHexView.DefinitionStatus.UNDEFINED); add(hexView, BorderLayout.CENTER); } private void setModified(boolean b) { isModified = b; if (!isModified) { hexView.clearModified(); } } private void updateStatusBar(int offset) { StatusBar sb = NearInfinity.getInstance().getStatusBar(); if (offset >= 0) { sb.setCursorText(String.format(FMT_OFFSET, offset)); } else { sb.setCursorText(""); } } }