Change AutoScroll Bounds in JTextPane

I cannot figure this out for the life of me. I would like to CHANGE the bounds for the autoscroll on a JTextPane that resides inside a JScrollPane when the user selects text and moves the mouse outside of the "autoscroll bounds".

The normal bounds for scrolling down a JTextPane (when inside a JScrollPane) are usually the bounds of the JTextPane itself. (You can verify this with my example by selecting some text with the mouse button held down, and moving your cursor outside the bottom of the window). I would like to move the bottom bounds up higher into the window (indicated by the black line that resides on the glasspane).

I have provided this example to use as a starting point. It doesn't do anything of use right now. I have tried about 20 things, but things have gotten so messed up, and the level of internal code that I started to mess around with got way over my head... so I'm just going to post this with a clean start, and hopefully we can move in a positive direction. I will relay my findings about a paticular route if we start moving down one i've already failed at.

Thanks for you time.

-Js

import java.awt.Color;

import java.awt.Dimension;

import java.awt.Graphics;

import javax.swing.JFrame;

import javax.swing.JPanel;

import javax.swing.JScrollPane;

import javax.swing.JTextPane;

import javax.swing.text.BadLocationException;

import javax.swing.text.StyledDocument;

publicclass AutoScrollTestextends JFrame

{

//DATA

privatestaticfinalint Y_SCROLL_THRESHOLD = 150;

//GUI

private JPanel glassPanel;

private JScrollPane scrollPane;

private JTextPane textPane;

public AutoScrollTest()

{

this.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

this.setGlassPane(getGlassPanel());

this.setContentPane(getScrollPane());

getGlassPanel().setVisible(true);

this.pack();

this.setVisible(true);

}

private JPanel getGlassPanel()

{

if(glassPanel ==null)

{

glassPanel =new JPanel()

{

publicvoid paintComponent(Graphics g)

{

super.paintComponent(g);

g.setColor(Color.BLACK);

g.drawLine(0, Y_SCROLL_THRESHOLD, 300, Y_SCROLL_THRESHOLD);

}

};

glassPanel.setOpaque(false);

}

return glassPanel;

}

private JScrollPane getScrollPane()

{

if(scrollPane ==null)

{

scrollPane =new JScrollPane(getTextPane());

scrollPane.setPreferredSize(new Dimension(300,300));

}

return scrollPane;

}

private JTextPane getTextPane()

{

if(textPane ==null)

{

textPane =new JTextPane();

try

{

StyledDocument sd = textPane.getStyledDocument();

for(int i=1; i<1000; i++)

{

sd.insertString(sd.getLength(),"This is line: " + i +"\n",null);

}

}

catch(BadLocationException ble)

{

ble.printStackTrace();

}

}

return textPane;

}

publicstaticvoid main(String args[])

{

new AutoScrollTest();

}

}

[5681 byte] By [JSnakea] at [2007-11-26 16:07:24]
# 1
You probably should add an adjustment listener to the vertical scrollbar of the scrollpane
tjacobs01a at 2007-7-8 22:29:40 > top of Java-index,Desktop,Core GUI APIs...
# 2
What am I listening for?Could you elaborate on that answer?-Js
JSnakea at 2007-7-8 22:29:40 > top of Java-index,Desktop,Core GUI APIs...
# 3
You could do something likeif (scrolloffset + viewport_height > scroll_limit_in_my_textpane) {verticalscrollbar.setValue(...)}
tjacobs01a at 2007-7-8 22:29:40 > top of Java-index,Desktop,Core GUI APIs...
# 4

Wouldn't that just be a "one time" set on the scrollbar?

In any case... i can't "respond" to any movement of the scrollbar because when the window first opens, while im drag-selecting the text, as soon as i move past that black line, I want it to start auto-scrolling down.

The only way I can see to do this is to figure out how JTextPane is doing it in the first place. There must be bounds on the window, that are being checked by something "scrollable" and is reacting accordingly. Any idea who is reacting?

