I am trying to simulate the movement of a debugging cursor using java. I am having problem to get the viewable area of the JScrollPane to the right position.
Here is
I don't think the line numbers should be part of the text. For example you have a horizontal scrollbar. If you scroll to the right you will lose the line numbers.
Instead you should use a row header to display the line numbers.
See Text Component Line Number. It contains a class that does custom painting of the line number for you. You can use add this component to the row header.
The painting code in that class will highlight the current line number. If you want to add an arrow then you will need to modify the painting code. In the paintComponent(...)
method you can add the following:
g.drawString(lineNumber, x, y);
// Code to paint an arrow
if (isCurrentLine(rowStartOffset))
{
int height = fontMetrics.getAscent() - fontMetrics.getDescent();
Polygon triangle = new Polygon();
triangle.addPoint(borderGap, y);
triangle.addPoint(borderGap, y - height);
triangle.addPoint(borderGap + 10, y - height / 2);
Graphics2D g2d = (Graphics2D)g.create();
g2d.fill( triangle );
g2d.dispose();
}
One more change to make. Since we are now painting an arrow we will need to increase the width of the components. So in the setPreferredWidth(...)
method you will need to make the following change:
//int preferredWidth = insets.left + insets.right + width;
int preferredWidth = insets.left + insets.right + width + 15;
I want to scroll only if the line I want to jump it is not visible.
Here is some code to do this:
public static void gotoStartOfLine(JTextComponent component, int line)
{
Element root = component.getDocument().getDefaultRootElement();
line = Math.max(line, 1);
line = Math.min(line, root.getElementCount());
int startOfLineOffset = root.getElement( line - 1 ).getStartOffset();
component.setCaretPosition( startOfLineOffset );
}
I took the above code from Text Utilities which may have other methods of interest (if not now, in the future).
You can also use the Line Painter if you want to highlight the entire line in the text pane.
This is simple example, using a JList
and JScrollPane
's rowHeader
support.
The magic basically happens here...
int index = list.getSelectedIndex();
index++;
if (index >= list.getModel().getSize()) {
index = 0;
}
list.setSelectedIndex(index);
Rectangle cellBounds = list.getCellBounds(index, index);
list.scrollRectToVisible(cellBounds);
Basically, we ask the view to calculate the Rectangle
which is represented by the selected index and simply ask the component to scroll so that rectangle is visible
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JList list;
public TestPane() {
setLayout(new BorderLayout());
DefaultListModel model = new DefaultListModel();
try (BufferedReader br = new BufferedReader(new FileReader(new File("src/test/Test.java")))) {
String text = null;
while ((text = br.readLine()) != null) {
model.addElement(text);
}
} catch (IOException exp) {
exp.printStackTrace();
}
list = new JList(model);
list.setSelectedIndex(0);
JScrollPane sp = new JScrollPane(list);
sp.setRowHeaderView(new Header(list));
add(sp);
JButton next = new JButton("Next");
next.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int index = list.getSelectedIndex();
index++;
if (index >= list.getModel().getSize()) {
index = 0;
}
list.setSelectedIndex(index);
Rectangle cellBounds = list.getCellBounds(index, index);
list.scrollRectToVisible(cellBounds);
}
});
add(next, BorderLayout.SOUTH);
}
}
protected class Header extends JPanel {
private JList list;
public Header(JList list) {
this.list = list;
list.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
repaint();
}
});
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Container parent = list.getParent();
if (parent instanceof JViewport) {
JViewport viewport = (JViewport) parent;
Graphics2D g2d = (Graphics2D) g.create();
int selectedRow = list.getSelectedIndex();
if (selectedRow >= 0) {
Rectangle cellBounds = list.getCellBounds(selectedRow, selectedRow);
cellBounds.y -= viewport.getViewPosition().y;
g2d.setColor(Color.RED);
g2d.fillRect(0, cellBounds.y, getWidth(), cellBounds.height);
}
g2d.dispose();
}
}
}
}