org.apache.pdfbox.debugger.hexviewer.HexPane Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pdfbox-debugger Show documentation
Show all versions of pdfbox-debugger Show documentation
The Apache PDFBox library is an open source Java tool for working with PDF documents.
This artefact contains the PDFDebugger.
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.pdfbox.debugger.hexviewer;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.font.TextAttribute;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JPanel;
/**
* @author Khyrul Bashar
*
* HexPane shows the byte in a Grid table where every row has 16 bytes. It only draws bytes those are
* only visible at a given time.
*/
@SuppressWarnings("squid:S1948")
class HexPane extends JPanel implements KeyListener, MouseListener, MouseMotionListener, HexModelChangeListener
{
private final HexModel model;
private int selectedIndex = -1;
private static final byte EDIT = 2;
private static final byte SELECTED = 1;
private static final byte NORMAL = 0;
private byte state = NORMAL;
private int selectedChar = 0;
private final List hexChangeListeners = new ArrayList<>();
private final List selectionChangeListeners = new ArrayList<>();
/**
* Constructor.
* @param model HexModel instance.
*/
HexPane(HexModel model)
{
this.model = model;
model.addHexModelChangeListener(this);
setPreferredSize(new Dimension(HexView.HEX_PANE_WIDTH, HexView.CHAR_HEIGHT * (model.totalLine()+1)));
this.addMouseListener(this);
this.addMouseMotionListener(this);
this.addKeyListener(this);
setAutoscrolls(true);
setFont(HexView.FONT);
}
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHints(HexView.RENDERING_HINTS);
Rectangle bound = getVisibleRect();
g.clearRect(bound.x, bound.y, bound.width, bound.height);
g.setColor(Color.WHITE);
g.fillRect(bound.x, bound.y, bound.width, bound.height);
int x = HexView.LINE_INSET;
int y = bound.y;
if (y == 0 || y% HexView.CHAR_HEIGHT != 0)
{
y += HexView.CHAR_HEIGHT - y% HexView.CHAR_HEIGHT;
}
int firstLine = y/ HexView.CHAR_HEIGHT;
g.setColor(Color.BLACK);
for (int i = firstLine; i <= firstLine + bound.height/ HexView.CHAR_HEIGHT; i++)
{
if (i > model.totalLine())
{
break;
}
byte[] bytes = model.getBytesForLine(i);
int index = (i - 1) * 16;
for (byte by : bytes)
{
String str = String.format("%02X", by);
if (selectedIndex == index && state == SELECTED)
{
g.drawString(getSelectedString(str).getIterator(), x, y);
}
else if (selectedIndex == index && state == EDIT)
{
paintInEdit(g, by, x, y);
}
else
{
g.drawString(str, x, y);
}
x += HexView.CHAR_WIDTH;
index++;
}
x = HexView.LINE_INSET;
y += HexView.CHAR_HEIGHT;
}
}
private void paintInEdit(Graphics g, byte content, int x, int y)
{
g.setFont(HexView.BOLD_FONT);
g.setColor(Color.white);
char[] chars = getChars(content);
if (selectedChar == 0)
{
g.setColor(HexView.SELECTED_COLOR);
g.drawChars(chars, 0, 1, x, y);
g.setColor(Color.black);
g.drawChars(chars, 1, 1, x + g.getFontMetrics().charWidth(chars[0]), y);
}
else
{
g.setColor(Color.black);
g.drawChars(chars, 0, 1, x, y);
g.setColor(HexView.SELECTED_COLOR);
g.drawChars(chars, 1, 1,x + g.getFontMetrics().charWidth(chars[0]), y);
}
setDefault(g);
}
private AttributedString getSelectedString(String str)
{
AttributedString string = new AttributedString(str);
string.addAttribute(TextAttribute.FONT, HexView.BOLD_FONT);
string.addAttribute(TextAttribute.FOREGROUND, HexView.SELECTED_COLOR);
return string;
}
private void setDefault(Graphics g)
{
g.setColor(Color.black);
g.setFont(this.getFont());
}
/**
* Returns the index for a given point If there is any byte in there.
* @param point Point instance.
* @return index.
*/
private int getIndexForPoint(Point point)
{
if (point.x <= 20 || point.x >= (16 * HexView.CHAR_WIDTH) + 20)
{
return -1;
}
int y = point.y;
int lineNumber = (y + (HexView.CHAR_HEIGHT - (y % HexView.CHAR_HEIGHT))) / HexView.CHAR_HEIGHT;
int x = point.x - 20;
int elementNumber = x / HexView.CHAR_WIDTH;
return (lineNumber - 1) * 16 + elementNumber;
}
/**
* Returns the starting point in the view for any index.
* @param index int.
* @return Point instance.
*/
private Point getPointForIndex(int index)
{
int x = HexView.LINE_INSET + HexModel.elementIndexInLine(index)* HexView.CHAR_WIDTH;
int y = HexModel.lineNumber(index) * HexView.CHAR_HEIGHT;
return new Point(x, y);
}
/**
* Puts an index in selected state or in other word it selects the byte of the index.
* @param index
*/
private void putInSelected(int index)
{
state = SELECTED;
selectedChar = 0;
Point point = getPointForIndex(index);
//for column one
if (index%16 == 0)
{
scrollRectToVisible(new Rectangle(0, HexModel.lineNumber(index) * HexView.CHAR_HEIGHT, 1, 1));
}
else if (!getVisibleRect().intersects(point.x, point.y, HexView.CHAR_WIDTH, HexView.CHAR_HEIGHT))
{
scrollRectToVisible(new Rectangle(point.x, point.y, HexView.CHAR_WIDTH, HexView.CHAR_HEIGHT));
}
selectedIndex = index;
repaint();
requestFocusInWindow();
}
private void fireSelectionChanged(SelectEvent event)
{
selectionChangeListeners.forEach(listener -> listener.selectionChanged(event));
}
private void fireHexValueChanged(byte value, int index)
{
hexChangeListeners.forEach(listener -> listener.hexChanged(new HexChangedEvent(value, index)));
}
public void addSelectionChangeListener(SelectionChangeListener listener)
{
selectionChangeListeners.add(listener);
}
public void addHexChangeListeners(HexChangeListener listener)
{
hexChangeListeners.add(listener);
}
@Override
public void keyTyped(KeyEvent keyEvent)
{
if (selectedIndex != -1)
{
char c = keyEvent.getKeyChar();
if (isHexChar(c))
{
byte previousByte = model.getByte(selectedIndex);
char[] chars = getChars(previousByte);
chars[selectedChar] = c;
byte editByte = getByte(chars);
if (selectedChar == 0)
{
state = EDIT;
selectedChar = 1;
fireHexValueChanged(editByte, selectedIndex);
}
else
{
fireHexValueChanged(editByte, selectedIndex);
fireSelectionChanged(new SelectEvent(selectedIndex, SelectEvent.NEXT));
}
}
}
}
@Override
public void keyPressed(KeyEvent keyEvent)
{
if (state == SELECTED || state == EDIT)
{
switch (keyEvent.getKeyCode())
{
case 37:
if (state == EDIT && selectedChar == 1)
{
selectedChar = 0;
repaint();
}
else
{
fireSelectionChanged(new SelectEvent(selectedIndex, SelectEvent.PREVIOUS));
}
break;
case 39:
fireSelectionChanged(new SelectEvent(selectedIndex, SelectEvent.NEXT));
break;
case 38:
fireSelectionChanged(new SelectEvent(selectedIndex, SelectEvent.UP));
break;
case 40:
fireSelectionChanged(new SelectEvent(selectedIndex, SelectEvent.DOWN));
break;
default:
break;
}
}
}
@Override
public void keyReleased(KeyEvent keyEvent)
{
// do nothing
}
@Override
public void mouseClicked(MouseEvent mouseEvent)
{
int index = getIndexForPoint(mouseEvent.getPoint());
if (index == -1)
{
fireSelectionChanged(new SelectEvent(-1, SelectEvent.NONE));
return;
}
fireSelectionChanged(new SelectEvent(index, SelectEvent.IN));
}
@Override
public void mousePressed(MouseEvent mouseEvent)
{
// do nothing
}
@Override
public void mouseReleased(MouseEvent mouseEvent)
{
// do nothing
}
@Override
public void mouseEntered(MouseEvent mouseEvent)
{
// do nothing
}
@Override
public void mouseExited(MouseEvent mouseEvent)
{
// do nothing
}
@Override
public void mouseDragged(MouseEvent mouseEvent)
{
// do nothing
}
@Override
public void mouseMoved(MouseEvent mouseEvent)
{
// do nothing
}
private static boolean isHexChar(char c)
{
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
private char[] getChars(byte b)
{
return String.format("%02X", b & 0XFF).toCharArray();
}
private byte getByte(char[] chars)
{
return (byte) (Integer.parseInt(new String(chars), 16) & 0XFF);
}
public void setSelected(int index)
{
if (index != selectedIndex)
{
putInSelected(index);
}
}
@Override
public void hexModelChanged(HexModelChangedEvent event)
{
repaint();
}
}