-Js

JSnakea at 2007-7-8 22:29:40 > top of Java-index,Desktop,Core GUI APIs...
# 5
> The only way I can see to do this is to figure out> how JTextPane is doing it in the first place. ThereThat's pretty easy. See scrollRectToVisible(...) (I forget what class this method is defined in)
tjacobs01a at 2007-7-8 22:29:40 > top of Java-index,Desktop,Core GUI APIs...
# 6

> Any idea who is reacting?

I was looking at JComponent where the setAutoScrolls() method is defined. The method will create an AutoScroller when auto scrolls is set to true.

So I tried to override the setAutoScrolls() and getAutoScrolls() methods to use a custom scroller. I created MyAutoScroller class by making the following change:

public void mouseDragged(MouseEvent e)

{

Rectangle visibleRect = component.getVisibleRect();

visibleRect.width = visibleRect.width / 2; // I added this

boolean contains = visibleRect.contains(e.getX(), e.getY());

Unfortunately, this class usescomponent.superProcessMouseMotionEvent(e); which is a package proctected method. I couldn't figure out how to get around the problem so I just tried a component.dispatchEvent(e), which didn't work.

My next attempt was to have the MyAutoScroller class extend MouseInputAdapter, and I then added it as a MouseMotionListener to the text pane. This worked a little better in that the selected text acutally stopped being highlighted one line below the black line, but unfortunately no scrolling happened. So I'm running out of ideas.

camickra at 2007-7-8 22:29:40 > top of Java-index,Desktop,Core GUI APIs...
# 7
(removed)Message was edited by: JSnake
JSnakea at 2007-7-8 22:29:40 > top of Java-index,Desktop,Core GUI APIs...
# 8

tjacobs01: No.. i understand how scrollRectToVisible is moving the window. My problem comes before this. Another method is calling scrollRectToVisible.

In fact, I added the AdjustmentListener to the vertical scrollbar for debugging purposes. Inside the AdjustmentListener I added a break point, ran my "text-selection-drag" test and of course it hit the breakpoint the moment i went over the edge of my JTextPane/JScrollPane border.

Here's the quick and dirty version of the stack trace (in correct reverse order of calls):

1. JTextPane.scrollRectToVisible()

2. DefaultCaret.adjustVisibility(Rectangle)

3. DefaultCaret.repaintNewCaret()

4. DefaultCaret.run()

From there it goes back to the everyday dispatch of events code. My problem is.. ... who is triggering the event, of which the EDT is then responding to, and telling the DefaultCaret that its too low on the screen and to move the window. There must be a check somwhere like..

if(mouseDragged == TRUE && mouseY >= windowBounds.height)

{

send_action_to_move_down_window();

}

I just don't know where this is to overload it. Any suggestions?

camickr: Those are some good ideas... I'm experimenting in the same area..please let me know if you get any further than you had just explained.

Thanx for your help guys. I appreciate it.

-Js

JSnakea at 2007-7-8 22:29:40 > top of Java-index,Desktop,Core GUI APIs...
# 9
camickr: Yeah, I am experimenting with the mouse motion listener, but it's turning out to be very difficult. Please let me know if you make it any further with this approach. I liked the other idea, too bad it didn't work!Thanx guys for you help.-Js
JSnakea at 2007-7-8 22:29:40 > top of Java-index,Desktop,Core GUI APIs...
# 10

I would separate the problem into two different ones.

1. Detecting whether we need to tart the scroller. Camickr showed how to achieve this.

2. Scroll itself.

For this see BasicScrollbarUI class. How scrollTimer works. In fact the timer starts when you pres and hold mouse on scroll button (up or down) of JScrollBar.

When the button is pressed scroll timer strts with some initial delay. After the delay it checks whether the button still pressed. If yes it scrolls and restarts. If no just stop itself.

I think you need the same.

See also ScrollListener inner clas in the same BasicScrollVarUI class.

regards,

Stas

StanislavLa at 2007-7-8 22:29:40 > top of Java-index,Desktop,Core GUI APIs...
# 11

camickr & StanislavL:

Great work guys. I have used both of your ideas... and it works GREAT! I'll split the dukes between you.

Please take a look at the finished code, its very cool. Again, thanks.

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

import javax.swing.event.*;

import javax.swing.text.*;

public class AutoScrollTest extends JFrame

{

//DATA

private static final int Y_SCROLL_THRESHOLD = 150;

private Timer scrollTimer;

//GUI

private JPanel glassPanel;

private JScrollPane scrollPane;

private JTextPane textPane;

public AutoScrollTest()

{

this.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

this.setGlassPane(getGlassPanel());

this.setContentPane(getScrollPane());

getGlassPanel().setVisible(true);

this.pack();

this.setVisible(true);

}

private JPanel getGlassPanel()

{

if(glassPanel == null)

{

glassPanel = new JPanel()

{

public void paintComponent(Graphics g)

{

super.paintComponent(g);

g.setColor(Color.BLACK);

g.drawLine(0, Y_SCROLL_THRESHOLD, 300, Y_SCROLL_THRESHOLD);

}

};

glassPanel.setOpaque(false);

}

return glassPanel;

}

private JScrollPane getScrollPane()

{

if(scrollPane == null)

{

scrollPane = new JScrollPane(getTextPane());

scrollPane.setPreferredSize(new Dimension(300,300));

}

return scrollPane;

}

private JTextPane getTextPane()

{

if(textPane == null)

{

textPane = new JTextPane();

MouseInputAdapter mia = new MouseInputAdapter()

{

public void mouseDragged(MouseEvent e)

{

Point viewPortPoint = getScrollPane().getViewport().getViewPosition();

if(e.getY()-viewPortPoint.getY() > Y_SCROLL_THRESHOLD)

{

getScrollTimer().start();

}

else

{

getScrollTimer().stop();

}

}

public void mouseReleased(MouseEvent e)

{

getScrollTimer().stop();

}

};

textPane.addMouseListener(mia);

textPane.addMouseMotionListener(mia);

try

{

StyledDocument sd = textPane.getStyledDocument();

for(int i=1; i<1000; i++)

{

sd.insertString(sd.getLength(),"This is line: " + i + "\n", null);

}

}

catch(BadLocationException ble)

{

ble.printStackTrace();

}

}

return textPane;

}

private Timer getScrollTimer()

{

if(scrollTimer == null)

{

scrollTimer = new Timer(60,new MyScrollListener(getTextPane(),getScrollPane().getVerticalScrollBar()));

}

return scrollTimer;

}

public static void main(String args[])

{

new AutoScrollTest();

}

}

class MyScrollListener implements ActionListener

{

private JTextPane textPane;

private JScrollBar scrollBar;

public MyScrollListener(JTextPane textPane, JScrollBar scrollBar)

{

this.scrollBar = scrollBar;

this.textPane = textPane;

}

public void actionPerformed(ActionEvent e)

{

//Move down the scrollbar

scrollBar.setValue(scrollBar.getValue()+scrollBar.getBlockIncrement());

//Update the mouse highlight

PointerInfo mousePointerInfo = MouseInfo.getPointerInfo();

Point convertedPoint = SwingUtilities.convertPoint(null,mousePointerInfo.getLocation(),textPane);

Position.Bias[] bias = new Position.Bias[1];

int mouseOffset = textPane.getUI().viewToModel(textPane,convertedPoint, bias);

if (bias[0] == Position.Bias.Backward && mouseOffset != 0)

{

--mouseOffset;

}

textPane.setSelectionEnd(mouseOffset);

}

}

JSnakea at 2007-7-8 22:29:40 > top of Java-index,Desktop,Core GUI APIs...