gui: show source line numbers

This commit is contained in:
Skylot
2014-05-30 22:08:30 +04:00
parent 0a1981f90e
commit 324f544ba2
10 changed files with 242 additions and 39 deletions
@@ -30,7 +30,7 @@ class CodeArea extends RSyntaxTextArea {
private static final long serialVersionUID = 6312736869579635796L;
private static final Color BACKGROUND = new Color(0xf7f7f7);
public static final Color BACKGROUND = new Color(0xf7f7f7);
private static final Color JUMP_FOREGROUND = new Color(0x785523);
private static final Color JUMP_BACKGROUND = new Color(0xE6E6FF);
@@ -96,7 +96,11 @@ class CodeArea extends RSyntaxTextArea {
}
Position getCurrentPosition() {
return new Position(cls, getCaretLineNumber());
return new Position(cls, getCaretLineNumber() + 1);
}
Integer getSourceLine(int line) {
return cls.getCls().getSourceLine(line);
}
void scrollToLine(int line) {
@@ -5,14 +5,13 @@ import jadx.gui.utils.Utils;
import javax.swing.AbstractAction;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import org.fife.ui.rtextarea.RTextScrollPane;
class CodePanel extends JPanel {
private static final long serialVersionUID = 5310536092010045565L;
@@ -21,7 +20,7 @@ class CodePanel extends JPanel {
private final JClass jClass;
private final SearchBar searchBar;
private final CodeArea codeArea;
private final RTextScrollPane scrollPane;
private final JScrollPane scrollPane;
CodePanel(TabbedPane panel, JClass cls) {
tabbedPane = panel;
@@ -29,8 +28,8 @@ class CodePanel extends JPanel {
codeArea = new CodeArea(this);
searchBar = new SearchBar(codeArea);
scrollPane = new RTextScrollPane(codeArea);
scrollPane.setFoldIndicatorEnabled(true);
scrollPane = new JScrollPane(codeArea);
scrollPane.setRowHeaderView(new LineNumbers(codeArea));
setLayout(new BorderLayout());
add(searchBar, BorderLayout.NORTH);
@@ -65,7 +64,7 @@ class CodePanel extends JPanel {
return codeArea;
}
RTextScrollPane getScrollPane() {
JScrollPane getScrollPane() {
return scrollPane;
}
}
@@ -0,0 +1,188 @@
package jadx.gui.ui;
import javax.swing.JPanel;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.MatteBorder;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.StyleConstants;
import javax.swing.text.Utilities;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
public class LineNumbers extends JPanel implements CaretListener {
private final static Border OUTER = new MatteBorder(0, 0, 0, 1, Color.LIGHT_GRAY);
private final static int HEIGHT = Integer.MAX_VALUE - 1000000;
public static final Color FOREGROUND = Color.GRAY;
public static final Color BACKGROUND = CodeArea.BACKGROUND;
public static final Color CURRENT_LINE_FOREGROUND = new Color(227, 0, 0);
private CodeArea codeArea;
private boolean useSourceLines = true;
private int lastDigits;
private int lastLine;
private HashMap<String, FontMetrics> fonts;
public LineNumbers(CodeArea component) {
this.codeArea = component;
setFont(component.getFont());
setBackground(BACKGROUND);
setForeground(FOREGROUND);
setBorderGap(5);
setPreferredWidth();
component.addCaretListener(this);
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
useSourceLines = !useSourceLines;
repaint();
}
}
});
}
public void setBorderGap(int borderGap) {
Border inner = new EmptyBorder(0, borderGap, 0, borderGap);
setBorder(new CompoundBorder(OUTER, inner));
lastDigits = 0;
}
private void setPreferredWidth() {
Element root = codeArea.getDocument().getDefaultRootElement();
int lines = root.getElementCount();
int digits = Math.max(String.valueOf(lines).length(), 3);
if (lastDigits != digits) {
lastDigits = digits;
FontMetrics fontMetrics = getFontMetrics(getFont());
int width = fontMetrics.charWidth('0') * digits;
Insets insets = getInsets();
int preferredWidth = insets.left + insets.right + width;
Dimension d = getPreferredSize();
if (d != null) {
d.setSize(preferredWidth, HEIGHT);
setPreferredSize(d);
setSize(d);
}
}
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
FontMetrics fontMetrics = codeArea.getFontMetrics(codeArea.getFont());
Insets insets = getInsets();
int availableWidth = getSize().width - insets.left - insets.right;
Rectangle clip = g.getClipBounds();
int rowStartOffset = codeArea.viewToModel(new Point(0, clip.y));
int endOffset = codeArea.viewToModel(new Point(0, clip.y + clip.height));
while (rowStartOffset <= endOffset) {
try {
if (isCurrentLine(rowStartOffset)) {
g.setColor(CURRENT_LINE_FOREGROUND);
} else {
g.setColor(FOREGROUND);
}
String lineNumber = getTextLineNumber(rowStartOffset);
int stringWidth = fontMetrics.stringWidth(lineNumber);
int x = availableWidth - stringWidth + insets.left;
int y = getOffsetY(rowStartOffset, fontMetrics);
g.drawString(lineNumber, x, y);
rowStartOffset = Utilities.getRowEnd(codeArea, rowStartOffset) + 1;
} catch (Exception e) {
break;
}
}
}
private boolean isCurrentLine(int rowStartOffset) {
int caretPosition = codeArea.getCaretPosition();
Element root = codeArea.getDocument().getDefaultRootElement();
return root.getElementIndex(rowStartOffset) == root.getElementIndex(caretPosition);
}
protected String getTextLineNumber(int rowStartOffset) {
Element root = codeArea.getDocument().getDefaultRootElement();
int index = root.getElementIndex(rowStartOffset);
Element line = root.getElement(index);
if (line.getStartOffset() == rowStartOffset) {
int lineNumber = index + 1;
if (useSourceLines) {
Integer sourceLine = codeArea.getSourceLine(lineNumber);
if (sourceLine != null) {
return String.valueOf(sourceLine);
}
} else {
return String.valueOf(lineNumber);
}
}
return "";
}
private int getOffsetY(int rowStartOffset, FontMetrics fontMetrics) throws BadLocationException {
Rectangle r = codeArea.modelToView(rowStartOffset);
if (r == null) {
throw new BadLocationException("Can't get Y offset", rowStartOffset);
}
int lineHeight = fontMetrics.getHeight();
int y = r.y + r.height;
int descent = 0;
if (r.height == lineHeight) {
descent = fontMetrics.getDescent();
} else {
if (fonts == null) {
fonts = new HashMap<String, FontMetrics>();
}
Element root = codeArea.getDocument().getDefaultRootElement();
int index = root.getElementIndex(rowStartOffset);
Element line = root.getElement(index);
for (int i = 0; i < line.getElementCount(); i++) {
Element child = line.getElement(i);
AttributeSet as = child.getAttributes();
String fontFamily = (String) as.getAttribute(StyleConstants.FontFamily);
Integer fontSize = (Integer) as.getAttribute(StyleConstants.FontSize);
String key = fontFamily + fontSize;
FontMetrics fm = fonts.get(key);
if (fm == null) {
Font font = new Font(fontFamily, Font.PLAIN, fontSize);
fm = codeArea.getFontMetrics(font);
fonts.put(key, fm);
}
descent = Math.max(descent, fm.getDescent());
}
}
return y - descent;
}
@Override
public void caretUpdate(CaretEvent e) {
int caretPosition = codeArea.getCaretPosition();
Element root = codeArea.getDocument().getDefaultRootElement();
int currentLine = root.getElementIndex(caretPosition);
if (lastLine != currentLine) {
repaint();
lastLine = currentLine;
}
}
}
@@ -54,13 +54,13 @@ public class MainWindow extends JFrame {
private static final double BORDER_RATIO = 0.15;
private static final double WINDOW_RATIO = 1 - BORDER_RATIO * 2;
private static final double SPLIT_PANE_RESIZE_WEIGHT = 0.15;
private static final ImageIcon ICON_OPEN = Utils.openIcon("folder");
private static final ImageIcon ICON_SAVE_ALL = Utils.openIcon("disk_multiple");
private static final ImageIcon ICON_CLOSE = Utils.openIcon("cross");
private static final ImageIcon ICON_FLAT_PKG = Utils.openIcon("empty_logical_package_obj");
private static final ImageIcon ICON_SEARCH = Utils.openIcon("magnifier");
private static final ImageIcon ICON_BACK = Utils.openIcon("icon_back");
private static final ImageIcon ICON_FORWARD = Utils.openIcon("icon_forward");
@@ -248,7 +248,7 @@ public class MainWindow extends JFrame {
private void initUI() {
mainPanel = new JPanel(new BorderLayout());
JSplitPane splitPane = new JSplitPane();
splitPane.setResizeWeight(0.2);
splitPane.setResizeWeight(SPLIT_PANE_RESIZE_WEIGHT);
mainPanel.add(splitPane);
DefaultMutableTreeNode treeRoot = new DefaultMutableTreeNode("Please open file");