package net.xoetrope.optional.svg;
import com.tinyline.svg.ImageLoader;
import com.tinyline.svg.SVG;
import com.tinyline.svg.SVGAttr;
import com.tinyline.svg.SVGDocument;
import com.tinyline.svg.SVGFontElem;
import com.tinyline.svg.SVGImageElem;
import com.tinyline.svg.SVGParser;
import com.tinyline.svg.SVGRaster;
import com.tinyline.svg.SVGRect;
import com.tinyline.svg.SVGSVGElem;
import com.tinyline.tiny2d.TinyBitmap;
import com.tinyline.tiny2d.TinyPixbuf;
import com.tinyline.tiny2d.TinyString;
import com.tinyline.tiny2d.TinyUtil;
import com.tinyline.tiny2d.TinyVector;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Toolkit;
import java.awt.image.ColorModel;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.zip.GZIPInputStream;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import net.xoetrope.optional.svg.tinyline.PPSVGBitmap;
import net.xoetrope.optional.svg.tinyline.PlayerListener;
import net.xoetrope.optional.svg.tinyline.SVGEvent;
import net.xoetrope.optional.svg.tinyline.SVGEventQueue;
import net.xoetrope.optional.svg.w3c.dom.events.Event;
import net.xoetrope.optional.svg.w3c.dom.events.EventListener;
import net.xoetrope.optional.svg.w3c.dom.events.EventTarget;
import net.xoetrope.xui.XAttributedComponent;
import net.xoetrope.xui.XProject;
import net.xoetrope.xui.XProjectManager;
import net.xoetrope.xui.helper.XuiUtilities;
/**
* The <tt>PPSVGCanvas</tt> is the J2ME Personal Profile
* implementation of the SVG Canvas.
* <p>anit
* @author (C) Andrew Girow
* @version 1.5
* <p>
*/
public class XSvgCanvas extends JComponent implements Runnable, ImageLoader,
EventTarget, XAttributedComponent
{
/** The canvas offscreen image */
private Image bimg;
/** The canvas bounds */
private int width,height;
/** The SVG renderer */
public SVGRaster raster;
/** The SVGImageProducer implementation */
private XSVGImageProducer imageProducer;
/** The events queue */
public SVGEventQueue eventQueue;
/** The events dispatching thread */
private Thread thread;
/** The events listeners */
private TinyVector listeners;
/** The base URL */
private URL baseURL;
/** The current URL to go */
public String currentURL="";
/** The image cash */
private MediaTracker tracker;
private PlayerListener defaultListener;
private boolean loadComplete;
private XProject currentProject;
/**
* Constructs a new PPSVGCanvas instance.
*/
public XSvgCanvas()
{
currentProject = XProjectManager.getCurrentProject();
setBackground(new Color(184,200,216));
loadComplete = false;
// Makes the events queues
eventQueue = new SVGEventQueue();
listeners = new TinyVector(4);
// Makes the image cash
tracker = new MediaTracker(this);
// Adds the default events listener
defaultListener = new PlayerListener(this);
addEventListener("default", defaultListener, false);
}
/**
* Has the component finished with the input file?
* @param true if the input file is closed
*/
public boolean hasFileLoadCompleted()
{
return loadComplete;
}
/**
* Requests a repaint of the control once it has been created
*/
public void addNotify()
{
super.addNotify();
start();
}
public synchronized void setBounds( int x, int y, int w, int h )
{
super.setBounds( x, y, w, h );
if (( width != w ) && ( height != h )) {
flush();
raster = null;
width = w;
height = h;
}
if ( width > 0 )
init();
}
public synchronized void init()
{
if (( width > 0 ) && ( raster == null )) {
// Creates the SVG raster
TinyPixbuf buffer = new TinyPixbuf(width, height);
raster = new SVGRaster(buffer);
imageProducer = new XSVGImageProducer(raster);
raster.setSVGImageProducer(imageProducer);
// Sets the SVG raster size and the color model
imageProducer.setColorModel(ColorModel.getRGBdefault());
// Sets the ImageLoader implementation needed for
// loading bitmaps
SVGImageElem.setImageLoader(this);
// Uncomment the following line for full antialiasing
raster.setAntialiased(true);
if ( SVGDocument.defaultFont == null ) {
SVGDocument doc = loadSVG( getClass().getResourceAsStream("/net/xoetrope/optional/svg/tinyline/helvetica_svg"));
SVGFontElem font = SVGDocument.getFont(doc,SVG.VAL_DEFAULT_FONTFAMILY);
SVGDocument.defaultFont = font;
}
}
}
/**
* Returns a TinyBitmap for the given image URL or path.
* @param uri The image URL or path.
* @return a TinyBitmap object which gets its pixel data from
* the specified URL or path.
*/
public TinyBitmap createTinyBitmap(TinyString uri)
{
String imgRef = new String(uri.data);
PPSVGBitmap bitmap = null;
try
{
URL url = new URL(baseURL,imgRef);
// check in the cash
bitmap = new PPSVGBitmap(tracker,url);
}
catch (Exception ex)
{
}
return bitmap;
}
/**
* Creates a TinyBitmap which decodes the image stored in the specified
* byte array, and at the specified offset and length.
* @param imageData an array of bytes, representing
* image data in a supported image format.
* @param imageOffset the offset of the beginning
* of the data in the array.
* @param imageLength the length of the data in the array.
* @return a TinyBitmap object.
*/
public TinyBitmap createTinyBitmap(byte[] imageData, int imageOffset, int imageLength)
{
return new PPSVGBitmap(tracker,imageData, imageOffset, imageLength);
}
/** Starts the events dispatching thread */
public synchronized void start()
{
init();
if (( width > 0 ) && ( thread == null )) {
thread = new Thread(this);
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
}
}
/** Stops the events dispatching thread */
public synchronized void stop()
{
flush();
if ( thread != null )
thread.interrupt();
thread = null;
imageProducer = null;
tracker = null;
eventQueue = null;
removeEventListener( defaultListener );
defaultListener = null;
listeners = null;
}
/**
* The events dispatching thread run()
*/
public void run()
{
Thread currentThread = Thread.currentThread();
while (currentThread == thread) {
try {
if ( raster == null )
init();
else
eventQueue.handleEvent(eventQueue.getNextEvent());
Thread.currentThread().sleep(50);
Thread.currentThread().yield();
}
catch ( InterruptedException ie )
{
break;
}
catch (Throwable thr)
{
thr.printStackTrace();
alertError("Internal Error");
}
}
thread = null;
}
/**
* Loads and dispalys an SVG document from the given URL.
* External hyperlinks handling
*/
synchronized public void goURL(String url)
{
SVGEvent event = new SVGEvent(SVGEvent.EVENT_LOAD,url);
postEvent(event);
}
/**
* Returns the current SVGT document to its original view.
*/
public void origView()
{
SVGEvent event = new SVGEvent(SVGEvent.EVENT_ORIGVIEW, null);
postEvent(event);
}
/**
* Switches the rendering quality of this <tt>SVGPlayer</tt> .
*/
public void switchQuality()
{
SVGEvent event = new SVGEvent(SVGEvent.EVENT_QUALITY, null );
postEvent(event);
}
/**
* Suspends or unsuspends all animations that are defined
* within the current SVGT document fragment.
*/
public void pauseResumeAnimations()
{
SVGEvent event = new SVGEvent(SVGEvent.EVENT_PAUSERESUME, null );
postEvent(event);
}
/**
* This method is called when information about an image which was
* previously requested using an asynchronous interface becomes
* available. Asynchronous interfaces are method calls such as
* getWidth(ImageObserver) and drawImage(img, x, y, ImageObserver)
* which take an ImageObserver object as an argument.
*
* @param img the image being observed.
* @param flags the image status flags.
* @param x the <i>x</i> coordinate.
* @param y the <i>y</i> coordinate.
* @param width the width.
* @param height the height.
* @return <code>false</code> if the infoflags indicate that the
* image is completely loaded; <code>true</code> otherwise.
*/
public boolean imageUpdate(Image img, int flags, int x, int y, int width, int height)
{
if((flags & 0x30) != 0)
repaint(x, y, width, height);
return (flags & 0x60) == 0;
}
/**
* Paints this canvas
* @param g the specified Graphics context.
*/
public void paintComponent(Graphics g)
{
if ( !SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater( new Runnable() {
public void run()
{
repaint( 0 );
return;
}
});
}
if (( bimg == null ) && ( width > 0 )) {
if ( raster == null )
start();
raster.setCamera();
raster.update();
raster.sendPixels();
bimg = createImage( imageProducer );
}
if (bimg != null ) {
g.drawImage(bimg, 0, 0, this);
Toolkit.getDefaultToolkit().sync();// ????
}
}
/**
* The minimum size of the canvas.
*
* @return The minimum size of the canvas.
*/
public Dimension getMinimumSize()
{
return new Dimension(width, height);
}
/**
* The preferred size of the canvas.
*
* @return The preferred size of the canvas.
*/
public Dimension getPreferredSize()
{
return new Dimension(width, height);
}
/** Flushes the allocated resources. */
public void flush()
{
if ( raster != null )
raster.flush();
if ( bimg != null ) {
bimg.flush();
bimg = null;
}
}
/**
* Loads an SVGT document from the given URL.
* @param urlStr The SVGT document URL or path.
* @return An SVGT document.
*/
public SVGDocument loadSVG(String urlStr)
{
alertWait("Wait...");
loadComplete = false;
InputStream is = null;
try {
URL url = new URL(baseURL,urlStr);
baseURL = url;
is = url.openStream();
if( url.toString().endsWith("svgz"))
is = new GZIPInputStream(is);
}
catch( Exception ex)
{
alertError("Not in SVGT format");
}
SVGDocument doc = loadSVG(is);
try {
if ( is != null )
is.close();
is = null;
}
catch( Exception ex)
{
alertError("Not in SVGT format");
}
loadComplete = true;
return doc;
}
/**
* Loads an SVGT document from the given InputStream.
* @param is The InputStream.
* @return An SVGT document.
*/
public SVGDocument loadSVG(InputStream is)
{
alertWait("Wait...");
String str = "";
if ( raster == null )
start();
SVGDocument doc = raster.createSVGDocument();
try {
// Read and parse the SVGT stream
TinyPixbuf pixbuf = raster.getPixelBuffer();
// Create the SVGT attributes parser
SVGAttr attrParser = new SVGAttr(pixbuf.width, pixbuf.height);
// Create the SVGT stream parser
SVGParser parser = new SVGParser(attrParser);
// Parse the input SVGT stream parser into the document
parser.load(doc,is);
str = "www.tinyline.com";
alertInit(str);
}
catch(OutOfMemoryError memerror)
{
doc = null;
Runtime.getRuntime().gc();
alertError("Not enought memory");
}
catch(SecurityException secex)
{
doc = null;
alertError("Security violation");
}
catch( Exception ex)
{
doc = null;
alertError("Not in SVGT format");
}
catch( Throwable thr)
{
doc = null;
alertError("Not in SVGT format");
}
finally
{
try
{
if (is != null) is.close();
}
catch( IOException ioe)
{
alertError(ioe.getMessage());
}
}
return doc;
}
/** Sets the status bar implementation */
// public void setStatusBar(StatusBar aStatusBar)
// {
// //statusBar = aStatusBar;
// }
/** StatusBar: shows an alert */
public void alertError(String s)
{
// if(statusBar!=null) statusBar.alertError(s);
}
/** StatusBar: shows a wait */
public void alertWait(String s)
{
// if(statusBar!=null) statusBar.alertWait(s);
}
/** StatusBar: inits the bar */
public void alertInit(String s)
{
// if(statusBar!=null) statusBar.alertInit(s);
}
//////////////////////////////////////////////////////////////////
/**
* Posts an event to the event queue.
*
* @param theEvent an instance of Event, or a
* subclass of it.
*/
public synchronized void postEvent(SVGEvent theEvent)
{
//IMPORTANT
theEvent.eventTarget = this;
eventQueue.postEvent(theEvent);
}
/*
* Methods inherited from interface org.w3c.dom.events.EventTarget
*/
/**
*
* <b>uDOM:</b> This method allows the registration of event listeners on the event target.
*
* @param type The event type for which the user is registering
*
* @param listener The listener parameter takes an interface implemented by the
* user which contains the methods to be called when the event occurs.
*
* @param useCapture If true, useCapture indicates that the user wishes
* to initiate capture. After initiating capture, all events of the
* specified type will be dispatched to the registered EventListener
* before being dispatched to any EventTargets beneath them in the tree.
* Events which are bubbling upward through the tree will not trigger an
* EventListener designated to use capture.
*/
public void addEventListener(java.lang.String type,
EventListener listener,
boolean useCapture)
{
listeners.addElement(listener);
}
/**
*
* <b>uDOM:</b> This method allows the removal of event listeners from the event target.
*
* @param listener The listener parameter indicates the EventListener to be removed.
*/
public void removeEventListener( EventListener listener)
{
int i = listeners.indexOf(listener,0);
if(i>0)
{
listeners.removeElementAt(i);
}
}
/**
* <b>uDOM:</b> This method allows the dispatch of events into the implementations event model.
* Events dispatched in this manner will have the same behavior as events dispatched
* directly by the implementation.
*
* @param evt Specifies the event type, behavior, and contextual
* information to be used in processing the event.
* @return The return value of dispatchEvent indicates whether
* any of the listeners which handled the event called preventDefault.
* If preventDefault was called the value is false, else the value is true.
*/
public boolean dispatchEvent(Event evt)
{
if ( raster == null )
start();
EventListener h;
for(int i=0; i < listeners.count; i++)
{
h = (EventListener)listeners.data[i];
if(h!=null) h.handleEvent(evt);
}
return true;
}
/**
* Set one or more attributes of the component.
* @param attribName the name of the attribute
* @param attribValue the value of the attribute
* @return 0 for success, non zero for failure or to require some further action
*/
public int setAttribute( String attribName, Object attribValue )
{
String attribNameLwr = attribName.toLowerCase();
String attribValueStr = (String)attribValue;
if ( attribNameLwr.equals( "content" )) {
String fileName = attribValueStr + ( attribValueStr.toLowerCase().endsWith( ".svgz" ) ? "" : ".svgz" );
URL resourceUrl = currentProject.findResource( fileName );
try {
InputStream is = resourceUrl.openStream();
is.read();
is.close();
}
catch ( Throwable t )
{
System.out.println( "File does not exist: " + attribValue );
return 0;
}
if ( resourceUrl != null ) {
String urlStr = resourceUrl.toExternalForm();
goURL( urlStr );//"file:/C:/Xui/Tests/NB5_20/resources/tgirl.svg");
if ( isVisible() && isShowing())
start();
}
}
else if ( attribNameLwr.equals( "tooltip" ))
setToolTipText( XuiUtilities.translate( currentProject, (String)attribValue ));
return 0;
}
public void setVisible( boolean state )
{
super.setVisible( state );
if ( state )
start();
}
/**
* Pan/scroll the image to toa new location
* @param x the number of pixels to move horizontally, moved the display viewport
* @param y the number of pixels to move vertically, moved the display viewport
*/
public void pan( int x, int y )
{
// Get the current viewport
SVGRect view = raster.view;
// Get the SVGT document
SVGDocument doc = raster.getSVGDocument();
// Get the root of the SVGT document
SVGSVGElem root = (SVGSVGElem)doc.root;
// Get the current scale value
int scale = root.getCurrentScale();
// Scale pan distances according to the current scale factor
// Change the current viewport
view.x += TinyUtil.div(x<<TinyUtil.FIX_BITS,scale);
view.y += TinyUtil.div(y<<TinyUtil.FIX_BITS,scale);
updateImage();
}
/**
* Zoom in on the image
* param scaleFactor the factor by which to adjust the scaling. Greater than 1.0
* for zooming in and less than 1.0 for zooming out.
*/
public void zoom( double scaleFactor )
{
// Get the current viewport
SVGRect view = raster.view;
// Get the SVGT document
// SVGDocument doc = raster.getSVGDocument();
// Get the root of the SVGT document
// SVGSVGElem root = (SVGSVGElem)doc.root;
// Get the current scale value
//int scale = root.getCurrentScale();
view.width /= scaleFactor;
view.height /= scaleFactor;
updateImage();
}
/**
* Reset the image to its original size and position
*/
public void reset()
{
SVGRect view = raster.view;
view.x = raster.origview.x;
view.y = raster.origview.y;
view.width = raster.origview.width;
view.height = raster.origview.height;
updateImage();
}
/**
* Update the raster image
*/
protected void updateImage()
{
// Change the camera transform according to the new current
// viewport and update the raster
raster.setCamera(); // call recalculate the current 'camera' transform for the SVGRaster object.
raster.update(); // call invokes the rendering pipeline.
raster.sendPixels(); // call notifies the SVGImageProducer about new pixels.
}
}