/*

 ============================================================================
                   The Apache Software License, Version 1.1
 ============================================================================

 Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.

 Redistribution and use in source and binary forms, with or without modifica-
 tion, are permitted provided that the following conditions are met:

 1. Redistributions of  source code must  retain the above copyright  notice,
    this list of conditions and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.

 3. The end-user documentation included with the redistribution, if any, must
    include  the following  acknowledgment:  "This product includes  software
    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
    Alternately, this  acknowledgment may  appear in the software itself,  if
    and wherever such third-party acknowledgments normally appear.

 4. The names "Batik" and  "Apache Software Foundation" must  not  be
    used to  endorse or promote  products derived from  this software without
    prior written permission. For written permission, please contact
    apache@apache.org.

 5. Products  derived from this software may not  be called "Apache", nor may
    "Apache" appear  in their name,  without prior written permission  of the
    Apache Software Foundation.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
 APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
 INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
 DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
 OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
 ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
 (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 This software  consists of voluntary contributions made  by many individuals
 on  behalf of the Apache Software  Foundation. For more  information on the
 Apache Software Foundation, please see <http://www.apache.org/>.

*/

package org.apache.batik.apps.svgbrowser;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Event;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.awt.print.PrinterException;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Vector;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.JWindow;
import javax.swing.KeyStroke;
import javax.swing.filechooser.FileFilter;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;

import org.apache.batik.bridge.DefaultExternalResourceSecurity;
import org.apache.batik.bridge.DefaultScriptSecurity;
import org.apache.batik.bridge.EmbededExternalResourceSecurity;
import org.apache.batik.bridge.EmbededScriptSecurity;
import org.apache.batik.bridge.ExternalResourceSecurity;
import org.apache.batik.bridge.NoLoadExternalResourceSecurity;
import org.apache.batik.bridge.NoLoadScriptSecurity;
import org.apache.batik.bridge.RelaxedExternalResourceSecurity;
import org.apache.batik.bridge.RelaxedScriptSecurity;
import org.apache.batik.bridge.ScriptSecurity;
import org.apache.batik.bridge.UpdateManagerEvent;
import org.apache.batik.bridge.UpdateManagerListener;
import org.apache.batik.dom.StyleSheetProcessingInstruction;
import org.apache.batik.dom.svg.SVGOMDocument;
import org.apache.batik.dom.util.HashTable;
import org.apache.batik.ext.swing.JAffineTransformChooser;
import org.apache.batik.swing.JSVGCanvas;
import org.apache.batik.swing.gvt.GVTTreeRendererEvent;
import org.apache.batik.swing.gvt.GVTTreeRendererListener;
import org.apache.batik.swing.svg.GVTTreeBuilderEvent;
import org.apache.batik.swing.svg.GVTTreeBuilderListener;
import org.apache.batik.swing.svg.LinkActivationEvent;
import org.apache.batik.swing.svg.LinkActivationListener;
import org.apache.batik.swing.svg.SVGDocumentLoaderEvent;
import org.apache.batik.swing.svg.SVGDocumentLoaderListener;
import org.apache.batik.swing.svg.SVGFileFilter;
import org.apache.batik.swing.svg.SVGLoadEventDispatcherEvent;
import org.apache.batik.swing.svg.SVGLoadEventDispatcherListener;
import org.apache.batik.swing.svg.SVGUserAgent;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.ImageTranscoder;
import org.apache.batik.transcoder.image.JPEGTranscoder;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.apache.batik.transcoder.image.TIFFTranscoder;
import org.apache.batik.transcoder.print.PrintTranscoder;
import org.apache.batik.util.ParsedURL;
import org.apache.batik.util.Service;
import org.apache.batik.util.gui.DOMViewer;
import org.apache.batik.util.gui.JErrorPane;
import org.apache.batik.util.gui.LocationBar;
import org.apache.batik.util.gui.MemoryMonitor;
import org.apache.batik.util.gui.URIChooser;
import org.apache.batik.util.gui.resource.ActionMap;
import org.apache.batik.util.gui.resource.JComponentModifier;
import org.apache.batik.util.gui.resource.MenuFactory;
import org.apache.batik.util.gui.resource.MissingListenerException;
import org.apache.batik.util.gui.resource.ResourceManager;
import org.apache.batik.util.gui.resource.ToolBarFactory;
import org.apache.batik.xml.XMLUtilities;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextListener;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.css.ViewCSS;
import org.w3c.dom.svg.SVGDocument;

/**
 * This class represents a SVG viewer swing frame.
 *
 * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
 * @version $Id: JSVGViewerFrame.java,v 1.101 2003/09/16 01:12:52 deweese Exp $
 */
