/*
 * [JDisplay.java]
 *
 * Summary: Displays a serialised pre-parsed Java program in fancy fonts and colours.
 *
 * Copyright: (c) 2004-2012 Roedy Green, Canadian Mind Products, http://mindprod.com
 *
 * Licence: This software may be copied and used freely for any purpose but military.
 *          http://mindprod.com/contact/nonmil.html
 *
 * Requires: JDK 1.5+
 *
 * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/
 *
 * Version History:
 *  1.2 2004-05-15 split off calcPreferredSize into its own
 *                 class
 *  1.3 2004-05-23 Put all logic about calculating panel and
 *                 frame size in PreferredSize None left to JDisplay macro. no bar
 *                 parameter. Computed automatically. manual control of when ScrollBars
 *                 used.
 *  1.4 2004-05-29 Flip back from Swing to AWT so that Ctrl-C
 *                 Ctrl-V will work. Even with AWT, I need a TextArea, not the
 *                 PrettyCanvas. Downside mainly was losing ability to turn or horizontal
 *                 and vertical scrollbars automatically. Adjust for fact scrollbars are
 *                 all or nothing. Can't have just vertical. Redo all tokenizers with
 *                 lookaheaad, and explicit handled boolean. Eliminate the enter method
 *                 on all tokenizers. Explicit list of all choices on default for
 *                 proofreading. Eliminate flicker with removal of super.paint(). \ in
 *                 bat now show in special font.
 *  1.5 2004-06-01 slightly larger margins, use new
 *                 BatTokenizer, HTMLTokenizer, JavaTokenizer
 *  1.6 2004-07-16 better recovery when cannot read *.ser
 *                 file.
 *  1.7 2005-06-12 destroy, make sure not null before remove.
 *                 Futures implement my own copy/paste that works with Swing or AWT token
 *                 needs to remember where it is on screen.
 *  1.8 2005-07-28 major overhaul to use new tokenizers..
 *  1.9 2005-09-07 allow JDisplay to run under Eclipse
 *  2.0 2005-11-11 make snippet/ optional in Applet url
 *                 parameter.
 *  2.1 2005-12-25 add parser for *.properties files
 *  2.2 2005-12-25 add parser for *.csv files
 *  2.3 2005-12-25 add parser for *.ini files
 *  2.4 2005-12-25 more robust error handling
 *  2.5 2006-01-27 prints vm version, more checks.
 *  2.6 2006-03-06 reformat with IntelliJ and add Javadoc
 *  2.7 2007-04-29 use a corresponding mono font when turn off colour.
 *  2.8 2007-05-05 add iformat rendering, use of snippet/ser and snippet/iformat
 *  2.9 2007-07-12 first public distribution.
 *  3.0 2007-07-26 add support for annotations.
 *  3.1 2007-08-20 new colour scheme.
 *  3.2 2007-09-17 rename snippets -> snippet. Label *.java and *.javafrag properly.
 *  3.3 2008-01-11 add support for hex and octal numerics.
 *  3.4 2008-02-23 bold variable definitions. more robust display of class on dump.
 *  3.5 2008-02-24 change sizes and spacing
 *  3.6 2008-03-06 convert to Swing
 *  3.7 2008-04-18 get JDisplay and CSS font renderings in closer sync
 *  3.8 2008-04-30 improve way numeric literals are rendered in Java.
 *  3.9 2008-08-08 add vanilla text parser for text files. No changes needed to JDisplay itself, just the bundle.
 *  4.0 2009-04-12 shorter style names, improved highlighting.
 *  4.1 2009-08-30 tone down colour for keywords.
 *  4.2 2009-09-30 fine tune size of fonts, adjusting large or small fonts to normal size, shrink keyword size.
 *  4.3 2010-02-08 highlight begin and ends of comments and CDATAs specially.
 *  4.4 2010-02-10 add manifest tokenizer.
 *  4.5 2011-02-03 tidy up code, fix bug stopping B&W version from scrolling.
 */
package com.mindprod.jdisplay;

import com.mindprod.common11.Build;
import com.mindprod.common11.VersionCheck;
import com.mindprod.common13.Common13;
import com.mindprod.common13.HybridJ;
import com.mindprod.common13.JEButton;
import com.mindprod.common15.FontFactory;
import com.mindprod.jtokens.Token;
import com.mindprod.jtokens.TokenFonts;

