package net.xoetrope.optional.svg.svgsalamander;
import com.kitfox.svg.SVGDiagram;
import com.kitfox.svg.SVGElement;
import com.kitfox.svg.SVGRoot;
import com.kitfox.svg.SVGUniverse;
import com.kitfox.svg.animation.AnimationElement;
import com.kitfox.svg.app.PlayerThread;
import com.kitfox.svg.app.PlayerThreadListener;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.Rectangle2D;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Vector;
import javax.swing.JComponent;
import net.xoetrope.debug.DebugLogger;
import net.xoetrope.optional.svg.HitTester;
import net.xoetrope.optional.svg.XSvgStateHelper;
import net.xoetrope.optional.svg.XSvgStateHelperFactory;
import net.xoetrope.xml.XmlElement;
import net.xoetrope.xml.XmlSource;
import net.xoetrope.xui.build.BuildProperties;
import org.jdesktop.swingx.painter.Painter;
/**
* A painter that renders an SVG image
* <p>Copyright (c) Xoetrope 2001-2006, see license.txt for more details</p>
*/
public class XSvgPainter implements Painter, HitTester, PlayerThreadListener
{
protected SVGDiagram diagram;
protected int oldWidth, oldHeight;
protected Color bkColor;
protected double scaleX, scaleY;
private JComponent component;
// private static HashMap painters = new HashMap();
private boolean scaled;
private Vector rescaleElements;
private Rectangle2D viewBox;
private Insets insets;
private XSvgStateHelper helper;
private XmlElement metadata;
private PlayerThread animationThread;
/**
* Creates a new instance of XSvgPainter
* @param comp the target component
*/
public XSvgPainter( JComponent comp )
{
component = comp;
helper = XSvgStateHelperFactory.getXSvgStateHelper( comp );
}
/**
* Get the ID of the selected element
* @param returns a comma separated list of ids
*/
public String getSelectedId()
{
if ( helper == null )
return "";
return helper.getSelectedId();
}
/**
* Flag this painter as an animation painter
* @param state true to animate
*/
public void setAnimate( boolean state )
{
if ( state ) {
animationThread = new PlayerThread();
animationThread.addListener( this );
}
else
animationThread = null;
}
public void play()
{
animationThread.setPlayState( PlayerThread.PS_PLAY_FWD );
animationThread.setTimeStep( 1.0 / 50.0 );
animationThread.setCurTime( 0 );
}
public double getCurTime()
{
if ( animationThread != null )
return animationThread.getCurTime();
return 0;
}
public void updateTime( double curTime, double timeStep, int playState )
{
try {
if ( playState == PlayerThread.PS_STOP )
return;
SVGUniverse universe = diagram.getUniverse();
if ( universe != null ) {
universe.setCurTime( curTime );
universe.updateTime();
component.repaint();
}
}
catch ( Exception e )
{
e.printStackTrace();
}
}
/**
* Set the element id for elements like masks etc
* @param element the element type/role
* @param id the SVG element id to use
*/
public void setElementIds( String[][] ids )
{
helper.setElementIds( ids );
helper.getStates( diagram );
}
/**
* Set the metadata URL
* @param mdURL the host component
*/
public void setMetadata( URL mdURL )
{
try {
metadata = XmlSource.read( new BufferedReader( new InputStreamReader( mdURL.openStream())));
}
catch ( IOException ex ) {
ex.printStackTrace();
}
}
/**
* Set the image managed and rendered by this painter
* @param imageURL the url of the svg
*/
public void setImage( URL imageURL )
{
setImage( imageURL, false );
}
/**
* Set the image managed and rendered by this painter
* @param imageURL the url of the svg
* @param clear true to clear the svg universe, if for instance the svg
* image has changed
*/
public void setImage( URL imageURL, boolean clear )
{
try {
SVGUniverse universe = new SVGUniverse();
if ( clear )
universe.clear();
if ( BuildProperties.DEBUG )
DebugLogger.logWarning( "Loading: " + imageURL.toExternalForm());
diagram = universe.getDiagram( new URI( imageURL.toExternalForm()));
}
catch ( URISyntaxException ex )
{
ex.printStackTrace();
}
}
// public void setImage( URL imageURL, int w, int h )
// {
// this.component = component;
// if ( imageURL == null )
// return;
//
//// theImage = imageURL;
// String key = imageURL.toString();// + "_" + w + "_" + h;
// diagram = (SVGDiagram)painters.get( key );
// if ( diagram == null ) {
// try {
//// diagram = (SVGDiagram)painters.get( imageURL.toString() );
//// if ( diagram == null ) {
// SVGUniverse universe = new SVGUniverse();
// //universe.clear();
// diagram = universe.getDiagram( new URI( imageURL.toExternalForm()));
// setup();
// painters.put( key, diagram );
//// }
//// else clone the existing image
// }
// catch ( URISyntaxException ex )
// {
// ex.printStackTrace();
// }
// }
// }
public void paint(Graphics2D g, Object object, int width, int height)
{
paint( g, (JComponent)object, width, height );
}
/**
* <p>Paints on the given Graphics2D object some effect which may or may not
* be related to the given component. For example, BackgroundPainter will
* use the background property of the component and the width/height of the
* component to perform a fill rect. Most other Painters will disregard the
* component entirely, except to get the component width/height.</p>
*
* <p>The Graphics2D object must be returned to the same state it started
* at by the end of the method. For example, if "setColor(c)" was called
* on the graphics object, it should be reset to the original color before
* the method returns.</p>
*
*
* @param g The Graphics2D object in which to paint
* @param component The JComponent that the Painter is delegate for. This
* must not be null.
* @param width
* @param height
*/
public void paint( Graphics2D g, JComponent component, int width, int height )
{
Object oldHint = g.getRenderingHint( RenderingHints.KEY_ANTIALIASING );
g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
// Force the initial resizing
if ( !scaled )
componentResized();
try {
if ( bkColor != null ) {
g.setColor( bkColor );
g.fillRect( 0, 0, width, height );
}
//setImage( theImage, width, height );
if ( diagram != null ) {
SVGRoot root = diagram.getRoot();
if (( oldWidth != width ) || ( oldHeight != height )) {
if ( root != null ) {
root.setAttribute( "width", AnimationElement.AT_XML, Double.toString( width ));
root.setAttribute( "height", AnimationElement.AT_XML, Double.toString( height ));
root.build();
//diagram.setDeviceViewport( new Rectangle( 0,0,width, height));
oldWidth = width;
oldHeight = height;
}
try {
double bbox[] = root.getPresAbsolute( "viewBox" ).getDoubleList();
scaleX = bbox[ 2 ] / width;//root.getPresAbsolute( "width" ).getDoubleValue();
scaleY = bbox[ 3 ] / height;//root.getPresAbsolute( "height" ).getDoubleValue();
} catch ( Exception e ) {}
}
if ( root != null ) {
if ( insets != null )
diagram.setDeviceViewport( new Rectangle( insets.left, insets.top,
width - ( insets.left + insets.right ), height - ( insets.top + insets.bottom )));
diagram.render( g );
}
}
}
catch ( Exception ex ) {
ex.printStackTrace();
}
g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, oldHint );
}
public void setBackground( Color c )
{
bkColor = c;
}
public boolean contains( int x, int y )
{
return helper.contains( scaleX * x, scaleY * y );
}
/**
* Update the svg state to be consistent with the button's state
* @param itemIndex the item index or -1 to update all items
* @return true if the state has changed
*/
public boolean updateState( int itemIndex )
{
return helper.updateState( itemIndex );
}
/**
* Get the managed states in the svg diagram. The diagram may include two
* types of managed elements. The first is managed by a helper class
* implementing the XSvgStateHelper interface, and the second manages the
* scaling of the component. The svg's metadata section may include a list of
* elements to scale, and these are managed directly by this painter.
*/
public void setup()
{
if ( diagram == null )
return;
if ( helper == null )
helper = XSvgStateHelperFactory.getXSvgStateHelper( component );
helper.getStates( diagram );
// Prepare for rescaling
viewBox = diagram.getViewRect();
rescaleElements = new Vector();
SVGElement svgMetadata = diagram.getElement( "metadata" );
if ( svgMetadata != null ) {
String modlist = svgMetadata.getPresAbsolute( "modlist" ).getStringValue();
String[] objectIds = modlist.split( ";" );
for ( int i = 0; i < objectIds.length; i++ ) {
RescaleElement re = new RescaleElement();
re.element = diagram.getElement( objectIds[ i ] );
String sid = re.element.getId();
int pos;
double r = 5.0;
double s = 4.0;
if (( pos = sid.indexOf( '_' )) > 0 ) {
int lastIdx = sid.lastIndexOf( '_' );
if ( lastIdx > pos ) {
re.radius = (double)Integer.parseInt( sid.substring( pos + 1, lastIdx ));
re.stroke = (double)Integer.parseInt( sid.substring( lastIdx + 1 ));
}
else
re.radius = (double)Integer.parseInt( sid.substring( pos + 1 ));
}
rescaleElements.add( re );
}
}
if ( metadata != null ) {
Vector children = metadata.getChildren();
int numChildren = children.size();
for ( int i = 0; i < numChildren; i++ ) {
XmlElement child = (XmlElement)children.elementAt( i );
String tag = child.getName();
String id = child.getAttribute( "id" );
RescaleElement re = new RescaleElement();
re.element = diagram.getElement( id );
if ( re.element == null )
continue;
String attr = child.getAttribute( "x" );
if ( attr != null )
re.x = new Double( attr ).doubleValue();
attr = child.getAttribute( "y" );
if ( attr != null )
re.y = new Double( attr ).doubleValue();
attr = child.getAttribute( "w" );
if ( attr != null )
re.w = new Double( attr ).doubleValue();
attr = child.getAttribute( "h" );
if ( attr != null )
re.h = new Double( attr ).doubleValue();
attr = child.getAttribute( "radius" );
if ( attr != null )
re.radius = new Double( attr ).doubleValue();
attr = child.getAttribute( "stroke" );
if ( attr != null )
re.stroke = new Double( attr ).doubleValue();
attr = child.getAttribute( "ow" );
if ( attr != null )
re.origW = new Double( attr ).doubleValue();
attr = child.getAttribute( "oh" );
if ( attr != null )
re.origH = new Double( attr ).doubleValue();
rescaleElements.add( re );
}
}
}
/**
* The component has been resize, so resize the svg
*/
public void componentResized()
{
try {
if ( rescaleElements != null ) {
Dimension size = component.getSize();
double vw = viewBox.getWidth();
double vh = viewBox.getHeight();
double ww = size.width;
double wh = size.height;
int numElements = rescaleElements.size();
for ( int i = 0; i < numElements; i++ ) {
RescaleElement re = (RescaleElement)rescaleElements.get( i );
if (( component != null ) && ( diagram != null )) {
if ( re.radius > 0.0 ) {
double rx = re.radius * vw / ww;
double ry = re.radius * vh / wh;
re.element.setAttribute( "rx", AnimationElement.AT_XML, Double.toString( rx ));
re.element.setAttribute( "ry", AnimationElement.AT_XML, Double.toString( ry ));
scaled = true;
}
if ( re.stroke > 0.0 ) {
double vd = Math.sqrt( vh * vh + vw * vw );
double wd = Math.sqrt( wh * wh + ww * ww );
re.element.setAttribute( "stroke-width", AnimationElement.AT_XML, Double.toString( re.stroke * vd / wd ));
re.element.updateTime( 0.0 );
scaled = true;
}
double dx, dy, dw, dh;
dx = dy = 0.0;
dw = dh = 1.0;
if ( re.x != 0.0 ) {
if ( re.x > 1.0 )
dx = ( re.x / ww ) * vw;
else if ( re.x < 0.0 )
dx = (( ww + re.x ) / ww ) * vw;
else
dx = re.x * vw;
}
if ( re.y != 0.0 ) {
if ( re.y > 1.0 )
dy = ( re.y / wh ) * vh;
else if ( re.y < 0.0 )
dy = (( wh + re.y ) / wh ) * vh;
else
dy = re.y * vh;
}
if ( re.w != 0.0 ) {
dw = vw / ww; // Preserve the original size
if ( re.w > 1.0 )
dw *= re.w / re.origW;
else
dw *= ( re.w * ww ) / re.origW;
}
if ( re.h != 0.0 ) {
dh = vh / wh; // Preserve the original size
if ( re.h > 1.0 )
dh *= re.h / re.origH;
else
dh *= ( re.h * wh ) / re.origH;
}
String transform = "translate(" + dx + "," + dy + ") scale(" + dw + "," + dh + ")";
re.element.setAttribute( "transform", AnimationElement.AT_XML, transform );
re.element.updateTime( 0.0 );
scaled = true;
}
if ( scaled )
oldWidth = oldHeight = 0;
}
}
}
catch ( Exception ex )
{
ex.printStackTrace();
}
}
public SVGElement getElement( String id )
{
return diagram.getElement( id );
}
class RescaleElement
{
double x, y, w, h, origW, origH, radius, stroke;
SVGElement element;
}
public void setInsets(Insets insets) {
this.insets = insets;
}
}