public class JSVGViewerFrame
    extends    JFrame
    implements ActionMap,
               SVGDocumentLoaderListener,
               GVTTreeBuilderListener,
               SVGLoadEventDispatcherListener,
               GVTTreeRendererListener,
               LinkActivationListener,
               UpdateManagerListener {

    /**
     * Kind of ugly, but we need to know if we are running before
     * or after 1.4...
     */
    protected static boolean priorJDK1_4 = true;

    /**
     * If the following class can be found (it appeared in JDK 1.4),
     * then we know we are post JDK 1.4.
     */
    protected static final String JDK_1_4_PRESENCE_TEST_CLASS
        = "java.util.logging.LoggingPermission";

    static {
        Class cl = null;
        try {
            cl = Class.forName(JDK_1_4_PRESENCE_TEST_CLASS);
        } catch (ClassNotFoundException e){
        }

        if (cl != null) {
            priorJDK1_4 = false;
        }
    }

    static JFrame debuggerFrame = null;
    static Class  debuggerClass = null;
    static Method clearAllBreakpoints = null;
    static Method scriptGo = null;
    static Method setExitAction = null;
    static {
        try {
            debuggerClass = JSVGViewerFrame.class.getClassLoader().loadClass
                ("org.mozilla.javascript.tools.debugger.Main");
            clearAllBreakpoints = debuggerClass.getMethod
                ("clearAllBreakpoints", null);
            scriptGo = debuggerClass.getMethod
                ("go", null);
            setExitAction = debuggerClass.getMethod
                ("setExitAction", new Class[] {Runnable.class});
        } catch (ThreadDeath td) {
            debuggerClass = null;
            clearAllBreakpoints = null;
            scriptGo = null;
            setExitAction = null;
            throw td;
        } catch (Throwable t) {
            debuggerClass = null;
            clearAllBreakpoints = null;
            scriptGo = null;
            setExitAction = null;
        }
    }

    public static void showDebugger() {
        if (debuggerClass == null) return;
        if (debuggerFrame == null) {
            try {
                Constructor c = debuggerClass.getConstructor
                    (new Class [] { String.class });
                debuggerFrame = (JFrame)c.newInstance
                    (new Object[] { "Rhino JavaScript Debugger" });
                // Customize the menubar a bit, disable menu
                // items that can't be used and change 'Exit' to 'Close'.
                JMenuBar menuBar = debuggerFrame.getJMenuBar();
                JMenu    menu    = menuBar.getMenu(0);
                menu.getItem(0).setEnabled(false); // Open...
                menu.getItem(1).setEnabled(false); // Run...
                menu.getItem(3).setText
                    (Resources.getString("Close.text")); // Exit -> "Close"
                menu.getItem(3).setAccelerator
                    (KeyStroke.getKeyStroke(KeyEvent.VK_W, Event.CTRL_MASK));

                debuggerFrame.setSize(600, 460);
                debuggerFrame.pack();
                WindowAdapter wa = new WindowAdapter() {
                        public void windowClosing(WindowEvent e) {
                            hideDebugger();
                        }};
                setExitAction.invoke(debuggerFrame, 
                                     new Object [] { new Runnable() {
                                             public void run() {
                                                 hideDebugger();
                                             }}});
                debuggerFrame.addWindowListener(wa);
            } catch (Exception ex) {
                ex.printStackTrace();
                return;
            }
        }
        if (debuggerFrame != null) {
            debuggerFrame.setVisible(true);
            Context.addContextListener((ContextListener)debuggerFrame);
        }
    }

    public static void hideDebugger() {
        if (debuggerFrame == null)
            return;
        Context.removeContextListener((ContextListener)debuggerFrame);
        debuggerFrame.setVisible(false);
        try {
            clearAllBreakpoints.invoke(debuggerFrame, null);
            scriptGo.invoke(debuggerFrame, null);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * The gui resources file name
     */
    public final static String RESOURCES =
        "org.apache.batik.apps.svgbrowser.resources.GUI";

    // The actions names.
    public final static String ABOUT_ACTION = "AboutAction";
    public final static String OPEN_ACTION = "OpenAction";
    public final static String OPEN_LOCATION_ACTION = "OpenLocationAction";
    public final static String NEW_WINDOW_ACTION = "NewWindowAction";
    public final static String RELOAD_ACTION = "ReloadAction";
    public final static String BACK_ACTION = "BackAction";
    public final static String FORWARD_ACTION = "ForwardAction";
    public final static String FULL_SCREEN_ACTION = "FullScreenAction";
    public final static String PRINT_ACTION = "PrintAction";
    public final static String EXPORT_AS_JPG_ACTION = "ExportAsJPGAction";
    public final static String EXPORT_AS_PNG_ACTION = "ExportAsPNGAction";
    public final static String EXPORT_AS_TIFF_ACTION = "ExportAsTIFFAction";
    public final static String PREFERENCES_ACTION = "PreferencesAction";
    public final static String CLOSE_ACTION = "CloseAction";
    public final static String VIEW_SOURCE_ACTION = "ViewSourceAction";
    public final static String EXIT_ACTION = "ExitAction";
    public final static String RESET_TRANSFORM_ACTION = "ResetTransformAction";
    public final static String ZOOM_IN_ACTION = "ZoomInAction";
    public final static String ZOOM_OUT_ACTION = "ZoomOutAction";
    public final static String PREVIOUS_TRANSFORM_ACTION = "PreviousTransformAction";
    public final static String NEXT_TRANSFORM_ACTION = "NextTransformAction";
    public final static String USE_STYLESHEET_ACTION = "UseStylesheetAction";
    public final static String PLAY_ACTION = "PlayAction";
    public final static String PAUSE_ACTION = "PauseAction";
    public final static String STOP_ACTION = "StopAction";
    public final static String MONITOR_ACTION = "MonitorAction";
    public final static String DOM_VIEWER_ACTION = "DOMViewerAction";
    public final static String SET_TRANSFORM_ACTION = "SetTransformAction";
    public final static String FIND_DIALOG_ACTION = "FindDialogAction";
    public final static String THUMBNAIL_DIALOG_ACTION = "ThumbnailDialogAction";
    public final static String FLUSH_ACTION = "FlushAction";
    public final static String TOGGLE_DEBUGGER_ACTION = "ToggleDebuggerAction";

    /**
     * The cursor indicating that an operation is pending.
     */
    public final static Cursor WAIT_CURSOR =
        new Cursor(Cursor.WAIT_CURSOR);

    /**
     * The default cursor.
     */
    public final static Cursor DEFAULT_CURSOR =
        new Cursor(Cursor.DEFAULT_CURSOR);

    /**
     * Name for the os-name property
     */
    public final static String PROPERTY_OS_NAME 
        = Resources.getString("JSVGViewerFrame.property.os.name");

    /**
     * Name for the os.name default
     */
    public final static String PROPERTY_OS_NAME_DEFAULT 
        = Resources.getString("JSVGViewerFrame.property.os.name.default");

    /**
     * Name for the os.name property prefix we are looking
     * for in OpenAction to work around JFileChooser bug
     */
    public final static String PROPERTY_OS_WINDOWS_PREFIX 
        = Resources.getString("JSVGViewerFrame.property.os.windows.prefix");

    /**
     * The input handlers
     */
    protected static Vector handlers;

    /**
     * The default input handler
     */
    protected static SquiggleInputHandler defaultHandler = new SVGInputHandler();

    /**
     * The resource bundle
     */
    protected static ResourceBundle bundle;

    /**
     * The resource manager
     */
    protected static ResourceManager resources;
    static {
        bundle = ResourceBundle.getBundle(RESOURCES, Locale.getDefault());
        resources = new ResourceManager(bundle);
    }

    /**
     * The current application.
     */
    protected Application application;

    /**
     * The JSVGCanvas.
     */
    protected JSVGCanvas svgCanvas;

    /**
     * The panel where the svgCanvas is displayed
     */
    protected JPanel svgCanvasPanel;

    /**
     * A window used for full screen display
     */
    protected JWindow window;

    /**
     * The memory monitor frame.
     */
    protected static JFrame memoryMonitorFrame;

    /**
     * The current path.
     */
    protected File currentPath = new File("");

    /**
     * The current export path.
     */
    protected File currentExportPath = new File("");

    /**
     * The back action
     */
    protected BackAction backAction = new BackAction();

    /**
     * The forward action
     */
    protected ForwardAction forwardAction = new ForwardAction();

    /**
     * The play action
     */
    protected PlayAction playAction = new PlayAction();

    /**
     * The pause action
     */
    protected PauseAction pauseAction = new PauseAction();

    /**
     * The stop action
     */
    protected StopAction stopAction = new StopAction();

    /**
     * The previous transform action
     */
    protected PreviousTransformAction previousTransformAction =
        new PreviousTransformAction();

    /**
     * The next transform action
     */
    protected NextTransformAction nextTransformAction =
        new NextTransformAction();

    /**
     * The use (author) stylesheet action
     */
    protected UseStylesheetAction useStylesheetAction =
        new UseStylesheetAction();

    /**
     * The debug flag.
     */
    protected boolean debug;

    /**
     * The auto adjust flag.
     */
    protected boolean autoAdjust = true;

    /**
     * Whether the update manager was stopped.
     */
    protected boolean managerStopped;

    /**
     * The SVG user agent.
     */
    protected SVGUserAgent userAgent = new UserAgent();

    /**
     * The current document.
     */
    protected SVGDocument svgDocument;

    /**
     * The URI chooser.
     */
    protected URIChooser uriChooser;

    /**
     * The DOM viewer.
     */
    protected DOMViewer domViewer;

    /**
     * The Find dialog.
     */
    protected FindDialog findDialog;

    /**
     * The Find dialog.
     */
    protected ThumbnailDialog thumbnailDialog;

    /**
     * The transform dialog
     */
    protected JAffineTransformChooser.Dialog transformDialog;

    /**
     * The location bar.
     */
    protected LocationBar locationBar;

    /**
     * The status bar.
     */
    protected StatusBar statusBar;

    /**
     * The initial frame title.
     */
    protected String title;

    /**
     * The local history.
     */
    protected LocalHistory localHistory;

    /**
     * The transform history.
     */
    protected TransformHistory transformHistory = new TransformHistory();

    /**
     * The alternate style-sheet title.
     */
    protected String alternateStyleSheet;

    /**
     * Creates a new SVG viewer frame.
     */
    public JSVGViewerFrame(Application app) {
        application = app;

        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                application.closeJSVGViewerFrame(JSVGViewerFrame.this);
            }
        });

        //
        // Set the frame's maximum size so that content
        // bigger than the screen does not cause the creation
        // of unnecessary large images.
        //
        svgCanvas = new JSVGCanvas(userAgent, true, true){
                Dimension screenSize;
                
                {
                    screenSize = Toolkit.getDefaultToolkit().getScreenSize();
                    setMaximumSize(screenSize);
                }
                
                public Dimension getPreferredSize(){
                    Dimension s = super.getPreferredSize();
                    if (s.width > screenSize.width) s.width =screenSize.width;
                    if (s.height > screenSize.height) s.height = screenSize.height;
                    return s;
                }

                
                /**
                 * This method is called when the component knows the desired
                 * size of the window (based on width/height of outermost SVG
                 * element). We override it to immediately pack this frame.
                 */
                public void setMySize(Dimension d) {
                    setPreferredSize(d);
                    invalidate();
                    if (JSVGViewerFrame.this.autoAdjust) {
                        JSVGViewerFrame.this.pack();
                    }
                }

                public void setDisableInteractions(boolean b) {
                    super.setDisableInteractions(b);

                    // Disable/Enable all our different ways to adjust the
                    // rendering transform (menus, toolbar, thumbnail, keyboard).

                    ((Action)listeners.get(SET_TRANSFORM_ACTION)) .setEnabled(!b);

                    if (thumbnailDialog != null)
                        thumbnailDialog.setInteractionEnabled(!b);
                }
            };
        
        javax.swing.ActionMap map = svgCanvas.getActionMap();
        map.put(FULL_SCREEN_ACTION, new FullScreenAction());
        javax.swing.InputMap imap = svgCanvas.getInputMap(JComponent.WHEN_FOCUSED);
        KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_F11, 0);
        imap.put(key, FULL_SCREEN_ACTION);

        svgCanvas.setDoubleBufferedRendering(true);

        listeners.put(ABOUT_ACTION, new AboutAction());
        listeners.put(OPEN_ACTION, new OpenAction());
        listeners.put(OPEN_LOCATION_ACTION, new OpenLocationAction());
        listeners.put(NEW_WINDOW_ACTION, new NewWindowAction());
        listeners.put(RELOAD_ACTION, new ReloadAction());
        listeners.put(BACK_ACTION, backAction);
        listeners.put(FORWARD_ACTION, forwardAction);
        listeners.put(PRINT_ACTION, new PrintAction());
        listeners.put(EXPORT_AS_JPG_ACTION, new ExportAsJPGAction());
        listeners.put(EXPORT_AS_PNG_ACTION, new ExportAsPNGAction());
        listeners.put(EXPORT_AS_TIFF_ACTION, new ExportAsTIFFAction());
        listeners.put(PREFERENCES_ACTION, new PreferencesAction());
        listeners.put(CLOSE_ACTION, new CloseAction());
        listeners.put(EXIT_ACTION, application.createExitAction(this));
        listeners.put(VIEW_SOURCE_ACTION, new ViewSourceAction());

        javax.swing.ActionMap cMap = svgCanvas.getActionMap();
        listeners.put(RESET_TRANSFORM_ACTION,
                      cMap.get(JSVGCanvas.RESET_TRANSFORM_ACTION));
        listeners.put(ZOOM_IN_ACTION,
                      cMap.get(JSVGCanvas.ZOOM_IN_ACTION));
        listeners.put(ZOOM_OUT_ACTION,
                      cMap.get(JSVGCanvas.ZOOM_OUT_ACTION));

        listeners.put(PREVIOUS_TRANSFORM_ACTION, previousTransformAction);
        key = KeyStroke.getKeyStroke(KeyEvent.VK_K, KeyEvent.CTRL_MASK);
        imap.put(key, previousTransformAction);

        listeners.put(NEXT_TRANSFORM_ACTION, nextTransformAction);
        key = KeyStroke.getKeyStroke(KeyEvent.VK_L, KeyEvent.CTRL_MASK);
        imap.put(key, nextTransformAction);

        listeners.put(USE_STYLESHEET_ACTION, useStylesheetAction);
        listeners.put(PLAY_ACTION, playAction);
        listeners.put(PAUSE_ACTION, pauseAction);
        listeners.put(STOP_ACTION, stopAction);
        listeners.put(MONITOR_ACTION, new MonitorAction());
        listeners.put(DOM_VIEWER_ACTION, new DOMViewerAction());
        listeners.put(SET_TRANSFORM_ACTION, new SetTransformAction());
        listeners.put(FIND_DIALOG_ACTION, new FindDialogAction());
        listeners.put(THUMBNAIL_DIALOG_ACTION, new ThumbnailDialogAction());
        listeners.put(FLUSH_ACTION, new FlushAction());
        listeners.put(TOGGLE_DEBUGGER_ACTION, new ToggleDebuggerAction());

        JPanel p = null;
        try {
            // Create the menu
            MenuFactory mf = new MenuFactory(bundle, this);
            JMenuBar mb = mf.createJMenuBar("MenuBar");
            setJMenuBar(mb);

            localHistory = new LocalHistory(mb, this);

            String uri[] = application.getVisitedURIs();
            for (int i=0; i<uri.length; i++) {
                if (uri[i] != null && !"".equals(uri[i])) {
                    localHistory.update(uri[i]);
                }
            }
            p = new JPanel(new BorderLayout());

            // Create the toolbar
            ToolBarFactory tbf = new ToolBarFactory(bundle, this);
            JToolBar tb = tbf.createJToolBar("ToolBar");
            tb.setFloatable(false);
            getContentPane().add(p, BorderLayout.NORTH);
            p.add(tb, BorderLayout.NORTH);
            p.add(new javax.swing.JSeparator(), BorderLayout.CENTER);
            p.add(locationBar = new LocationBar(), BorderLayout.SOUTH);

        } catch (MissingResourceException e) {
            System.out.println(e.getMessage());
            System.exit(0);
        }

        svgCanvasPanel = new JPanel(new BorderLayout());
        svgCanvasPanel.setBorder(BorderFactory.createEtchedBorder());

        svgCanvasPanel.add(svgCanvas, BorderLayout.CENTER);
        p = new JPanel(new BorderLayout());
        p.add(svgCanvasPanel, BorderLayout.CENTER);
        p.add(statusBar = new StatusBar(), BorderLayout.SOUTH);

        getContentPane().add(p, BorderLayout.CENTER);

        svgCanvas.addSVGDocumentLoaderListener(this);
        svgCanvas.addGVTTreeBuilderListener(this);
        svgCanvas.addSVGLoadEventDispatcherListener(this);
        svgCanvas.addGVTTreeRendererListener(this);
        svgCanvas.addLinkActivationListener(this);
        svgCanvas.addUpdateManagerListener(this);

        svgCanvas.addMouseMotionListener(new MouseMotionAdapter() {
                public void mouseMoved(MouseEvent e) {
                    if (svgDocument == null) {
                        statusBar.setXPosition(e.getX());
                        statusBar.setYPosition(e.getY());
                    } else {
                        try {
                            AffineTransform at;
                            at = svgCanvas.getViewBoxTransform();
                            if (at != null) {
                                at = at.createInverse();
                                Point2D p2d =
                                    at.transform(new Point2D.Float(e.getX(), e.getY()),
                                                 null);
                                statusBar.setXPosition((float)p2d.getX());
                                statusBar.setYPosition((float)p2d.getY());
                                return;
                            }
                        } catch (NoninvertibleTransformException ex) {
                        }
                        statusBar.setXPosition(e.getX());
                        statusBar.setYPosition(e.getY());
                    }
                }
            });
        svgCanvas.addMouseListener(new MouseAdapter() {
                public void mouseExited(MouseEvent e) {
                    Dimension dim = svgCanvas.getSize();
                    if (svgDocument == null) {
                        statusBar.setWidth(dim.width);
                        statusBar.setHeight(dim.height);
                    } else {
                        try {
                            AffineTransform at;
                            at = svgCanvas.getViewBoxTransform();
                            if (at != null) {
                                at = at.createInverse();
                                Point2D o =
                                    at.transform(new Point2D.Float(0, 0),
                                                 null);
                                Point2D p2d =
                                    at.transform(new Point2D.Float(dim.width,
                                                                   dim.height),
                                                 null);
                                statusBar.setWidth((float)(p2d.getX() - o.getX()));
                                statusBar.setHeight((float)(p2d.getY() - o.getY()));
                                return;
                            }
                        } catch (NoninvertibleTransformException ex) {
                        }
                        statusBar.setWidth(dim.width);
                        statusBar.setHeight(dim.height);
                    }
                }
            });
        svgCanvas.addComponentListener(new ComponentAdapter() {
                public void componentResized(ComponentEvent e) {
                    Dimension dim = svgCanvas.getSize();
                    if (svgDocument == null) {
                        statusBar.setWidth(dim.width);
                        statusBar.setHeight(dim.height);
                    } else {
                        try {
                            AffineTransform at;
                            at = svgCanvas.getViewBoxTransform();
                            if (at != null) {
                                at = at.createInverse();
                                Point2D o =
                                    at.transform(new Point2D.Float(0, 0),
                                                 null);
                                Point2D p2d =
                                    at.transform(new Point2D.Float(dim.width,
                                                                   dim.height),
                                                 null);
                                statusBar.setWidth((float)(p2d.getX() - o.getX()));
                                statusBar.setHeight((float)(p2d.getY() - o.getY()));
                                return;
                            }
                        } catch (NoninvertibleTransformException ex) {
                        }
                        statusBar.setWidth(dim.width);
                        statusBar.setHeight(dim.height);
                    }
                }
            });

        locationBar.addActionListener(new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                String st = locationBar.getText().trim();
                int i = st.indexOf("#");
                String t = "";
                if (i != -1) {
                    t = st.substring(i + 1);
                    st = st.substring(0, i);
                }
                if (!st.equals("")) {
                    try{
                        File f = new File(st);
                        if (f.exists()) {
                            if (f.isDirectory()) {
                                st = null;
                            } else {
                                try {
                                    st = f.getCanonicalPath();
                                    if (st.startsWith("/")) {
                                        st = "file:" + st;
                                    } else {
                                        st = "file:/" + st;
                                    }
                                } catch (IOException ex) {
                                }
                            }
                        }
                    }catch(SecurityException se){
                        // Could not patch the file URI for security
                        // reasons (e.g., when run as an unsigned
                        // JavaWebStart jar): file access is not
                        // allowed. Loading will fail, but there is
                        // nothing more to do at this point.
                    }

                    if (st != null) {
                        if (svgDocument != null) {
                            ParsedURL docPURL 
                                = new ParsedURL(svgDocument.getURL());
                            ParsedURL purl = new ParsedURL(docPURL, st);
                            String fi = svgCanvas.getFragmentIdentifier();
                            fi = (fi == null) ? "" : fi;
                            if (docPURL.equals(purl) && t.equals(fi)) {
                                return;
                            }
                        }
                        if (t.length() != 0) {
                            st += "#" + t;
                        }
                        locationBar.setText(st);
                        locationBar.addToHistory(st);
                        showSVGDocument(st);
                    }
                }
            }
        });

    }

    /**
     * Call dispose on canvas as well.
     */
    public void dispose() {
        svgCanvas.dispose();
        super.dispose();
    }

    /**
     * Whether to show the debug traces.
     */
    public void setDebug(boolean b) {
        debug = b;
    }

    /**
     * Whether to auto adjust the canvas to the size of the document.
     */
    public void setAutoAdjust(boolean b) {
        autoAdjust = b;
    }

    /**
     * Returns the main JSVGCanvas of this frame.
     */
    public JSVGCanvas getJSVGCanvas() {
        return svgCanvas;
    }

    /**
     * Needed to work-around JFileChooser bug with abstract Files
     */
    private static File makeAbsolute(File f){
        if(!f.isAbsolute()){
            return f.getAbsoluteFile();
        }
        return f;
    }

    /**
     * To show the about dialog
     */
    public class AboutAction extends AbstractAction {
        public AboutAction(){
        }

        public void actionPerformed(ActionEvent e){
            AboutDialog dlg = new AboutDialog(JSVGViewerFrame.this);
            dlg.setSize(dlg.getPreferredSize()); // Work around pack() bug on some platforms
            dlg.setLocationRelativeTo(JSVGViewerFrame.this);
            dlg.show();
            dlg.toFront();
        }
    }

    /**
     * To open a new file.
     */
    public class OpenAction extends AbstractAction {

        public OpenAction() {
        }
        public void actionPerformed(ActionEvent e) {
            JFileChooser fileChooser = null;

            // Apply work around Windows problem when security is enabled, 
            // and when prior to JDK 1.4.
            String os = System.getProperty(PROPERTY_OS_NAME, PROPERTY_OS_NAME_DEFAULT);
            SecurityManager sm = System.getSecurityManager();
            
            if ( priorJDK1_4 && sm != null && os.indexOf(PROPERTY_OS_WINDOWS_PREFIX) != -1 ){
                fileChooser = new JFileChooser(makeAbsolute(currentPath),
                                               new WindowsAltFileSystemView());
            } else {
                fileChooser = new JFileChooser(makeAbsolute(currentPath));
            }

            fileChooser.setFileHidingEnabled(false);
            fileChooser.setFileSelectionMode
                (JFileChooser.FILES_ONLY);

            //
            // Add file filters from the handlers map
            //
            Iterator iter = getHandlers().iterator();
            while (iter.hasNext()) {
                SquiggleInputHandler handler 
                    = (SquiggleInputHandler)iter.next();
                fileChooser.addChoosableFileFilter
                    (new SquiggleInputHandlerFilter(handler));
            }
            
            int choice = fileChooser.showOpenDialog(JSVGViewerFrame.this);
            if (choice == JFileChooser.APPROVE_OPTION) {
                File f = fileChooser.getSelectedFile();
                
                currentPath = f;
                try { 
                    String furl = f.toURL().toString();
                    showSVGDocument(furl);
                } catch (MalformedURLException ex) {
                    if (userAgent != null) {
                        userAgent.displayError(ex);
                    }
                }
            }
        }
    }

    /**
     * Shows the given document into the viewer frame
     */
    public void showSVGDocument(String uri){
        try {
            ParsedURL purl = new ParsedURL(uri);
            SquiggleInputHandler 
                handler = getInputHandler(purl);
            
            handler.handle(purl,
                           JSVGViewerFrame.this);
        } catch (Exception e) {
            if (userAgent != null) {
                userAgent.displayError(e);
            }
        }

    }

    /**
     * Returns the input handler for the given URI
     */
    public SquiggleInputHandler getInputHandler(ParsedURL purl) throws IOException {
        Iterator iter = getHandlers().iterator();
        SquiggleInputHandler handler = null;

        while (iter.hasNext()) {
            SquiggleInputHandler curHandler = 
                (SquiggleInputHandler)iter.next();
            if (curHandler.accept(purl)) {
                handler = curHandler;
                break;
            }
        }

        // No handler found, use the default one.
        if (handler == null) {
            handler = defaultHandler;
        }

        return handler;
    }


    /**
     * Returns the list of input file handler. 
     */
    protected static Vector getHandlers() {
        if (handlers != null) {
            return handlers;
        }

        handlers = new Vector();
        registerHandler(new SVGInputHandler());
        
        Iterator iter = Service.providers(SquiggleInputHandler.class);
        while (iter.hasNext()) {
            SquiggleInputHandler handler 
                = (SquiggleInputHandler)iter.next();

            registerHandler(handler);
        }

        return handlers;
    }

    /**
     * Registers an input file handler by adding it to the handlers map.
     * @param handler the new input handler to register.
     */
    public static synchronized 
        void registerHandler(SquiggleInputHandler handler) {
        Vector handlers = getHandlers();
        handlers.addElement(handler);
    }

    /**
     * To open a new document.
     */
    public class OpenLocationAction extends AbstractAction {
        public OpenLocationAction() {}
        public void actionPerformed(ActionEvent e) {
            if (uriChooser == null) {
                uriChooser = new URIChooser(JSVGViewerFrame.this);
                uriChooser.setFileFilter(new SVGFileFilter());
                uriChooser.pack();
                Rectangle fr = getBounds();
                Dimension sd = uriChooser.getSize();
                uriChooser.setLocation(fr.x + (fr.width  - sd.width) / 2,
                                       fr.y + (fr.height - sd.height) / 2);
            }
            if (uriChooser.showDialog() == URIChooser.OK_OPTION) {
                String s = uriChooser.getText();
                int i = s.indexOf("#");
                String t = "";
                if (i != -1) {
                    t = s.substring(i + 1);
                    s = s.substring(0, i);
                }
                if (!s.equals("")) {
                    File f = new File(s);
                    if (f.exists()) {
                        if (f.isDirectory()) {
                            s = null;
                        } else {
                            try {
                                s = f.getCanonicalPath();
                                if (s.startsWith("/")) {
                                    s = "file:" + s;
                                } else {
                                    s = "file:/" + s;
                                }
                            } catch (IOException ex) {
                            }
                        }
                    }
                    if (s != null) {
                        if (svgDocument != null) {
                            ParsedURL docPURL 
                                = new ParsedURL(svgDocument.getURL());
                            ParsedURL purl = new ParsedURL(docPURL, s);
                            String fi = svgCanvas.getFragmentIdentifier();
                            if (docPURL.equals(purl) && t.equals(fi)) {
                                return;
                            }
                        }
                        if (t.length() != 0) {
                            s += "#" + t;
                        }

                        showSVGDocument(s);
                    }
                }
            }
        }
    }

    /**
     * To open a new window.
     */
    public class NewWindowAction extends AbstractAction {
        public NewWindowAction() {}
        public void actionPerformed(ActionEvent e) {
            JSVGViewerFrame vf = application.createAndShowJSVGViewerFrame();

            // Copy the current settings to the new window.
            vf.autoAdjust = autoAdjust;
            vf.debug = debug;
            vf.svgCanvas.setProgressivePaint(svgCanvas.getProgressivePaint());
            vf.svgCanvas.setDoubleBufferedRendering
                (svgCanvas.getDoubleBufferedRendering());
        }
    }

    /**
     * To show the preferences.
     */
    public class PreferencesAction extends AbstractAction {
        public PreferencesAction() {}
        public void actionPerformed(ActionEvent e) {
            application.showPreferenceDialog(JSVGViewerFrame.this);
        }
    }

    /**
     * To close the last document.
     */
    public class CloseAction extends AbstractAction {
        public CloseAction() {}
        public void actionPerformed(ActionEvent e) {
            application.closeJSVGViewerFrame(JSVGViewerFrame.this);
        }
    }

    /**
     * To reload the current document.
     */
    public class ReloadAction extends AbstractAction {
        public ReloadAction() {}
        public void actionPerformed(ActionEvent e) {
            if ((e.getModifiers() & ActionEvent.SHIFT_MASK) == 1) {
                svgCanvas.flushImageCache();
            }
            if (svgDocument != null) {
                localHistory.reload();
            }
        }
    }

    /**
     * To go back to the previous document
     */
    public class BackAction extends    AbstractAction
                            implements JComponentModifier {
        List components = new LinkedList();
        public BackAction() {}
        public void actionPerformed(ActionEvent e) {
            if (localHistory.canGoBack()) {
                localHistory.back();
            }
        }

        public void addJComponent(JComponent c) {
            components.add(c);
            c.setEnabled(false);
        }

        protected void update() {
            boolean b = localHistory.canGoBack();
            Iterator it = components.iterator();
            while (it.hasNext()) {
                ((JComponent)it.next()).setEnabled(b);
            }
        }
    }

    /**
     * To go forward to the next document
     */
    public class ForwardAction extends    AbstractAction
                               implements JComponentModifier {
        List components = new LinkedList();
        public ForwardAction() {}
        public void actionPerformed(ActionEvent e) {
            if (localHistory.canGoForward()) {
                localHistory.forward();
            }
        }

        public void addJComponent(JComponent c) {
            components.add(c);
            c.setEnabled(false);
        }

        protected void update() {
            boolean b = localHistory.canGoForward();
            Iterator it = components.iterator();
            while (it.hasNext()) {
                ((JComponent)it.next()).setEnabled(b);
            }
        }
    }

    /**
     * To print the current document.
     */
    public class PrintAction extends AbstractAction {
        public PrintAction() {}
        public void actionPerformed(ActionEvent e) {
            if (svgDocument != null) {
                final SVGDocument doc = svgDocument;
                new Thread() {
                    public void run(){
                        String uri = doc.getURL();
                        String fragment = svgCanvas.getFragmentIdentifier();
                        if (fragment != null) {
                            uri += "#"+fragment;
                        }

                        //
                        // Build a PrintTranscoder to handle printing
                        // of the svgDocument object
                        //
                        PrintTranscoder pt = new PrintTranscoder();

                        //
                        // Set transcoding hints
                        //
                        pt.addTranscodingHint(PrintTranscoder.KEY_XML_PARSER_CLASSNAME,
                                              application.getXMLParserClassName());

                        pt.addTranscodingHint(PrintTranscoder.KEY_SHOW_PAGE_DIALOG,
                                              Boolean.TRUE);


                        pt.addTranscodingHint(PrintTranscoder.KEY_SHOW_PRINTER_DIALOG,
                                              Boolean.TRUE);

                        //
                        // Do transcoding now
                        //
                        pt.transcode(new TranscoderInput(uri), null);

                        //
                        // Print
                        //
                        try {
                            pt.print();
                        } catch (PrinterException ex) {
                            userAgent.displayError(ex);
                        }
                    }
                }.start();
            }
        }
    }

    /**
     * To save the current document as JPG.
     */
    public class ExportAsJPGAction extends AbstractAction {
        public ExportAsJPGAction() {}
        public void actionPerformed(ActionEvent e) {
            JFileChooser fileChooser =
                new JFileChooser(makeAbsolute(currentExportPath));
            fileChooser.setDialogTitle(resources.getString("ExportAsJPG.title"));
            fileChooser.setFileHidingEnabled(false);
            fileChooser.setFileSelectionMode
                (JFileChooser.FILES_ONLY);
            fileChooser.addChoosableFileFilter(new ImageFileFilter(".jpg"));

            int choice = fileChooser.showSaveDialog(JSVGViewerFrame.this);
            if (choice == JFileChooser.APPROVE_OPTION) {
                float quality =
                    JPEGOptionPanel.showDialog(JSVGViewerFrame.this);

                final File f = fileChooser.getSelectedFile();
                BufferedImage buffer = svgCanvas.getOffScreen();
                if (buffer != null) {
                    statusBar.setMessage
                        (resources.getString("Message.exportAsJPG"));

                    // create a BufferedImage of the appropriate type
                    int w = buffer.getWidth();
                    int h = buffer.getHeight();
                    final ImageTranscoder trans = new JPEGTranscoder();
                    trans.addTranscodingHint
                        (JPEGTranscoder.KEY_XML_PARSER_CLASSNAME,
                         application.getXMLParserClassName());
                    trans.addTranscodingHint
                        (JPEGTranscoder.KEY_QUALITY, new Float(quality));

                    final BufferedImage img = trans.createImage(w, h);

                    // paint the buffer to the image
                    Graphics2D g2d = img.createGraphics();
                    g2d.setColor(Color.white);
                    g2d.fillRect(0, 0, w, h);
                    g2d.drawImage(buffer, null, 0, 0);
                    new Thread() {
                        public void run() {
                            try {
                                currentExportPath = f;
                                OutputStream ostream =
                                    new BufferedOutputStream(new FileOutputStream(f));
                                trans.writeImage(img, new TranscoderOutput(ostream));
                                ostream.flush();
                                ostream.close();
                            } catch (Exception ex) { }
                            statusBar.setMessage
                                (resources.getString("Message.done"));
                        }
                    }.start();
                }
            }
        }
    }

    /**
     * To save the current document as PNG.
     */
    public class ExportAsPNGAction extends AbstractAction {
        public ExportAsPNGAction() {}
        public void actionPerformed(ActionEvent e) {
            JFileChooser fileChooser =
                new JFileChooser(makeAbsolute(currentExportPath));
            fileChooser.setDialogTitle(resources.getString("ExportAsPNG.title"));
            fileChooser.setFileHidingEnabled(false);
            fileChooser.setFileSelectionMode
                (JFileChooser.FILES_ONLY);
            fileChooser.addChoosableFileFilter(new ImageFileFilter(".png"));

            int choice = fileChooser.showSaveDialog(JSVGViewerFrame.this);
            if (choice == JFileChooser.APPROVE_OPTION) {

		// Start: By Jun Inamori (jun@oop-reserch.com)
                boolean isIndexed =
                    PNGOptionPanel.showDialog(JSVGViewerFrame.this);
		// End: By Jun Inamori (jun@oop-reserch.com)

                final File f = fileChooser.getSelectedFile();
                BufferedImage buffer = svgCanvas.getOffScreen();
                if (buffer != null) {
                    statusBar.setMessage
                        (resources.getString("Message.exportAsPNG"));

                    // create a BufferedImage of the appropriate type
                    int w = buffer.getWidth();
                    int h = buffer.getHeight();
                    final ImageTranscoder trans = new PNGTranscoder();
                    trans.addTranscodingHint(PNGTranscoder.KEY_XML_PARSER_CLASSNAME,
                                             application.getXMLParserClassName());
                    trans.addTranscodingHint(PNGTranscoder.KEY_FORCE_TRANSPARENT_WHITE,
                                             new Boolean(true));

		    // Start: By Jun Inamori
		    if(isIndexed){
			trans.addTranscodingHint
                            (PNGTranscoder.KEY_INDEXED,new Integer(256));
		    }
		    // End: By Jun Inamori

                    final BufferedImage img = trans.createImage(w, h);

                    // paint the buffer to the image
                    Graphics2D g2d = img.createGraphics();
                    g2d.drawImage(buffer, null, 0, 0);
                    new Thread() {
                        public void run() {
                            try {
                                currentExportPath = f;
                                OutputStream ostream =
                                    new BufferedOutputStream(new FileOutputStream(f));
                                trans.writeImage(img,
                                                 new TranscoderOutput(ostream));
                                ostream.flush();
                            } catch (Exception ex) {}
                            statusBar.setMessage
                                (resources.getString("Message.done"));
                        }
                    }.start();
                }
            }
        }
    }

    /**
     * To save the current document as TIFF.
     */
    public class ExportAsTIFFAction extends AbstractAction {
        public ExportAsTIFFAction() {}
        public void actionPerformed(ActionEvent e) {
            JFileChooser fileChooser =
                new JFileChooser(makeAbsolute(currentExportPath));
            fileChooser.setDialogTitle(resources.getString("ExportAsTIFF.title"));
            fileChooser.setFileHidingEnabled(false);
            fileChooser.setFileSelectionMode
                (JFileChooser.FILES_ONLY);
            fileChooser.addChoosableFileFilter(new ImageFileFilter(".tiff"));

            int choice = fileChooser.showSaveDialog(JSVGViewerFrame.this);
            if (choice == JFileChooser.APPROVE_OPTION) {
                final File f = fileChooser.getSelectedFile();
                BufferedImage buffer = svgCanvas.getOffScreen();
                if (buffer != null) {
                    statusBar.setMessage
                        (resources.getString("Message.exportAsTIFF"));

                    // create a BufferedImage of the appropriate type
                    int w = buffer.getWidth();
                    int h = buffer.getHeight();
                    final ImageTranscoder trans = new TIFFTranscoder();
                    trans.addTranscodingHint
                        (TIFFTranscoder.KEY_XML_PARSER_CLASSNAME,
                         application.getXMLParserClassName());
                    final BufferedImage img = trans.createImage(w, h);

                    // paint the buffer to the image
                    Graphics2D g2d = img.createGraphics();
                    g2d.drawImage(buffer, null, 0, 0);
                    new Thread() {
                        public void run() {
                            try {
                                currentExportPath = f;
                                OutputStream ostream = new BufferedOutputStream
                                    (new FileOutputStream(f));
                                trans.writeImage
                                    (img, new TranscoderOutput(ostream));
                                ostream.flush();
                            } catch (Exception ex) {}
                            statusBar.setMessage
                                (resources.getString("Message.done"));
                        }
                    }.start();
                }
            }
        }
    }

    /**
     * To view the source of the current document.
     */
    public class ViewSourceAction extends AbstractAction {
        public ViewSourceAction() {}
        public void actionPerformed(ActionEvent e) {
            if (svgDocument == null) {
                return;
            }

            final ParsedURL u = new ParsedURL(svgDocument.getURL());
            
            final JFrame fr = new JFrame(u.toString());
            fr.setSize(resources.getInteger("ViewSource.width"),
                       resources.getInteger("ViewSource.height"));
            final JTextArea ta  = new JTextArea();
            ta.setLineWrap(true);
            ta.setFont(new Font("monospaced", Font.PLAIN, 12));

            JScrollPane scroll = new JScrollPane();
            scroll.getViewport().add(ta);
            scroll.setVerticalScrollBarPolicy
                (JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
            fr.getContentPane().add(scroll, BorderLayout.CENTER);

            new Thread() {
                public void run() {
                    char [] buffer = new char[4096];

                    try {
                        Document  doc = new PlainDocument();

                        ParsedURL purl = new ParsedURL(svgDocument.getURL());
                        InputStream is
                            = u.openStream(getInputHandler(purl).
                                           getHandledMimeTypes());
                        // u.openStream(MimeTypeConstants.MIME_TYPES_SVG);

                        Reader in = XMLUtilities.createXMLDocumentReader(is);
                        int len;
                        while ((len=in.read(buffer, 0, buffer.length)) != -1) {
                            doc.insertString(doc.getLength(),
                                             new String(buffer, 0, len), null);
                        }

                        ta.setDocument(doc);
                        ta.setEditable(false);
                        ta.setBackground(Color.white);
                        fr.show();
                    } catch (Exception ex) {
                        userAgent.displayError(ex);
                    }
                }
            }.start();
        }
    }

    /**
     * To flush image cache (purely for debugging purposes)
     */
    public class FlushAction extends AbstractAction {
        public FlushAction() {}
        public void actionPerformed(ActionEvent e) {
            svgCanvas.flush();
            // Force redraw...
            svgCanvas.setRenderingTransform(svgCanvas.getRenderingTransform());
        }
    }

    /**
     * To toggle visiblity of JavaScript Debugger.
     */
    public class ToggleDebuggerAction extends AbstractAction {
        public ToggleDebuggerAction() {
            super("Toggle Debugger Action");
            if (debuggerClass == null)
                setEnabled(false);
        }

        public void actionPerformed(ActionEvent e) {
            if (debuggerClass == null) {
                setEnabled(false);
                return;
            }
            if ((debuggerFrame == null) || !debuggerFrame.isShowing())
                showDebugger();
            else
                hideDebugger();
        }
    }

    /**
     * To go back to the previous transform
     */
    public class PreviousTransformAction extends    AbstractAction
                                         implements JComponentModifier {
        List components = new LinkedList();
        public PreviousTransformAction() {}
        public void actionPerformed(ActionEvent e) {
            if (transformHistory.canGoBack()) {
                transformHistory.back();
                update();
                nextTransformAction.update();
                svgCanvas.setRenderingTransform(transformHistory.currentTransform());
            }
        }

        public void addJComponent(JComponent c) {
            components.add(c);
            c.setEnabled(false);
        }

        protected void update() {
            boolean b = transformHistory.canGoBack();
            Iterator it = components.iterator();
            while (it.hasNext()) {
                ((JComponent)it.next()).setEnabled(b);
            }
        }
    }

    /**
     * To go forward to the next transform
     */
    public class NextTransformAction extends    AbstractAction
                                         implements JComponentModifier {
        List components = new LinkedList();
        public NextTransformAction() {}
        public void actionPerformed(ActionEvent e) {
            if (transformHistory.canGoForward()) {
                transformHistory.forward();
                update();
                previousTransformAction.update();
                svgCanvas.setRenderingTransform(transformHistory.currentTransform());
            }
        }

        public void addJComponent(JComponent c) {
            components.add(c);
            c.setEnabled(false);
        }

        protected void update() {
            boolean b = transformHistory.canGoForward();
            Iterator it = components.iterator();
            while (it.hasNext()) {
                ((JComponent)it.next()).setEnabled(b);
            }
        }
    }

    /**
     * To apply the selected author stylesheet
     */
    public class UseStylesheetAction extends    AbstractAction
                                     implements JComponentModifier {

        List components = new LinkedList();

        public UseStylesheetAction() {}

        public void actionPerformed(ActionEvent e) {
        }

        public void addJComponent(JComponent c) {
            components.add(c);
            c.setEnabled(false);
        }

        protected void update() {
            alternateStyleSheet = null;
            Iterator it = components.iterator();
            SVGDocument doc = svgCanvas.getSVGDocument();
            while (it.hasNext()) {
                JComponent stylesheetMenu = (JComponent)it.next();
                stylesheetMenu.removeAll();
                stylesheetMenu.setEnabled(false);

                ButtonGroup buttonGroup = new ButtonGroup();

                for (Node n = doc.getFirstChild();
                     n != null && n.getNodeType() != Node.ELEMENT_NODE;
                     n = n.getNextSibling()) {
                    if (n instanceof StyleSheetProcessingInstruction) {
                        StyleSheetProcessingInstruction sspi;
                        sspi = (StyleSheetProcessingInstruction)n;
                        HashTable attrs = sspi.getPseudoAttributes();
                        final String title = (String)attrs.get("title");
                        String alt = (String)attrs.get("alternate");
                        if (title != null && "yes".equals(alt)) {
                            JRadioButtonMenuItem button;
                            button = new JRadioButtonMenuItem(title);

                            button.addActionListener
                                (new java.awt.event.ActionListener() {
                                    public void actionPerformed(ActionEvent e) {
                                        SVGOMDocument doc;
                                        doc = (SVGOMDocument)svgCanvas.getSVGDocument();
                                        doc.clearViewCSS();
                                        alternateStyleSheet = title;
                                        svgCanvas.setSVGDocument(doc);
                                    }
                                });

                            buttonGroup.add(button);
                            stylesheetMenu.add(button);
                            stylesheetMenu.setEnabled(true);
                        }
                    }
                }
            }
        }
    }

    /**
     * To restart after a pause.
     */
    public class PlayAction extends   AbstractAction
                            implements JComponentModifier {
        java.util.List components = new LinkedList();
        public PlayAction() {}
        public void actionPerformed(ActionEvent e) {
            svgCanvas.resumeProcessing();
        }

        public void addJComponent(JComponent c) {
            components.add(c);
            c.setEnabled(false);
        }

        public void update(boolean enabled) {
            Iterator it = components.iterator();
            while (it.hasNext()) {
                ((JComponent)it.next()).setEnabled(enabled);
            }
        }
    }

    /**
     * To pause a document.
     */
    public class PauseAction extends   AbstractAction
                            implements JComponentModifier {
        java.util.List components = new LinkedList();
        public PauseAction() {}
        public void actionPerformed(ActionEvent e) {
            svgCanvas.suspendProcessing();
        }

        public void addJComponent(JComponent c) {
            components.add(c);
            c.setEnabled(false);
        }

        public void update(boolean enabled) {
            Iterator it = components.iterator();
            while (it.hasNext()) {
                ((JComponent)it.next()).setEnabled(enabled);
            }
        }
    }

    /**
     * To stop the current processing.
     */
    public class StopAction extends    AbstractAction
                            implements JComponentModifier {
        java.util.List components = new LinkedList();
        public StopAction() {}
        public void actionPerformed(ActionEvent e) {
            svgCanvas.stopProcessing();
        }

        public void addJComponent(JComponent c) {
            components.add(c);
            c.setEnabled(false);
        }

        public void update(boolean enabled) {
            Iterator it = components.iterator();
            while (it.hasNext()) {
                ((JComponent)it.next()).setEnabled(enabled);
            }
        }
    }

    /**
     * To show the set transform dialog
     */
    public class SetTransformAction extends AbstractAction {
        public SetTransformAction(){}
        public void actionPerformed(ActionEvent e){
            if (transformDialog == null){
                transformDialog
                    = JAffineTransformChooser.createDialog
                    (JSVGViewerFrame.this,
                     resources.getString("SetTransform.title"));
            }

            AffineTransform txf = transformDialog.showDialog();
            if(txf != null){
                AffineTransform at = svgCanvas.getRenderingTransform();
                if(at == null){
                    at = new AffineTransform();
                }

                txf.concatenate(at);
                svgCanvas.setRenderingTransform(txf);
            }
        }
    }

    /**
     * To display the memory monitor.
     */
    public class MonitorAction extends AbstractAction {
        public MonitorAction() {}
        public void actionPerformed(ActionEvent e) {
            if (memoryMonitorFrame == null) {
                memoryMonitorFrame = new MemoryMonitor();
                Rectangle fr = getBounds();
                Dimension md = memoryMonitorFrame.getSize();
                memoryMonitorFrame.setLocation(fr.x + (fr.width  - md.width) / 2,
                                               fr.y + (fr.height - md.height) / 2);
            }
            memoryMonitorFrame.show();
        }
    }

    /**
     * To display the Find dialog
     */
    public class FindDialogAction extends AbstractAction {
        public FindDialogAction() {}
        public void actionPerformed(ActionEvent e) {
            if (findDialog == null) {
                findDialog = new FindDialog(JSVGViewerFrame.this, svgCanvas);
                findDialog.setGraphicsNode(svgCanvas.getGraphicsNode());
                findDialog.pack();
                Rectangle fr = getBounds();
                Dimension td = findDialog.getSize();
                findDialog.setLocation(fr.x + (fr.width  - td.width) / 2,
                                       fr.y + (fr.height - td.height) / 2);
            }
            findDialog.show();
        }
    }

    /**
     * To display the Thumbnail dialog
     */
    public class ThumbnailDialogAction extends AbstractAction {
        public ThumbnailDialogAction() {}
        public void actionPerformed(ActionEvent e) {
            if (thumbnailDialog == null) {
                thumbnailDialog
                    = new ThumbnailDialog(JSVGViewerFrame.this, svgCanvas);
                thumbnailDialog.pack();
                Rectangle fr = getBounds();
                Dimension td = thumbnailDialog.getSize();
                thumbnailDialog.setLocation(fr.x + (fr.width  - td.width) / 2,
                                            fr.y + (fr.height - td.height) / 2);
            }
            thumbnailDialog.setInteractionEnabled
                (!svgCanvas.getDisableInteractions());
            thumbnailDialog.show();
        }
    }

    /**
     * To display the document full screen
     */
    public class FullScreenAction extends AbstractAction {
        public FullScreenAction() {}

        public void actionPerformed(ActionEvent e) {
            if (window == null || !window.isVisible()) {
                if (window == null) {
                    window = new JWindow(JSVGViewerFrame.this);
                    Dimension size = Toolkit.getDefaultToolkit().getScreenSize();
                    window.setSize(size);
                }
                // Go to full screen in JWindow)
                svgCanvas.getParent().remove(svgCanvas);
                window.getContentPane().add(svgCanvas);
                window.setVisible(true);
                window.toFront();
                svgCanvas.requestFocus();
            } else {
                // Go back to JSVGViewerFrame display
                svgCanvas.getParent().remove(svgCanvas);
                svgCanvasPanel.add(svgCanvas, BorderLayout.CENTER);
                window.setVisible(false);
            } 
        }
    }
    
    /**
     * To display the DOM viewer of the document
     */
    public class DOMViewerAction extends AbstractAction {
        public DOMViewerAction() {}
        public void actionPerformed(ActionEvent e) {
            if (domViewer == null) {
                domViewer = new DOMViewer();
                if (svgDocument != null) {
                    domViewer.setDocument(svgDocument,
                                          (ViewCSS)svgDocument.getDocumentElement());
                }
                Rectangle fr = getBounds();
                Dimension td = domViewer.getSize();
                domViewer.setLocation(fr.x + (fr.width  - td.width) / 2,
                                      fr.y + (fr.height - td.height) / 2);
            }
            domViewer.show();
        }
    }

    // ActionMap /////////////////////////////////////////////////////

    /**
     * The map that contains the action listeners
     */
    protected Map listeners = new HashMap();

    /**
     * Returns the action associated with the given string
     * or null on error
     * @param key the key mapped with the action to get
     * @throws MissingListenerException if the action is not found
     */
    public Action getAction(String key) throws MissingListenerException {
        Action result = (Action)listeners.get(key);
        //if (result == null) {
        //result = canvas.getAction(key);
        //}
        if (result == null) {
            throw new MissingListenerException("Can't find action.", RESOURCES, key);
        }
        return result;
    }

    // SVGDocumentLoaderListener ///////////////////////////////////////////

    long time; // For debug.

    /**
     * Called when the loading of a document was started.
     */
    public void documentLoadingStarted(SVGDocumentLoaderEvent e) {
        if (debug) {
            System.out.println("Document load started...");
            time = System.currentTimeMillis();
        }
        statusBar.setMainMessage(resources.getString("Message.documentLoad"));
        stopAction.update(true);
        svgCanvas.setCursor(WAIT_CURSOR);
    }


    /**
     * Called when the loading of a document was completed.
     */
    public void documentLoadingCompleted(SVGDocumentLoaderEvent e) {
        if (debug) {
            System.out.print("Document load completed in ");
            System.out.println((System.currentTimeMillis() - time) + " ms");
        }

        setSVGDocument(e.getSVGDocument(),
                       e.getSVGDocument().getURL(),
                       e.getSVGDocument().getTitle());
    }

    /**
     * Forces the viewer frame to show the input SVGDocument
     */
    public void setSVGDocument(SVGDocument svgDocument,
                               String svgDocumentURL,
                               String svgDocumentTitle) {
        this.svgDocument = svgDocument;

        if (domViewer != null) {
            if(domViewer.isVisible() && svgDocument != null) {
                domViewer.setDocument(svgDocument,
                                      (ViewCSS)svgDocument.getDocumentElement());
            } else {
                domViewer.dispose();
                domViewer = null;
            }
        }
        stopAction.update(false);
        svgCanvas.setCursor(DEFAULT_CURSOR);
        String s = svgDocumentURL;
        String t = svgCanvas.getFragmentIdentifier();
        if (t != null) {
            s += "#" + t;
        }

        locationBar.setText(s);
        if (title == null) {
            title = getTitle();
        }

        String dt = svgDocumentTitle;
        if (dt.length() != 0) {
            setTitle(title + ":" + dt);
        } else {
            int i = s.lastIndexOf("/");
            if (i == -1) {
                i = s.lastIndexOf("\\");
                if (i == -1) {
                    setTitle(title + ":" + s);
                } else {
                    setTitle(title + ":" + s.substring(i + 1));
                }
            } else {
                setTitle(title + ":" + s.substring(i + 1));
            }
        }

        localHistory.update(s);
        application.addVisitedURI(s);
        backAction.update();
        forwardAction.update();

        transformHistory = new TransformHistory();
        previousTransformAction.update();
        nextTransformAction.update();

        useStylesheetAction.update();
    }

    /**
     * Called when the loading of a document was cancelled.
     */
    public void documentLoadingCancelled(SVGDocumentLoaderEvent e) {
        if (debug) {
            System.out.println("Document load cancelled");
        }
        statusBar.setMainMessage("");
        statusBar.setMessage(resources.getString("Message.documentCancelled"));
        stopAction.update(false);
        svgCanvas.setCursor(DEFAULT_CURSOR);
    }

    /**
     * Called when the loading of a document has failed.
     */
    public void documentLoadingFailed(SVGDocumentLoaderEvent e) {
        if (debug) {
            System.out.println("Document load failed");
        }
        statusBar.setMainMessage("");
        statusBar.setMessage(resources.getString("Message.documentFailed"));
        stopAction.update(false);
        svgCanvas.setCursor(DEFAULT_CURSOR);
    }

    // GVTTreeBuilderListener //////////////////////////////////////////////

    /**
     * Called when a build started.
     * The data of the event is initialized to the old document.
     */
    public void gvtBuildStarted(GVTTreeBuilderEvent e) {
        if (debug) {
            System.out.println("GVT build started...");
            time = System.currentTimeMillis();
        }
        statusBar.setMainMessage(resources.getString("Message.treeBuild"));
        stopAction.update(true);
        svgCanvas.setCursor(WAIT_CURSOR);
    }

    /**
     * Called when a build was completed.
     */
    public void gvtBuildCompleted(GVTTreeBuilderEvent e) {
        if (debug) {
            System.out.print("GVT build completed in ");
            System.out.println((System.currentTimeMillis() - time) + " ms");
        }
        if (findDialog != null) {
            if(findDialog.isVisible()) {
                findDialog.setGraphicsNode(svgCanvas.getGraphicsNode());
            } else {
                findDialog.dispose();
                findDialog = null;
            }
        }
        stopAction.update(false);
        svgCanvas.setCursor(DEFAULT_CURSOR);
        svgCanvas.setSelectionOverlayXORMode
            (application.isSelectionOverlayXORMode());
        svgCanvas.requestFocus();  // request focus when load completes.
    }

    /**
     * Called when a build was cancelled.
     */
    public void gvtBuildCancelled(GVTTreeBuilderEvent e) {
        if (debug) {
            System.out.println("GVT build cancelled");
        }
        statusBar.setMainMessage("");
        statusBar.setMessage(resources.getString("Message.treeCancelled"));
        stopAction.update(false);
        svgCanvas.setCursor(DEFAULT_CURSOR);
        svgCanvas.setSelectionOverlayXORMode
            (application.isSelectionOverlayXORMode());
    }

    /**
     * Called when a build failed.
     */
    public void gvtBuildFailed(GVTTreeBuilderEvent e) {
        if (debug) {
            System.out.println("GVT build failed");
        }
        statusBar.setMainMessage("");
        statusBar.setMessage(resources.getString("Message.treeFailed"));
        stopAction.update(false);
        svgCanvas.setCursor(DEFAULT_CURSOR);
        svgCanvas.setSelectionOverlayXORMode
            (application.isSelectionOverlayXORMode());
        if (autoAdjust) {
            pack();
        }
    }

    // SVGLoadEventDispatcherListener //////////////////////////////////////

    /**
     * Called when a onload event dispatch started.
     */
    public void svgLoadEventDispatchStarted(SVGLoadEventDispatcherEvent e) {
        if (debug) {
            System.out.println("Onload dispatch started...");
            time = System.currentTimeMillis();
        }
        stopAction.update(true);
        statusBar.setMainMessage(resources.getString("Message.onload"));
    }

    /**
     * Called when a onload event dispatch was completed.
     */
    public void svgLoadEventDispatchCompleted(SVGLoadEventDispatcherEvent e) {
        if (debug) {
            System.out.print("Onload dispatch completed in ");
            System.out.println((System.currentTimeMillis() - time) + " ms");
        }
        stopAction.update(false);
        statusBar.setMainMessage("");
        statusBar.setMessage(resources.getString("Message.done"));
    }

    /**
     * Called when a onload event dispatch was cancelled.
     */
    public void svgLoadEventDispatchCancelled(SVGLoadEventDispatcherEvent e) {
        if (debug) {
            System.out.println("Onload dispatch cancelled.");
        }
        stopAction.update(false);
        statusBar.setMainMessage("");
        statusBar.setMessage(resources.getString("Message.onloadCancelled"));
    }

    /**
     * Called when a onload event dispatch failed.
     */
    public void svgLoadEventDispatchFailed(SVGLoadEventDispatcherEvent e) {
        if (debug) {
            System.out.println("Onload dispatch failed.");
        }
        stopAction.update(false);
        statusBar.setMainMessage("");
        statusBar.setMessage(resources.getString("Message.onloadFailed"));
    }

    // GVTTreeRendererListener /////////////////////////////////////////////

    /**
     * Called when a rendering is in its preparing phase.
     */
    public void gvtRenderingPrepare(GVTTreeRendererEvent e) {
        if (debug) {
            System.out.println("GVT rendering preparation...");
            time = System.currentTimeMillis();
        }
        stopAction.update(true);
        svgCanvas.setCursor(WAIT_CURSOR);
        statusBar.setMainMessage(resources.getString("Message.treeRendering"));
    }

    /**
     * Called when a rendering started.
     */
    public void gvtRenderingStarted(GVTTreeRendererEvent e) {
        if (debug) {
            System.out.print("GVT rendering prepared in ");
            System.out.println((System.currentTimeMillis() - time) + " ms");
            time = System.currentTimeMillis();
            System.out.println("GVT rendering started...");
        }
        // Do nothing
    }

    /**
     * Called when a rendering was completed.
     */
    public void gvtRenderingCompleted(GVTTreeRendererEvent e) {
        if (debug) {
            System.out.print("GVT rendering completed in ");
            System.out.println((System.currentTimeMillis() - time) + " ms");
        }
        statusBar.setMainMessage("");
        statusBar.setMessage(resources.getString("Message.done"));
        if (!svgCanvas.isDynamic() || managerStopped) {
            stopAction.update(false);
        }
        svgCanvas.setCursor(DEFAULT_CURSOR);

        transformHistory.update(svgCanvas.getRenderingTransform());
        previousTransformAction.update();
        nextTransformAction.update();
    }

    /**
     * Called when a rendering was cancelled.
     */
    public void gvtRenderingCancelled(GVTTreeRendererEvent e) {
        if (debug) {
            System.out.println("GVT rendering cancelled");
        }
        statusBar.setMainMessage("");
        if (!svgCanvas.isDynamic()) {
            stopAction.update(false);
        }
        svgCanvas.setCursor(DEFAULT_CURSOR);
    }

    /**
     * Called when a rendering failed.
     */
    public void gvtRenderingFailed(GVTTreeRendererEvent e) {
        if (debug) {
            System.out.println("GVT rendering failed");
        }
        statusBar.setMainMessage("");
        if (!svgCanvas.isDynamic()) {
            stopAction.update(false);
        }
        svgCanvas.setCursor(DEFAULT_CURSOR);
    }

    // LinkActivationListener /////////////////////////////////////////

    /**
     * Called when a link was activated.
     */
    public void linkActivated(LinkActivationEvent e) {
        String s = e.getReferencedURI();
        if (svgDocument != null) {
            ParsedURL docURL = new ParsedURL(svgDocument.getURL());
            ParsedURL url    = new ParsedURL(docURL, s);
            if (!url.sameFile(docURL)) {
                return;
            }

            if (s.indexOf("#") != -1) {
                localHistory.update(s);
                application.addVisitedURI(s);
                backAction.update();
                forwardAction.update();

                transformHistory = new TransformHistory();
                previousTransformAction.update();
                nextTransformAction.update();
            }
        }
    }

    // UpdateManagerListener ////////////////////////////////////////////////

    /**
     * Called when the manager was started.
     */
    public void managerStarted(UpdateManagerEvent e) {
        if (debug) {
            System.out.println("Update manager started...");
        }
        managerStopped = false;
        playAction.update(false);
        pauseAction.update(true);
        stopAction.update(true);
    }

    /**
     * Called when the manager was suspended.
     */
    public void managerSuspended(UpdateManagerEvent e) {
        if (debug) {
            System.out.println("Update manager suspended");
        }
        playAction.update(true);
        pauseAction.update(false);
    }

    /**
     * Called when the manager was resumed.
     */
    public void managerResumed(UpdateManagerEvent e) {
        if (debug) {
            System.out.println("Update manager resumed");
        }
        playAction.update(false);
        pauseAction.update(true);
    }

    /**
     * Called when the manager was stopped.
     */
    public void managerStopped(UpdateManagerEvent e) {
        if (debug) {
            System.out.println("Update manager stopped");
        }
        managerStopped = true;
        playAction.update(false);
        pauseAction.update(false);
        stopAction.update(false);
    }

    /**
     * Called when an update started.
     */
    public void updateStarted(final UpdateManagerEvent e) {
    }

    /**
     * Called when an update was completed.
     */
    public void updateCompleted(final UpdateManagerEvent e) {
    }

    /**
     * Called when an update failed.
     */
    public void updateFailed(UpdateManagerEvent e) {
    }

    /**
     * This class implements a SVG user agent.
     */
    protected class UserAgent implements SVGUserAgent {

        /**
         * Creates a new SVGUserAgent.
         */
        protected UserAgent() {
        }

        /**
         * Displays an error message.
         */
        public void displayError(String message) {
            if (debug) {
                System.err.println(message);
            }
            JOptionPane pane =
                new JOptionPane(message, JOptionPane.ERROR_MESSAGE);
            JDialog dialog = pane.createDialog(JSVGViewerFrame.this, "ERROR");
            dialog.setModal(false);
            dialog.show();
        }

        /**
         * Displays an error resulting from the specified Exception.
         */
        public void displayError(Exception ex) {
            if (debug) {
                ex.printStackTrace();
            }
            JErrorPane pane =
                new JErrorPane(ex, JOptionPane.ERROR_MESSAGE);
            JDialog dialog = pane.createDialog(JSVGViewerFrame.this, "ERROR");
            dialog.setModal(false);
            dialog.show();
        }

        /**
         * Displays a message in the User Agent interface.
         * The given message is typically displayed in a status bar.
         */
        public void displayMessage(String message) {
            statusBar.setMessage(message);
        }

        /**
         * Shows an alert dialog box.
         */
        public void showAlert(String message) {
            svgCanvas.showAlert(message);
        }

        /**
         * Shows a prompt dialog box.
         */
        public String showPrompt(String message) {
            return svgCanvas.showPrompt(message);
        }

        /**
         * Shows a prompt dialog box.
         */
        public String showPrompt(String message, String defaultValue) {
            return svgCanvas.showPrompt(message, defaultValue);
        }

        /**
         * Shows a confirm dialog box.
         */
        public boolean showConfirm(String message) {
            return svgCanvas.showConfirm(message);
        }

        /**
         * Returns the size of a px CSS unit in millimeters.
         */
        public float getPixelUnitToMillimeter() {
            return 0.26458333333333333333333333333333f; // 96dpi
        }
        
        /**
         * Returns the size of a px CSS unit in millimeters.
         * This will be removed after next release.
         * @see #getPixelUnitToMillimeter()
         */
        public float getPixelToMM() {
            return getPixelUnitToMillimeter();
            
        }

        /**
         * Returns the default font family.
         */
        public String getDefaultFontFamily() {
            return application.getDefaultFontFamily(); 
        }

        /** 
         * Returns the  medium font size. 
         */
        public float getMediumFontSize() {
            // 9pt (72pt == 1in)
            return 9f * 25.4f / (72f * getPixelUnitToMillimeter());
        }

        /**
         * Returns a lighter font-weight.
         */
        public float getLighterFontWeight(float f) {
            // Round f to nearest 100...
            int weight = ((int)((f+50)/100))*100;
            switch (weight) {
            case 100: return 100;
            case 200: return 100;
            case 300: return 200;
            case 400: return 300;
            case 500: return 400;
            case 600: return 400;
            case 700: return 400;
            case 800: return 400;
            case 900: return 400;
            default:
                throw new IllegalArgumentException("Bad Font Weight: " + f);
            }
        }

        /**
         * Returns a bolder font-weight.
         */
        public float getBolderFontWeight(float f) {
            // Round f to nearest 100...
            int weight = ((int)((f+50)/100))*100;
            switch (weight) {
            case 100: return 600;
            case 200: return 600;
            case 300: return 600;
            case 400: return 600;
            case 500: return 600;
            case 600: return 700;
            case 700: return 800;
            case 800: return 900;
            case 900: return 900;
            default:
                throw new IllegalArgumentException("Bad Font Weight: " + f);
            }
        }


        /**
         * Returns the language settings.
         */
        public String getLanguages() {
            return application.getLanguages();
        }

        /**
         * Returns the user stylesheet uri.
         * @return null if no user style sheet was specified.
         */
        public String getUserStyleSheetURI() {
            return application.getUserStyleSheetURI();
        }

        /**
         * Returns the class name of the XML parser.
         */
        public String getXMLParserClassName() {
            return application.getXMLParserClassName();
        }

        /**
         * Returns true if the XML parser must be in validation mode, false
         * otherwise.
         */
        public boolean isXMLParserValidating() {
            return application.isXMLParserValidating();
        }

        /**
         * Returns this user agent's CSS media.
         */
        public String getMedia() {
            return application.getMedia();
        }

        /**
         * Returns this user agent's alternate style-sheet title.
         */
        public String getAlternateStyleSheet() {
            return alternateStyleSheet;
        }

        /**
         * Opens a link.
         * @param uri The document URI.
         * @param newc Whether the link should be activated in a new component.
         */
        public void openLink(String uri, boolean newc) {
            if (newc) {
                application.openLink(uri);
            } else {
                showSVGDocument(uri);
            }
        }

        /**
         * Tells whether the given extension is supported by this
         * user agent.
         */
        public boolean supportExtension(String s) {
            return false;
        }

        public void handleElement(Element elt, Object data){
        }

        /**
         * Returns the security settings for the given script
         * type, script url and document url
         * 
         * @param scriptType type of script, as found in the 
         *        type attribute of the &lt;script&gt; element.
         * @param scriptURL url for the script, as defined in
         *        the script's xlink:href attribute. If that
         *        attribute was empty, then this parameter should
         *        be null
         * @param docURL url for the document into which the 
         *        script was found.
         */
        public ScriptSecurity getScriptSecurity(String scriptType,
                                                ParsedURL scriptURL,
                                                ParsedURL docURL){
            if (!application.canLoadScriptType(scriptType)) {
                return new NoLoadScriptSecurity(scriptType);
            } else {
                switch(application.getAllowedScriptOrigin()) {
                case ResourceOrigin.ANY:
                    return new RelaxedScriptSecurity(scriptType,
                                                     scriptURL,
                                                     docURL);
                case ResourceOrigin.DOCUMENT:
                    return new DefaultScriptSecurity(scriptType,
                                                     scriptURL,
                                                     docURL);
                case ResourceOrigin.EMBEDED:
                    return new EmbededScriptSecurity(scriptType,
                                                     scriptURL,
                                                     docURL);
                default:
                    return new NoLoadScriptSecurity(scriptType);
                }
            }
        }

        /**
         * This method throws a SecurityException if the script
         * of given type, found at url and referenced from docURL
         * should not be loaded.
         * 
         * This is a convenience method to call checkLoadScript
         * on the ScriptSecurity strategy returned by 
         * getScriptSecurity.
         *
         * @param scriptType type of script, as found in the 
         *        type attribute of the &lt;script&gt; element.
         * @param scriptURL url for the script, as defined in
         *        the script's xlink:href attribute. If that
         *        attribute was empty, then this parameter should
         *        be null
         * @param docURL url for the document into which the 
         *        script was found.
         */
        public void checkLoadScript(String scriptType,
                                    ParsedURL scriptURL,
                                    ParsedURL docURL) throws SecurityException {
            ScriptSecurity s = getScriptSecurity(scriptType,
                                                     scriptURL,
                                                     docURL);

            if (s != null) {
                s.checkLoadScript();
            } 
        }

        /**
         * Returns the security settings for the given 
         * resource url and document url
         * 
         * @param resourceURL url for the resource, as defined in
         *        the resource's xlink:href attribute. If that
         *        attribute was empty, then this parameter should
         *        be null
         * @param docURL url for the document into which the 
         *        resource was found.
         */
        public ExternalResourceSecurity 
            getExternalResourceSecurity(ParsedURL resourceURL,
                                        ParsedURL docURL){
            switch(application.getAllowedExternalResourceOrigin()) {
            case ResourceOrigin.ANY:
                return new RelaxedExternalResourceSecurity(resourceURL,
                                                           docURL);
            case ResourceOrigin.DOCUMENT:
                return new DefaultExternalResourceSecurity(resourceURL,
                                                           docURL);
            case ResourceOrigin.EMBEDED:
                return new EmbededExternalResourceSecurity(resourceURL);
            default:
                return new NoLoadExternalResourceSecurity();
            }
        }

        /**
         * This method throws a SecurityException if the resource
         * found at url and referenced from docURL
         * should not be loaded.
         * 
         * This is a convenience method to call checkLoadExternalResource
         * on the ExternalResourceSecurity strategy returned by 
         * getExternalResourceSecurity.
         *
         * @param scriptURL url for the script, as defined in
         *        the script's xlink:href attribute. If that
         *        attribute was empty, then this parameter should
         *        be null
         * @param docURL url for the document into which the 
         *        script was found.
         */
        public void 
            checkLoadExternalResource(ParsedURL resourceURL,
                                      ParsedURL docURL) throws SecurityException {
            ExternalResourceSecurity s 
                =  getExternalResourceSecurity(resourceURL, docURL);
            
            if (s != null) {
                s.checkLoadExternalResource();
            }
        }
    }

    /**
     * A FileFilter used when exporting the SVG document as an image.
     */
    protected static class ImageFileFilter extends FileFilter {

        /** The extension of the image filename. */
        protected String extension;

        public ImageFileFilter(String extension) {
            this.extension = extension;
        }

        /**
         * Returns true if <tt>f</tt> is a file with the correct extension,
         * false otherwise.
         */
        public boolean accept(File f) {
            boolean accept = false;
            String fileName = null;
            if (f != null) {
                if (f.isDirectory()) {
                    accept = true;
                } else {
                    fileName = f.getPath().toLowerCase();
                    if (fileName.endsWith(extension)) {
                        accept = true;
                    }
                }
            }
            return accept;
        }

        /**
         * Returns the file description
         */
        public String getDescription() {
            return extension;
        }
    }

}