import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JScrollPane;
import java.awt.Color;
import java.awt.Container;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.zip.GZIPInputStream;

import static java.lang.System.err;
import static java.lang.System.out;

/**
 * Displays a serialised pre-parsed Java program in fancy fonts and colours.
 * <p/>
 * takes parm url = relative or absolute url of
 * the *.java file without the .ser.
 * <p/>
 * jdisplay  is an applet to render large snippets. jdisplayaux  handles inserting code into the HTML for htmlmacros.
 * jprep parses the snippet.
 *
 * @author Roedy Green, Canadian Mind Products
 * @version 4.5 2011-02-03 tidy up code, fix bug stopping B&W version from scrolling.
 * @since 2004
 */
public final class JDisplay extends JApplet
    {
    // ------------------------------ CONSTANTS ------------------------------

    /**
     * true if want additional debugging info
     */
    private static final boolean DEBUGGING = false;

    /**
     * should we use line numbers?
     */
    private static final boolean HAS_LINE_NUMBERS_DEFAULT = false;

    /**
     * height of applet box in pixels. Does not include surrounding frame. Only useful when run as an application.
     */
    private static final int APPLET_HEIGHT = 720;

    /**
     * Width of applet box in pixels. Only useful when run as an application.
     */
    private static final int APPLET_WIDTH = 960;

    /**
     * version number for this class
     */
    public static final long serialVersionUID = 30;

    /**
     * undisplayed copyright notice
     *
     * @noinspection UnusedDeclaration
     */
    public static final String EMBEDDED_COPYRIGHT =
            "Copyright: (c) 2004-2012 Roedy Green, Canadian Mind Products, http://mindprod.com";

    /**
     * @noinspection UnusedDeclaration
     */
    private static final String RELEASE_DATE = "2011-02-03";

    /**
     * Title
     */
    private static final String TITLE_STRING = "JDisplay";

    /**
     * Version, is no About box.
     */
    private static final String VERSION_STRING = "4.5";

    /**
     * used for background of bar userWantsColor
     */
    private static final Color BACKGROUND_FOR_APPLET = Build.BACKGROUND_FOR_BLENDING;

    // ------------------------------ FIELDS ------------------------------

    /**
     * plain B & W TextArea to display on for copy/paste.
     */
    private BWTokenPanel bwTokenPanel;

    /**
     * Display with colours, as bit image.
     */
    private ColorTokenPanel colorTokenPanel;

    /**
     * contentPane for JApplet, not local as usual.
     */
    private Container contentPane;

    /**
     * payload size information.
     */
    private Footprint footprint;

    /**
     * button to click to download source.
     */
    private JButton download;

    /**
     * check to display in userWantsColor/B&W.
     */
    private JCheckBox userWantsColor;

    /**
     * use Line numbers?  End user decides
     */
    private JCheckBox userWantsLineNumbers;

    /**
     * scrolls BWTokenPanel
     */
    private JScrollPane bwTokenPanelScroller;

    /**
     * scrolls colorTokenPanel
     */
    private JScrollPane colorTokenPanelScroller;

    /**
     * e.g.  Myprog.java  (no lead snippet/ser or trail .ser .
     */
    private String snippetName;

    /**
     * The list of tokens
     */
    private Token[] tokens;

    /**
     * true if running as Applet, false as as application.
     */
    private final boolean asApplet;

    /**
     * true if want control bar on top. Controlled by an Applet bar parameter. Nearly always true.
     */
    private boolean hasBar = true;

    /**
     * true after bwTokenPanel has been loaded with tokens
     */
    private boolean hasBWPlainTextBeenLoaded = false;

    // -------------------------- PUBLIC INSTANCE  METHODS --------------------------

    /**
     * Default constructor when started as an Applet.
     */
    public JDisplay()
        {
        this.asApplet = true;
        this.hasBar = true;
        }

    /**
     * Called by the browser or applet viewer to inform
     * this applet that it is being reclaimed and that it should destroy
     * any resources that it has allocated.
     *
     * @override
     */
    public void destroy()
        {
        if ( userWantsColor != null )
            {
            contentPane.remove( userWantsColor );
            userWantsColor = null;
            }
        if ( download != null )
            {
            contentPane.remove( download );
            download = null;
            }
        if ( userWantsLineNumbers != null )
            {
            contentPane.remove( userWantsLineNumbers );
            userWantsLineNumbers = null;
            }
        if ( colorTokenPanelScroller != null && colorTokenPanel != null )
            {
            colorTokenPanelScroller.remove( colorTokenPanel );
            colorTokenPanel = null;
            }
        if ( colorTokenPanelScroller != null )
            {
            contentPane.remove( colorTokenPanelScroller );
            colorTokenPanelScroller = null;
            }
        if ( bwTokenPanelScroller != null && bwTokenPanel != null )
            {
            bwTokenPanelScroller.remove( bwTokenPanel );
            bwTokenPanel = null;
            }
        if ( bwTokenPanelScroller != null )
            {
            contentPane.remove( bwTokenPanelScroller );
            bwTokenPanelScroller = null;
            }
        }

    /**
     * Called by the browser or applet viewer to inform
     * this applet that it has been loaded into the system.
     *
     * @override
     */
    @Override
    public void init()
        {
        if ( !VersionCheck.isJavaVersionOK( 1, 5, 0, this ) )
            {
            // effectively abort
            return;
            }
        Common13.setLaf();
        // helps track bugs to know version customer was using
        if ( DEBUGGING )
            {
            out.println( "initialising "
                         + TITLE_STRING
                         + " "
                         + VERSION_STRING
                         + " released:"
                         + RELEASE_DATE
                         + " build:"
                         + Build.BUILD_NUMBER
                         + " in Java "
                         + System.getProperty( "java.version",
                    "unknown" ) );
            }
        if ( asApplet )
            {
            getParams();
            }
        contentPane = this.getContentPane();
        contentPane.setLayout( new GridBagLayout() );
        contentPane.setBackground( BACKGROUND_FOR_APPLET );// make it blend into CMP background
        buildComponents();
        // add components:
        layoutGridBag();
        addListeners();
        }// end init

    /**
     * make sure the pretty version is displaying.
     */
    public void start()
        {
        if ( userWantsColor != null )
            {
            userWantsColor.setSelected( true );
            redisplay();
            }
        }

    // --------------------------- CONSTRUCTORS ---------------------------

    /**
     * Constructor for when running from command line.
     *
     * @param snippetName bare name of snippet, no lead snippet/ser or trailing .ser
     */
    private JDisplay( String snippetName )
        {
        this.snippetName = snippetName;
        this.asApplet = false;
        this.hasBar = true;
        }

    // -------------------------- OTHER METHODS --------------------------

    /**
     * hook up the listeners
     */
    private void addListeners()
        {
        // if click on pretty image, get plain textarea can be cut/pasted
        colorTokenPanel.addMouseListener( new MouseAdapter()
        {
        /**
         * If user clicks colour display as if to copy /paste, flip to bw
         *
         * @param event details of event
         */
        public void mouseClicked( MouseEvent event )
            {
            userWantsColor.setSelected( false );
            userWantsLineNumbers.setSelected( false );
            redisplay();
            }// end mouseClicked
        }// end anonymous class
        );// end addMouseListener line
        ItemListener theListener = new ItemListener()
        {
        /**
         * Notice any change to one of the list box selectors.
         *
         * @param event details of just what the user clicked.
         */
        public void itemStateChanged( ItemEvent event )
            {
            // we are interested it both SELECTED and DESELECTED events
            redisplay();
            }
        };
        // hook up so display will change if any widgets touched.
        userWantsColor.addItemListener( theListener );
        // hook up so display will change if any widgets touched.
        userWantsLineNumbers.addItemListener( theListener );
        download.addActionListener( new ActionListener()
        {
        /**
         * Notice any change to one of the list box selectors.
         *
         * @param event details of just what the user clicked.
         */
        public void actionPerformed( ActionEvent event )
            {
            download.setEnabled( false );
            download();
            download.setEnabled( true );
            }// end actionPerformed
        } );
        }

    /**
     * allocate GUI components
     */
    private void buildComponents()
        {
        userWantsLineNumbers = new JCheckBox( "line numbers", HAS_LINE_NUMBERS_DEFAULT );
        userWantsColor = new JCheckBox( "Color", true );
        download = new JEButton( "download" );
        // leave background default, smaller text that usual
        download.setFont( FontFactory.build( "Dialog", Font.BOLD, 12 ) );
        // Allocate the BWTokenPanel now, but don't populate it with tokens unless
        // we have to.
        bwTokenPanel = new BWTokenPanel();
        bwTokenPanel.setBackground( Color.WHITE );
        bwTokenPanel.setFont( FontFactory.build( "monospaced",
                Font.PLAIN,
                TokenFonts.NORMAL_FONT_SIZE_IN_POINTS ) );
        // don't load bwTokenPanel with tokens until until needed.
        bwTokenPanel.setVisible( false );
        colorTokenPanel = new ColorTokenPanel();
        colorTokenPanel.setBackground( Color.WHITE );
        colorTokenPanel.setVisible( false );
        // gets tokens
        fetchTokens();
        colorTokenPanel.setTokens( tokens, footprint.totalLines );
        // Decide if we need scrollbars
        // See if it will fit without:
        // Recompute with our font metrics
        footprint.s2CalcPayloadFootprint( tokens, this );
        //  footprint.s3CalcFat( tokens ) not needed since we are rendering with Applet.
        footprint.s4CalcScrollableFootprint( Rendering.APPLET );
        footprint.s5CalcIdealAppletFootPrint( Rendering.APPLET,
                hasBar,
                HAS_LINE_NUMBERS_DEFAULT,
                false
                /* hscroll */,
                false
                /* vscroll */,
                1.0f
                /* no safety factor, we know exact metrics now */ );
        boolean horBars = this.getWidth() < footprint.idealAppletWidth;
        boolean vertBars = this.getHeight() < footprint.idealAppletHeight;
        // we dont set values in the colorTokenPanel until later, in
        // start/redisplay.
        colorTokenPanelScroller =
                new JScrollPane( colorTokenPanel, vertBars ?
                                                  JScrollPane.VERTICAL_SCROLLBAR_ALWAYS :
                                                  JScrollPane.VERTICAL_SCROLLBAR_NEVER,
                        horBars ?
                        JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS :
                        JScrollPane.HORIZONTAL_SCROLLBAR_NEVER );
        // controls how fast you scroll with the wheelmouse.
        colorTokenPanelScroller.getVerticalScrollBar().setUnitIncrement( Geometry.LEADING_PX );
        colorTokenPanelScroller.setVisible( false );
        bwTokenPanelScroller =
                new JScrollPane( bwTokenPanel, vertBars ?
                                               JScrollPane.VERTICAL_SCROLLBAR_ALWAYS :
                                               JScrollPane.VERTICAL_SCROLLBAR_NEVER,
                        horBars ?
                        JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS :
                        JScrollPane.HORIZONTAL_SCROLLBAR_NEVER );
        // controls how fast you scroll with the wheelmouse.
        bwTokenPanelScroller.getVerticalScrollBar().setUnitIncrement( Geometry.LEADING_PX );
        bwTokenPanelScroller.setVisible( false );
        // both b&w and Color are invisible at this point.
        }

    /**
     * download java source, DOWNLOAD not display. Let user capture to disk. cannot do FileChooser and Write in unsigned
     * applet. so get browser to do it.
     */
    private void download()
        {
        try
            {
            final URL url;
            if ( asApplet )
                {
                // get original document, not the *.ser
                url = new URL( getDocumentBase(), "snippet/" + snippetName );
                getAppletContext().showDocument( url );
                }
            else
                {
                out.println( "download ignored in local mode." );
                // url = new URL( snippetName );
                // we ignore the download if running standalone.
                // Ideally we should do some download dialog here.
                }
            }
        catch ( MalformedURLException e )
            {
            err.println();
            e.printStackTrace( err );
            // no .ser, this is the human readable fragment
            err.println( "\007problem downloading " + snippetName );
            err.println();
            }
        }// end download

    /**
     * Gets an array of pre-parsed serialized tokens from website representing this program. Gets from snippetName.
     * Leaves the array in tokens.
     */
    private void fetchTokens()
        {
        // Read serialised tokens from a compressed URL.
        tokens = null;
        // O P E N
        // Generate an HTTP GET Command
        final ObjectInputStream ois;
        footprint = null;
        try
            {
            // O P E N
            final URL url;
            // get corresponding *.ser
            if ( asApplet )
                {
                url =
                        new URL( getDocumentBase(),
                                "snippet/ser/" + snippetName + ".ser" );
                }
            else
                {
                url = new URL( "file:snippet/ser/" + snippetName + ".ser" );
                }
            // out.println( "fetching: " + url );
            final URLConnection urlc = url.openConnection();
            if ( urlc == null )
                {
                throw new IOException(
                        "\007ailed to connect to document server." );
                }
            urlc.setAllowUserInteraction( false );
            urlc.setDoInput( true );
            urlc.setDoOutput( false );
            urlc.setUseCaches( false );
            urlc.connect(); // ignored if already connected.
            final InputStream is = urlc.getInputStream();
            final GZIPInputStream gzis =
                    new GZIPInputStream( is, 4096/* buffsize */ );
            ois = new ObjectInputStream( gzis );
            // R E A D, footprintversion, footprint, tokens
            long expectedVersion = Footprint.serialVersionUID;
            long fileVersion = ( Long ) ois.readObject();
            if ( fileVersion != expectedVersion )
                {
                err.println( "\007Stale "
                             + snippetName
                             + " *.ser files are version  "
                             + fileVersion
                             + ". JDisplay is expecting  "
                             + expectedVersion );
                ois.close();
                tokens = new Token[ 0 ];
                return;
                }
            // we have to recompute it with our font metrics, but we want the
            // totalLines count.
            footprint = ( Footprint ) ois.readObject();
            tokens = ( Token[] ) ois.readObject();
            // C L O S E
            ois.close();
            }
        catch ( InvalidClassException e )
            {
            err.println( "\007Stale " + snippetName );
            }
        catch ( ClassNotFoundException e )
            {
            err.println( "\007Bug: Token class files missing from jar " + e.getMessage() );
            }
        catch ( IOException e )
            {
            err.println();
            e.printStackTrace( err );
            err.println( "\007Problem getting compacted source document " + snippetName );
            err.println();
            }
        if ( tokens == null )
            {
            tokens = new Token[ 0 ];
            }
        }

    /**
     * Get applet optional boolean parameter
     *
     * @param paramName    Name of the parameter. Case insensitive.
     * @param defaultValue default if param is missing.
     *
     * @return Value of the parameter from the applet true or false
     * @noinspection SameParameterValue
     */
    private boolean getBooleanParameter( String paramName,
                                         boolean defaultValue )
        {
        String boolString = getParameter( paramName );
        if ( boolString == null )
            {
            return defaultValue;
            }
        else
            {
            if ( boolString.equalsIgnoreCase( "true" )
                 || boolString.equalsIgnoreCase( "yes" )
                 || boolString.equalsIgnoreCase( "t" )
                 || boolString.equalsIgnoreCase( "y" ) )
                {
                return true;
                }
            else if ( boolString.equalsIgnoreCase( "false" )
                      || boolString.equalsIgnoreCase( "no" )
                      || boolString.equalsIgnoreCase( "yes" )
                      || boolString.equalsIgnoreCase( "f" )
                      || boolString.equalsIgnoreCase( "n" ) )
                {
                return false;
                }
            else
                {
                throw new IllegalArgumentException( "JDisplay: "
                                                    + paramName
                                                    + " param: "
                                                    + boolString
                                                    + " should be true or false." );
                }
            }
        }// getBooleanParameter

    /**
     * Get parameters from Applet, but only when running as Applet
     */
    private void getParams()
        {
        // We are in Applet, don't have parms yet.
        this.snippetName = getParameter( "snippet" );
        if ( this.snippetName == null )
            {
            throw new IllegalArgumentException( "missing snippet parameter" );
            }
        // should not have leading snippet/ser/
        if ( this.snippetName.startsWith( "snippet/" ) )
            {
            this.snippetName =
                    this.snippetName.substring( "snippet/".length() );
            }
        if ( this.snippetName.startsWith( "ser/" ) )
            {
            this.snippetName = this.snippetName.substring( "ser/".length() );
            }
        // in Java 1.6 the above code fails to prepend.
        // can't use assert. This code has to compile under JDK 1.2
        hasBar = getBooleanParameter( "bar", true );
        }

    /**
     * layout the components
     */
    /**
     * layout components
     */
    private void layoutGridBag()
        {
        /*  layout
        * ----0--------1--------2--------
        * userWantsColor--userWantsLineNumbers--download-- 0
        * pretty ------------------------ 1
        * plain ------------------------- 2
        */
        if ( hasBar )
            {
            /*
             * The bar is not a component, just the camouflage Applet background
             * showing through.
            */
            // x y w h wtx wty anchor fill T L B R padx pady
            contentPane.add( userWantsColor,
                    new GridBagConstraints( 0,
                            0,
                            1,
                            1,
                            0.0,
                            0.0,
                            GridBagConstraints.NORTHWEST,
                            GridBagConstraints.NONE,
                            new Insets( 0, 0, 2, 0 ),
                            0,
                            0 ) );
            // x y w h wtx wty anchor fill T L B R padx pady
            contentPane.add( userWantsLineNumbers,
                    new GridBagConstraints( 1,
                            0,
                            1,
                            1,
                            0.0,
                            0.0,
                            GridBagConstraints.NORTH,
                            GridBagConstraints.NONE,
                            new Insets( 0, 10, 2, 0 ),
                            0,
                            0 ) );
            // x y w h wtx wty anchor fill T L B R padx pady
            contentPane.add( download,
                    new GridBagConstraints( 2,
                            0,
                            1,
                            1,
                            0.0,
                            0.0,
                            GridBagConstraints.NORTHEAST,
                            GridBagConstraints.NONE,
                            new Insets( 0, 10, 2, 0 ),
                            0,
                            0 ) );
            }
        // x y w h wtx wty anchor fill T L B R padx pady
        contentPane.add( colorTokenPanelScroller,
                new GridBagConstraints( 0,
                        1,
                        3,
                        1,
                        1.0,
                        1.0,
                        GridBagConstraints.CENTER,
                        GridBagConstraints.BOTH,
                        new Insets( 0, 0, 0, 0 ),
                        0,
                        0 ) );
        // x y w h wtx wty anchor fill T L B R padx pady
        contentPane.add( bwTokenPanelScroller,
                new GridBagConstraints( 0,
                        2
                        /* place it beside colorTokenPanelScroller, though actually only one visible at a time */,
                        3,
                        1,
                        1.0,
                        1.0,
                        GridBagConstraints.CENTER,
                        GridBagConstraints.BOTH,
                        new Insets( 0, 0, 0, 0 ),
                        0,
                        0 ) );
        }

    /**
     * refresh the display based on whether should use userWantsColor and userWantsLineNumbers
     */
    private void redisplay()
        {
        final boolean useColor = userWantsColor.isSelected();
        final boolean useLineNumbers = this.userWantsLineNumbers.isSelected();
        // can't have line numbers in B&W state, user would not want to copy/paste them.
        final int width = useLineNumbers
                          ? footprint.scrollableWidthWithLineNumbers
                          : footprint.scrollableWidthWithoutLineNumbers;
        final int height = footprint.scrollableHeight;
        /*
        * Tell the colorTokenPanel how we want the tokens rendered.
        */
        if ( useColor )
            {
            // leave B&W connected, just not visible.
            colorTokenPanel.set( width,
                    height,
                    useLineNumbers,
                    footprint.lineNumberWidthInPixels );  // invalidates
            this.userWantsLineNumbers.setEnabled( true );
            this.userWantsLineNumbers.setVisible( true );
            bwTokenPanelScroller.setVisible( false );
            colorTokenPanelScroller.setVisible( true );
            bwTokenPanel.setVisible( false );
            colorTokenPanel.setVisible( true );
            }
        else
            {
            // B&W
            // Leave userWantsColor connected, just not visible.
            this.userWantsLineNumbers.setEnabled( false );
            this.userWantsLineNumbers.setVisible( false );
            this.userWantsLineNumbers.setSelected( false );
            if ( !hasBWPlainTextBeenLoaded )
                {
                bwTokenPanel.setTokens( tokens );
                hasBWPlainTextBeenLoaded = true;
                }
            colorTokenPanelScroller.setVisible( false );
            bwTokenPanelScroller.setVisible( true );
            colorTokenPanel.setVisible( false );
            bwTokenPanel.setVisible( true );
            }
        // no need for invalidate/validate/repaint.
        }

    // --------------------------- main() method ---------------------------

    /**
     * Allow this applet to run as as application as well.
     *
     * @param args url of text file to display e.g. abs.example1.javafrag . CWD must be E:\mindprod\jgloss\
     */
    public static void main( String args[] )
        {
        if ( args.length == 0 )
            {
            throw new IllegalArgumentException( "missing snippet parameter" );
            }
        HybridJ.fireup( new JDisplay( args[ 0 ] ),
                TITLE_STRING + " " + VERSION_STRING,
                APPLET_WIDTH,
                APPLET_HEIGHT );
        }// end main
    }

