package eu.irreality.age.swing;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.StyleConstants;
import eu.irreality.age.ColoredSwingClient;
/**
* Timer for smooth scrolling a JScrollPane to the bottom.
* @author carlos
*
*/
public class SmoothScrollTimer extends Timer
{
/** Client containing the text pane that we are going to scroll.*/
private ColoredSwingClient cl;
public static final int FIXED_SPEED_MODE = 0;
public static final int ADAPTIVE_SPEED_MODE = 1;
/** Whether the speed is constant or adaptive.*/
private int movementMode = FIXED_SPEED_MODE;
/** Number of pixels that we scroll per frame of the timer, if in fixed speed mode. In adaptive speed mode, this can be multiplied by a factor.*/
private int pixelsPerFrame = 2;
public int getMovementMode()
{
return movementMode;
}
/**
* Sets the movement mode: FIXED_SPEED_MODE for fixed speed, ADAPTIVE_SPEED_MODE for speed that varies according to
* distance to the scrolling goal.
* @param movementMode
*/
public void setMovementMode ( int movementMode )
{
this.movementMode = movementMode;
}
/**
* Obtains the movement mode: FIXED_SPEED_MODE for fixed speed, ADAPTIVE_SPEED_MODE for speed that varies according to
* distance to the scrolling goal.
* @return
*/
public int getPixelsPerFrame()
{
return pixelsPerFrame;
}
public void setPixelsPerFrame ( int pixelsPerFrame )
{
this.pixelsPerFrame = pixelsPerFrame;
}
/**
* Sets the speed of scrolling in pixels per second, leaving the frame unchanged but changing pixelsPerFrame.
* @param pixelsPerSecond
*/
public void setSpeed ( int pixelsPerSecond )
{
int fps = 1000/this.getDelay();
pixelsPerFrame = pixelsPerSecond / fps;
}
/**
* Gets the speed of scrolling in pixels per second.
*/
public int getSpeed ( )
{
int fps = 1000/this.getDelay();
return pixelsPerFrame * fps;
}
/*Returns height of a line in pixels in the client.*/
private int getLineHeight()
{
MutableAttributeSet atributos = cl.getTextAttributes();
String fontFamily = StyleConstants.getFontFamily(atributos);
int fontSize = StyleConstants.getFontSize(atributos);
Font font = new Font(fontFamily,Font.PLAIN,fontSize);
return cl.getTextArea().getGraphics().getFontMetrics(font).getHeight();
}
private void doSetLinesPerSecond ( double linesPerSecond )
{
int lineHeight = getLineHeight();
int candidateSpeed = (int) ( linesPerSecond * lineHeight );
if ( candidateSpeed > 0 )
setSpeed(candidateSpeed);
else
setSpeed(1);
}
/**
* Sets the speed of scrolling in lines per second, leaving the frame unchanged but changing pixelsPerFrame.
* Executes automatically in dispatch thread because it queries components.
* @param pixelsPerSecond
*/
public void setLinesPerSecond ( final double linesPerSecond )
{
ColoredSwingClient.execInDispatchThread( new Runnable()
{
public void run()
{
doSetLinesPerSecond(linesPerSecond);
}
}
);
}
/**
* Gets the speed of scrolling in lines per second.
* @param pixelsPerSecond
*/
//yeah, but how do we get the return value from invokeLater?
private double doGetLinesPerSecond ( )
{
int lineHeight = getLineHeight();
return ((double)getSpeed()/(double)lineHeight);
}
public int calculateSpeed ( ActionEvent evt , ColoredSwingClient cl )
{
JScrollPane elScrolling = cl.getScrollPane();
JScrollBar vbar = (JScrollBar) elScrolling.getVerticalScrollBar();
int distance = vbar.getMaximum() - (vbar.getValue() + vbar.getVisibleAmount());
double speedFactor = ((double)distance)/40;
int theSpeed = (int) Math.round(pixelsPerFrame * speedFactor);
if ( theSpeed < 1 ) return 1;
else return theSpeed;
}
public SmoothScrollTimer( final int millis , final ColoredSwingClient cl )
{
super( millis , null );
this.cl = cl;
final JScrollPane elScrolling = cl.getScrollPane();
final JTextPane elAreaTexto = cl.getTextArea();
Action smoothScrollAction = new AbstractAction()
{
public void actionPerformed ( ActionEvent evt )
{
//runs on the EDT
if ( cl.scrollIsAtBottom() )
{
//stop scrolling: we're already at bottom
SmoothScrollTimer.this.stop();
return;
}
Point p = elScrolling.getViewport().getViewPosition();
int offset = 1;
if ( movementMode == FIXED_SPEED_MODE )
offset = pixelsPerFrame;
else if ( movementMode == ADAPTIVE_SPEED_MODE )
offset = calculateSpeed ( evt , cl );
p.y = p.y+offset;
elScrolling.getViewport().setViewPosition(p);
//elAreaTexto.setVisible(true);
elAreaTexto.repaint();
//elAreaTexto.revalidate();
}
};
this.addActionListener(smoothScrollAction);
}
}