0

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 a picture I want to achive: The desired layout

I want to scroll only if the line I want to jump it is not visible. The calculation if it helps can by done using CodeDrowingPanel.NUMBER_OF_LINES and CodeDrowingPanel.FONT_SIZE the lines are painted on a panel using these constants.

If I have to jump, the line I have to jump should be at the bottom.

I have to bare in mind that the visible area depends of the screen resolution. The application is maximized with no chance of resizing.

EDIT:

public void setCursorToLine(int line, JScrollPane codeArea)
{
    if(line*CodeDrowingPanel.FONT_SIZE > this.getHeight()+43)
        this.cursorPosition = this.getHeight()+43;
    else
        this.cursorPosition = line * CodeDrowingPanel.FONT_SIZE;
    JViewport viewPort = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, codeArea);
    if (viewPort != null) 
    {
        Rectangle view = viewPort.getViewRect();
        view.y += line - previousLine;

        codeArea.scrollRectToVisible(view);
    }
    this.repaint();
}

This is how I am trying now to modify the line. But it does not work. I tried to follow your second example from the first comment. I don't know how to use the method from the second comment.

Tandura
  • 866
  • 2
  • 7
  • 19
  • The simple solution would be to use `scrollRectToVisible`, for [example](http://stackoverflow.com/questions/15399315/scrolling-jcomponent-in-jscrollpane/15399342#15399342) and [example](http://stackoverflow.com/questions/31171502/scroll-jscrollpane-by-dragging-mouse-java-swing/31173371#31173371) – MadProgrammer Nov 30 '15 at 23:55
  • A few simpler solutions might be to use a `JList` and the `JScrollPane`'s `rowHeader` suppport – MadProgrammer Nov 30 '15 at 23:56
  • @MadProgrammer can you give me an example of how my code should roughly look like? – Tandura Dec 01 '15 at 00:58
  • For which point? I provide two links to example for `scrollRectToVisible`. Since I have no idea what you code actually looks like now, I'd have little hope of providing you any guidance into how you would modify your existing code – MadProgrammer Dec 01 '15 at 00:59
  • @MadProgrammer edited the post. If you can point what I did wrong or give an example for the second comment? use instead of actual line code strings like "line 1", "line 2". – Tandura Dec 01 '15 at 01:08
  • Shouldn't `view.y += line - previousLine;` be `view.y += cursorPosition;`? Assuming the `cursorPosition ` is a pixel point and not some virtual index into the large model – MadProgrammer Dec 01 '15 at 01:19
  • the cursor position is the bottom-left point of the rectangle for the green triangle. It is an int because only the y coordonate is changeing – Tandura Dec 01 '15 at 01:34
  • But what do `line` and `previousLine` represent? Consider providing a [runnable example](https://stackoverflow.com/help/mcve) which demonstrates your problem. This is not a code dump, but an example of what you are doing which highlights the problem you are having. This will result in less confusion and better responses – MadProgrammer Dec 01 '15 at 01:43
  • Line is the line number I want to go and previousLine is the line I currently am – Tandura Dec 01 '15 at 01:47
  • In the model's context? Line 1, Line 2, not pixel coordinates – MadProgrammer Dec 01 '15 at 01:49

2 Answers2

2

This is simple example, using a JList and JScrollPane's rowHeader support.

Scroll rect to visible

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();
            }
        }

    }

}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • (1+) A row header is the way to go. – camickr Dec 01 '15 at 04:16
  • the problem with this solution is that the user can click an element and move the index anywhere – Tandura Dec 01 '15 at 12:49
  • @Tandura Oh dear me, what a shame you can't see past the problem to the demonstration of the solution. You can control use selection of a `JList`, if you tried, but if you can't see your self past that minor problem, then I guess your stuck – MadProgrammer Dec 01 '15 at 20:29
  • @MadProgrammer I used the first answer to solve the problem. It is a good one. Thank you! P.S.: I did tried the second one first tho it seemed more simple. – Tandura Dec 01 '15 at 20:34
  • @Tandura I really only wanted to demonstrate the use of the row header and `scrollRectToVisible` and this was the simplest way to do so ;) – MadProgrammer Dec 01 '15 at 20:37
2

enter image description here

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.

camickr
  • 321,443
  • 19
  • 166
  • 288