/*******************************************************************************
* This file is part of logisim-evolution.
*
* logisim-evolution 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.
*
* logisim-evolution 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 logisim-evolution. If not, see <http://www.gnu.org/licenses/>.
*
* Original code by Carl Burch (http://www.cburch.com), 2011.
* Subsequent modifications by :
* + Haute École Spécialisée Bernoise
* http://www.bfh.ch
* + Haute École du paysage, d'ingénierie et d'architecture de Genève
* http://hepia.hesge.ch/
* + Haute École d'Ingénierie et de Gestion du Canton de Vaud
* http://www.heig-vd.ch/
* The project is currently maintained by :
* + REDS Institute - HEIG-VD
* Yverdon-les-Bains, Switzerland
* http://reds.heig-vd.ch
*******************************************************************************/
package com.cburch.hex;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import javax.swing.JComponent;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
public class HexEditor extends JComponent implements Scrollable {
private class Listener implements HexModelListener {
public void bytesChanged(HexModel source, long start, long numBytes,
int[] oldValues) {
repaint(0, measures.toY(start), getWidth(),
measures.toY(start + numBytes) + measures.getCellHeight());
}
public void metainfoChanged(HexModel source) {
measures.recompute();
repaint();
}
}
private static final long serialVersionUID = 1L;
private HexModel model;
private Listener listener;
private Measures measures;
private Caret caret;
private Highlighter highlighter;
public HexEditor(HexModel model) {
this.model = model;
this.listener = new Listener();
this.measures = new Measures(this);
this.caret = new Caret(this);
this.highlighter = new Highlighter(this);
setOpaque(true);
setBackground(Color.WHITE);
if (model != null)
model.addHexModelListener(listener);
measures.recompute();
}
public Object addHighlight(int start, int end, Color color) {
return highlighter.add(start, end, color);
}
public void delete() {
long p0 = caret.getMark();
long p1 = caret.getDot();
if (p0 < 0 || p1 < 0)
return;
if (p0 > p1) {
long t = p0;
p0 = p1;
p1 = t;
}
model.fill(p0, p1 - p0 + 1, 0);
}
public Caret getCaret() {
return caret;
}
Highlighter getHighlighter() {
return highlighter;
}
Measures getMeasures() {
return measures;
}
public HexModel getModel() {
return model;
}
//
// Scrollable methods
//
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
public int getScrollableBlockIncrement(Rectangle vis, int orientation,
int direction) {
if (orientation == SwingConstants.VERTICAL) {
int height = measures.getCellHeight();
if (height < 1) {
measures.recompute();
height = measures.getCellHeight();
if (height < 1)
return 19 * vis.height / 20;
}
int lines = Math.max(1, (vis.height / height) - 1);
return lines * height;
} else {
return 19 * vis.width / 20;
}
}
public boolean getScrollableTracksViewportHeight() {
return false;
}
public boolean getScrollableTracksViewportWidth() {
return true;
}
public int getScrollableUnitIncrement(Rectangle vis, int orientation,
int direction) {
if (orientation == SwingConstants.VERTICAL) {
int ret = measures.getCellHeight();
if (ret < 1) {
measures.recompute();
ret = measures.getCellHeight();
if (ret < 1)
return 1;
}
return ret;
} else {
return Math.max(1, vis.width / 20);
}
}
@Override
protected void paintComponent(Graphics g) {
measures.ensureComputed(g);
Rectangle clip = g.getClipBounds();
if (isOpaque()) {
g.setColor(getBackground());
g.fillRect(clip.x, clip.y, clip.width, clip.height);
}
long addr0 = model.getFirstOffset();
long addr1 = model.getLastOffset();
long xaddr0 = measures.toAddress(0, clip.y);
if (xaddr0 == addr0)
xaddr0 = measures.getBaseAddress(model);
long xaddr1 = measures.toAddress(getWidth(), clip.y + clip.height) + 1;
highlighter.paint(g, xaddr0, xaddr1);
g.setColor(getForeground());
Font baseFont = g.getFont();
FontMetrics baseFm = g.getFontMetrics(baseFont);
Font labelFont = baseFont.deriveFont(Font.ITALIC);
FontMetrics labelFm = g.getFontMetrics(labelFont);
int cols = measures.getColumnCount();
int baseX = measures.getBaseX();
int baseY = measures.toY(xaddr0) + baseFm.getAscent()
+ baseFm.getLeading() / 2;
int dy = measures.getCellHeight();
int labelWidth = measures.getLabelWidth();
int labelChars = measures.getLabelChars();
int cellWidth = measures.getCellWidth();
int cellChars = measures.getCellChars();
for (long a = xaddr0; a < xaddr1; a += cols, baseY += dy) {
String label = toHex(a, labelChars);
g.setFont(labelFont);
g.drawString(
label,
baseX - labelWidth
+ (labelWidth - labelFm.stringWidth(label)) / 2,
baseY);
g.setFont(baseFont);
long b = a;
for (int j = 0; j < cols; j++, b++) {
if (b >= addr0 && b <= addr1) {
String val = toHex(model.get(b), cellChars);
int x = measures.toX(b)
+ (cellWidth - baseFm.stringWidth(val)) / 2;
g.drawString(val, x, baseY);
}
}
}
caret.paintForeground(g, xaddr0, xaddr1);
}
public void removeHighlight(Object tag) {
highlighter.remove(tag);
}
public void scrollAddressToVisible(int start, int end) {
if (start < 0 || end < 0)
return;
int x0 = measures.toX(start);
int x1 = measures.toX(end) + measures.getCellWidth();
int y0 = measures.toY(start);
int y1 = measures.toY(end);
int h = measures.getCellHeight();
if (y0 == y1) {
scrollRectToVisible(new Rectangle(x0, y0, x1 - x0, h));
} else {
scrollRectToVisible(new Rectangle(x0, y0, x1 - x0, (y1 + h) - y0));
}
}
public void selectAll() {
caret.setDot(model.getLastOffset(), false);
caret.setDot(0, true);
}
//
// selection methods
//
public boolean selectionExists() {
return caret.getMark() >= 0 && caret.getDot() >= 0;
}
@Override
public void setBounds(int x, int y, int width, int height) {
super.setBounds(x, y, width, height);
measures.widthChanged();
}
@Override
public void setFont(Font value) {
super.setFont(value);
measures.recompute();
}
public void setModel(HexModel value) {
if (model == value)
return;
if (model != null)
model.removeHexModelListener(listener);
model = value;
highlighter.clear();
caret.setDot(-1, false);
if (model != null)
model.addHexModelListener(listener);
measures.recompute();
}
private String toHex(long value, int chars) {
String ret = Long.toHexString(value);
int retLen = ret.length();
if (retLen < chars) {
ret = "0" + ret;
for (int i = retLen + 1; i < chars; i++) {
ret = "0" + ret;
}
return ret;
} else if (retLen == chars) {
return ret;
} else {
return ret.substring(retLen - chars);
}
}
}