package editor;

/*
 * Based on TextComponentDemo.java f
 */
import java.awt.*;
import java.awt.event.*;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.HashMap;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.event.*;
import javax.swing.filechooser.FileFilter;
import javax.swing.undo.*;
import parser.MackASM;


public class  MiniTextEditor  extends JFrame  implements WindowListener
	{
	//
	// Constants
	//
  private static final Dimension  FRAME_DIMENSION = new Dimension( 600, 800 );
	private static final String 	  newline 				= "\n";  // Should we use this? System.getProperty("line.separator")
	private static final String  	  APP_NAME 				= "Mack Asembler";
  private static final String     INFILE_SUFFIX     = ".asm";
  private static final String     OUTFILE_SUFFIX    = ".mack";
	private static final SimpleAttributeSet		DEFAULT_FONT = new SimpleAttributeSet();
		{ StyleConstants.setFontFamily( DEFAULT_FONT, "SansSerif" );
		  StyleConstants.setFontSize( DEFAULT_FONT, 12 ); }
	//
	// Member Data
	//
	public static MiniTextEditor 		frame;
	private File  									file = null;
	private boolean									dirty = false;
	private JTextPane 							textPane;
	private AbstractDocument 				doc;
	private JTextArea 							changeLog;
	private HashMap<Object, Action> actions;

	// undo and other menu helpers
	protected UndoAction 						undoAction;
	protected RedoAction 						redoAction;
	protected UndoManager 					undo = new UndoManager();
	protected SaveAction            saveAction;
	
	// Parser stuff
	private MackASM  								parser = null;
	private Mack                    mack = null;

	//
	// Constructor
	//
	public MiniTextEditor()
		{
		super( APP_NAME );
		setPreferredSize( FRAME_DIMENSION );
		
		// Create the text pane and configure it.
		textPane = new JTextPane();
		textPane.setCaretPosition( 0 );
		textPane.setMargin( new Insets( 5, 5, 5, 5 ) );
		StyledDocument styledDoc = textPane.getStyledDocument();
		if( styledDoc instanceof AbstractDocument )
			{
			doc = (AbstractDocument) styledDoc;
			}
		else
			{
			System.err.println( "Text pane's document isn't an AbstractDocument!" );
			System.exit( -1 );
			}
		JScrollPane  scrollPane = new JScrollPane( textPane );
		scrollPane.setPreferredSize( new Dimension(FRAME_DIMENSION.width, FRAME_DIMENSION.height / 2) );

		// Create the text area for the status log and configure it.
		changeLog = new JTextArea( 5, 30 );
		changeLog.setEditable( false );
		JScrollPane scrollPaneForLog = new JScrollPane( changeLog );

		// Create a split pane for the change log and the text area.
		JSplitPane  splitPane = new JSplitPane( JSplitPane.VERTICAL_SPLIT,
				scrollPane, scrollPaneForLog );
		splitPane.setOneTouchExpandable( true );

		// Create the status area.
		JPanel statusPane = new JPanel( new GridLayout( 1, 1 ) );
		CaretListenerLabel caretListenerLabel = new CaretListenerLabel(
				"Caret Status" );
		statusPane.add( caretListenerLabel );

		// Add the components.
		getContentPane().add( splitPane, BorderLayout.CENTER );
		getContentPane().add( statusPane, BorderLayout.PAGE_END );

		// Set up the menu bar.
		actions = createActionTable( textPane );
		JMenu fileMenu  = createFileMenu();
		JMenu editMenu  = createEditMenu();
		JMenu asmMenu   = createAsmMenu();
		JMenu styleMenu = createStyleMenu();
		JMenuBar mb = new JMenuBar();
		mb.add( fileMenu );
		mb.add( editMenu );
		mb.add( asmMenu );
		mb.add( styleMenu );
		setJMenuBar( mb );

		// Add some key bindings.
		addBindings();

		// Put the initial text into the text pane.
		//textPane.setCaretPosition( 0 );

		// Start watching for undoable edits and caret changes.
		doc.addUndoableEditListener( new MyUndoableEditListener() );
		textPane.addCaretListener( caretListenerLabel );
		doc.addDocumentListener( new MyDocumentListener() );
		}

	
	//
	// Helper classes and Methods
	//
	
	// This listens for and reports caret movements.
	protected class CaretListenerLabel extends JLabel implements CaretListener
		{
		public CaretListenerLabel( String label )
			{
			super( label );
			}

		// Might not be invoked from the event dispatch thread.
		public void caretUpdate( CaretEvent e )
			{
			displaySelectionInfo( e.getDot(), e.getMark() );
			}

		// This method can be invoked from any thread. It
		// invokes the setText and modelToView methods, which
		// must run on the event dispatch thread. We use
		// invokeLater to schedule the code for execution
		// on the event dispatch thread.
		protected void displaySelectionInfo( final int dot, final int mark )
			{
			SwingUtilities.invokeLater( new Runnable()
				{
					public void run()
						{
						byte[] text;
            try
              {
              text = doc.getText( 0, Math.min(dot, doc.getLength()) ).getBytes();
              }
            catch( BadLocationException e ) { text = new byte[0]; }
					  int  line = 1;
					  
					  // Loop thru all characters of the document counting "New Lines".
					  // This might sound slow, but computers are really fast these days.
					  for( byte b : text )
					    if( b == '\n' )
					      line++;
					  setText( "line: " + line );
						}
				} );
			}
		}

	// This one listens for edits that can be undone.
	protected class MyUndoableEditListener implements UndoableEditListener
		{
		public void undoableEditHappened( UndoableEditEvent e )
			{
			// Remember the edit and update the menus.
			undo.addEdit( e.getEdit() );
			undoAction.updateUndoState();
			redoAction.updateRedoState();
			}
		}

	// And this one listens for any changes to the document.
	protected class MyDocumentListener implements DocumentListener
		{
		public void insertUpdate( DocumentEvent e )
			{
			dirty = true;
			displayEditInfo( e );
			}

		public void removeUpdate( DocumentEvent e )
			{
			dirty = true;
			displayEditInfo( e );
			}

		public void changedUpdate( DocumentEvent e )
			{
			dirty = true;
			displayEditInfo( e );
			}

		private void displayEditInfo( DocumentEvent e )
			{
//			Document document = e.getDocument();
//			int changeLength = e.getLength();
//			changeLog.append( e.getType().toString() + ": " + changeLength
//					+ " character" + ((changeLength == 1) ? ". " : "s. ")
//					+ " Text length = " + document.getLength() + "." + newline );
			}
		}
	
	// Add a couple of emacs key bindings for navigation.
	protected void addBindings()
		{
		KeyStroke key;
		InputMap 	inputMap = textPane.getInputMap();
		
		// Ctrl-b to go backward one character
		key = KeyStroke.getKeyStroke( KeyEvent.VK_B, Event.CTRL_MASK );
		inputMap.put( key, DefaultEditorKit.backwardAction );

		// Ctrl-f to go forward one character
		key = KeyStroke.getKeyStroke( KeyEvent.VK_F, Event.CTRL_MASK );
		inputMap.put( key, DefaultEditorKit.forwardAction );

		// Ctrl-p to go up one line
		key = KeyStroke.getKeyStroke( KeyEvent.VK_P, Event.CTRL_MASK );
		inputMap.put( key, DefaultEditorKit.upAction );

		// Ctrl-n to go down one line
		key = KeyStroke.getKeyStroke( KeyEvent.VK_N, Event.CTRL_MASK );
		inputMap.put( key, DefaultEditorKit.downAction );
		}

	// Create the File menu.
	protected JMenu createFileMenu()
		{
		JMenu menu = new JMenu( "File" );

		// Create the "New" item.
		JMenuItem  newMenuItem = new JMenuItem();
		newMenuItem.setText( "New" );
		newMenuItem.addActionListener( new ActionListener()
      {
      public void actionPerformed(ActionEvent e)
      		{
      		// Simulate clicking on the "close" button.
          try
						{
						doc.remove( 0, doc.getLength() );
						dirty = false;
						file = null;
				    saveAction.updateState(); // Disable "Save" menu item.
						}
					catch( BadLocationException e1 ) {}
          } 
      } );
		menu.add( newMenuItem );

		// Create the "Open...", "Save" and "Save As..." items.
		menu.add( new OpenAction() );
		saveAction = new SaveAction();
		menu.add( saveAction );
		menu.add( new SaveAsAction() );

		menu.addSeparator();
		
		// Create the "Exit" item.
		JMenuItem  exitMenuItem = new JMenuItem();
    exitMenuItem.setText( "Exit" );
    exitMenuItem.addActionListener( new ActionListener()
      {
      public void actionPerformed(ActionEvent e)
      		{
      		// Simulate clicking on the "close" button.
          dispose(); 
          } 
      } );
		menu.add( exitMenuItem );
		return menu;
		}

	
	// Create the edit menu.
	protected JMenu createEditMenu()
		{
		JMenu menu = new JMenu( "Edit" );

		// Undo and redo are actions of our own creation.
		undoAction = new UndoAction();
		menu.add( undoAction );

		redoAction = new RedoAction();
		menu.add( redoAction );

		menu.addSeparator();

		// These actions come from the default editor kit.
		// Get the ones we want and stick them in the menu.
		menu.add( getActionByName( DefaultEditorKit.cutAction ) );
		menu.add( getActionByName( DefaultEditorKit.copyAction ) );
		menu.add( getActionByName( DefaultEditorKit.pasteAction ) );

		menu.addSeparator();

		menu.add( getActionByName( DefaultEditorKit.selectAllAction ) );
		return menu;
		}

	// Create the Asm menu.
	protected JMenu createAsmMenu()
		{
		JMenu menu = new JMenu( "Asm" );

		// Create the "Compile" item.
		JMenuItem  compileMenuItem = new JMenuItem();
		compileMenuItem.setText( "Compile" );
		compileMenuItem.addActionListener( new ActionListener()
      {
      public void actionPerformed(ActionEvent e)
      		{
					String					msg = "";
      		// Run the parser.
          try
						{
						// Turn the "doc" text in to an character stream. Add a New Line incase it ends with a comment.
						StringReader 	sr = new java.io.StringReader( doc.getText(0, doc.getLength()) + "\n" );
						Reader 				r = new java.io.BufferedReader( sr );
						
						// Give the string to the parser.
						if( parser == null )
							parser = new MackASM( r );
						else
							MackASM.ReInit( r );
						
						//
						// Parse the file.
						//
						mack = new Mack();
						MackASM.MackASMdoc( mack );
						
						// Great, everything worked so far. Now fix the forward references.
						mack.backPatch();
						
						msg += "Compilation Successful :-)" + newline + newline;
						msg += mack.objCode;
						mack.dump();
						}
		      catch( Exception  exception ) 
		      	{
            msg += "Compilation Failed :-(" + newline + newline;
		      	msg += "ERROR before \"";
            msg += MackASM.getNextToken().image + " ";
            msg += MackASM.getNextToken().image + " ";
            msg += MackASM.getNextToken().image + " ";
            msg += MackASM.getNextToken().image + "\"." + newline;
		      	msg += exception.getMessage() + newline;
		      	mack = null;
			      }
		      catch( Error  error ) 
		      	{
            msg += "Compilation Failed :-(" + newline + newline;
            msg += "Line: " + MackASM.getNextToken().beginLine + newline;
		      	msg += error.getMessage() + newline;
		      	mack = null;
		      	}
					
					changeLog.setText( msg );
					changeLog.setCaretPosition( 0 );
          } 
      } );
		menu.add( compileMenuItem );
		
	  // Create the "Write Object..." item.
    JMenuItem  writeObjMenuItem = new JMenuItem();
    writeObjMenuItem.setText( "Write Object..." );
    writeObjMenuItem.addActionListener( new ActionListener()
      {
      public void actionPerformed(ActionEvent e)
        {
        if( mack == null )
          JOptionPane.showMessageDialog( frame, "Must compile successfully before write output." );
        
        File  newFile = saveAsDialogBox( OUTFILE_SUFFIX, MiniTextEditor.APP_NAME + " Output Files" );
        if( newFile != null )
          // Write out the object file.
          mack.write( newFile );
        } 
      } );
    menu.add( writeObjMenuItem );
		return menu;
		}

	// Create the style menu.
	protected JMenu createStyleMenu()
		{
		JMenu menu = new JMenu( "Style" );

		Action action = new StyledEditorKit.BoldAction();
		action.putValue( Action.NAME, "Bold" );
		menu.add( action );

		action = new StyledEditorKit.ItalicAction();
		action.putValue( Action.NAME, "Italic" );
		menu.add( action );

		action = new StyledEditorKit.UnderlineAction();
		action.putValue( Action.NAME, "Underline" );
		menu.add( action );

		menu.addSeparator();

		menu.add( new StyledEditorKit.FontSizeAction( "12", 12 ) );
		menu.add( new StyledEditorKit.FontSizeAction( "14", 14 ) );
		menu.add( new StyledEditorKit.FontSizeAction( "18", 18 ) );

		menu.addSeparator();

		menu.add( new StyledEditorKit.FontFamilyAction( "Serif", "Serif" ) );
		menu.add( new StyledEditorKit.FontFamilyAction( "SansSerif", "SansSerif" ) );

		menu.addSeparator();

		menu.add( new StyledEditorKit.ForegroundAction( "Red", Color.red ) );
		menu.add( new StyledEditorKit.ForegroundAction( "Green", Color.green ) );
		menu.add( new StyledEditorKit.ForegroundAction( "Blue", Color.blue ) );
		menu.add( new StyledEditorKit.ForegroundAction( "Black", Color.black ) );

		return menu;
		}


	// The following two methods allow us to find an
	// action provided by the editor kit by its name.
	private HashMap<Object, Action>  createActionTable(
			JTextComponent textComponent )
		{
		HashMap<Object, Action>  newActions = new HashMap<Object, Action>();
		Action[] actionsArray = textComponent.getActions();
		for( int i = 0; i < actionsArray.length; i++ )
			{
			Action a = actionsArray[i];
			String  name = (String)a.getValue( Action.NAME );
			newActions.put( name, a );
			// Change a few of the menu item names.
			if( name == DefaultEditorKit.cutAction )
				{
				a.putValue( Action.NAME, "Cut" );
				//a.putValue( Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke( KeyEvent.VK_X, Event.CTRL_MASK, true ) );
				}
			else if( name == DefaultEditorKit.copyAction )
				{
				a.putValue( Action.NAME, "Copy" );
				//a.putValue( Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke( KeyEvent.VK_C, Event.CTRL_MASK, true ) );
				}
			else if( name == DefaultEditorKit.pasteAction )
				{
				a.putValue( Action.NAME, "Paste" );
				//a.putValue( Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke( KeyEvent.VK_V, Event.CTRL_MASK, true ) );
				}
			else if( name == DefaultEditorKit.selectAllAction )
				{
				a.putValue( Action.NAME, "Select All" );
				//a.putValue( Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke( KeyEvent.VK_A, Event.CTRL_MASK, true ) );
				}
			}
		return newActions;
		}

	private Action  getActionByName( String name )
		{
		return actions.get( name );
		}

	
	class OpenAction extends AbstractAction
	{
	public OpenAction()
		{
		super( "Open" );
		setEnabled( true );
		}

	public void actionPerformed( ActionEvent e )
		{
		// System properties directories:
		// "user.dir" User's current working directory (ie, location of executable)
		// "user.home" User home directory (ie, "My Documents")
		String wd = System.getProperty( "user.dir" );
		if( wd == null )
			return;

		JFileChooser  fc = new JFileChooser( wd );
		
		// Create a File Filter that will only show .asm files.
		FileFilter  filter = new FileFilter()
			{
			@Override
			public boolean accept( File f )
				{
				return  f.isDirectory()  ||  f.getName().endsWith( INFILE_SUFFIX );
				}

			@Override
			public String getDescription()
				{
				return APP_NAME + " Input  Files";
				}
			};
				
		fc.setFileFilter( filter );
		int rc = fc.showOpenDialog( frame );
		if( rc != JFileChooser.APPROVE_OPTION )
			// File chooser cancel button clicked
			return;

		file = fc.getSelectedFile();
    saveAction.updateState();
		if( file == null )
			return;

		// Do the open!
		try
			{
			BufferedReader  asmFile = new BufferedReader( new FileReader(file) );
			StringBuilder  body = new StringBuilder( 2000 ); // just guess at the size.

	    // Loop thru the configuration file line by line
			String  strLine;
	    while( (strLine = asmFile.readLine()) != null )
	    	{
	    	body.append( strLine );
	    	body.append( "\n" );
	    	}
	    if( body.length() > 0 )
	    	body.setLength( body.length()-1 ); // Remove trailing \n
	    
			// Replace all editor text with the file text.
	    doc.replace( 0, doc.getLength(), body.toString(), DEFAULT_FONT );
	    dirty = false;
			}
		catch( FileNotFoundException  fileNotFoundException )
			{
			JOptionPane.showMessageDialog( frame, "File not found: " + file.getName() );
			}
		catch( IOException  ioException )
			{
			JOptionPane.showMessageDialog( frame, "File not found: " + file.getName() );
			}
		catch( BadLocationException  badLocationException )
			{
			JOptionPane.showMessageDialog( frame, "Internal Error: Can't insert inital text." );
			}
		}
	}
	
	private void save()
		{
		if( file == null )
			{
	    saveAction.updateState(); // This should never happen.
			return;
			}
			
		try
			{
		  // Create or overwrite a file.
			BufferedWriter  asmFile = new BufferedWriter( new FileWriter(file) );
			asmFile.write( doc.getText(0, doc.getLength()) );
			asmFile.close();
			dirty = false;
			}
		catch( IOException e )
			{
			JOptionPane.showMessageDialog( frame, "Cannot write to: " + file.getName() );
			}
		catch( BadLocationException badLocationException )
			{
			JOptionPane.showMessageDialog( frame, "Internal error: Cannot get text." );
			}
		}
	
	class SaveAction extends AbstractAction
	{
	public SaveAction()
		{
		super( "Save" );
		setEnabled( false );
		}

	public void actionPerformed( ActionEvent e )
		{
		save();
		}
	
	protected void updateState()
		{
		setEnabled( file != null );
		}
	}
	
	
	private File  saveAsDialogBox( final String  fileSuffix, final String  fileDescription )
	  {
    // System properties directories:
    //   "user.dir"     User's current working directory
    //   "user.home"    User home directory (ie "My Documents")
    String wd = System.getProperty( "user.dir" );
    if( wd == null )
      {
      JOptionPane.showMessageDialog( frame, "Error: Can't get current directory." );
      return null;
      }
    
    JFileChooser  fc = new JFileChooser( wd );

    // Create a File Filter that will only show .asm files.
    FileFilter  filter = new FileFilter()
      {
      @Override
      public boolean accept( File f )
        {
        return  f.isDirectory()  ||  f.getName().endsWith( fileSuffix );
        }

      @Override
      public String getDescription()
        {
        return fileDescription;
        }
      };
    fc.setFileFilter( filter );
    
    int rc = fc.showDialog( frame, "Create New File" );
    if( rc != JFileChooser.APPROVE_OPTION )
      // File chooser cancel button clicked
      return null;

    // Create a new file. If all goes well, this is our return variable.
    File  newFile = fc.getSelectedFile();
    if( newFile == null )
      {
      JOptionPane.showMessageDialog( frame, "Error: Can't get selected file." );
      return null;
      }
    
    if( !newFile.getName().toLowerCase().endsWith( fileSuffix ) )
      newFile = new File( newFile.getName() + fileSuffix );
    
    // If they picked an existing file, ask them what to do.
    if( newFile.exists() )
      {
      String  msg = "File already exists: ";
      try
        {
        msg += newFile.getCanonicalPath();
        }
      catch( IOException ioException )
        {
        JOptionPane.showMessageDialog( frame, "I/O Error: " + ioException );
        }
      
      // Pop up a dialog box if the file does not exist.
      Object[]  choices = { "Overwrite", "Cancel" };
      int  retVal = JOptionPane.showOptionDialog( frame, msg, fileDescription, 
          JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, choices, choices[0] );
      if( retVal == 0  )
        newFile.delete();
      else if( retVal == 1  )
        return null; // Cancel
      }

    // Save and mark whole tree as clean (no changes since last save).
    if( newFile == null )
      {
      JOptionPane.showMessageDialog( frame, "Error opening file. " );
      return null;
      }
    return newFile;
	  }
	
	
	class SaveAsAction extends AbstractAction
	{
	public SaveAsAction()
		{
		super( "Save As..." );
		setEnabled( true );
		}

	public void actionPerformed( ActionEvent e )
		{
		File  newFile = saveAsDialogBox( INFILE_SUFFIX, MiniTextEditor.APP_NAME + " Input Files" );
		if( newFile != null )
		  {
  		file = newFile;
      saveAction.updateState();
  		save();
		  }
		}
	}
	
	
	class UndoAction extends AbstractAction
	{
	public UndoAction()
		{
		super( "Undo" );
		putValue( Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke( KeyEvent.VK_Z, Event.CTRL_MASK, true ) );
		setEnabled( false );
		}

	public void actionPerformed( ActionEvent e )
		{
		try
			{
			undo.undo();
			}
		catch( CannotUndoException ex )
			{
			System.out.println( "Unable to undo: " + ex );
			ex.printStackTrace();
			}
		updateUndoState();
		redoAction.updateRedoState();
		}

	protected void updateUndoState()
		{
		if( undo.canUndo() )
			{
			setEnabled( true );
			putValue( Action.NAME, undo.getUndoPresentationName() );
			}
		else
			{
			setEnabled( false );
			putValue( Action.NAME, "Undo" );
			}
		}
	}

	class RedoAction extends AbstractAction
		{
		public RedoAction()
			{
			super( "Redo" );
			putValue( Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke( KeyEvent.VK_Y, Event.CTRL_MASK, true ) );
			setEnabled( false );
			}

		public void actionPerformed( ActionEvent e )
			{
			try
				{
				undo.redo();
				}
			catch( CannotRedoException ex )
				{
				System.out.println( "Unable to redo: " + ex );
				ex.printStackTrace();
				}
			updateRedoState();
			undoAction.updateUndoState();
			}

		protected void updateRedoState()
			{
			if( undo.canRedo() )
				{
				setEnabled( true );
				putValue( Action.NAME, undo.getRedoPresentationName() );
				}
			else
				{
				setEnabled( false );
				putValue( Action.NAME, "Redo" );
				}
			}
		}

	//
	// WindowListener methods
	//
	public void windowActivated( WindowEvent e ) {}
	public void windowDeactivated( WindowEvent e ) {}
	public void windowDeiconified( WindowEvent e ) {}
	public void windowIconified( WindowEvent e ) {}
	public void windowOpened( WindowEvent e ) {}

	public void windowClosed( WindowEvent e )
		{
		System.exit( 0 );
		}

	public void windowClosing( WindowEvent e )
		{
		if( dirty )
			{
			if( file == null )
				{
  			// Pop up a dialog box if the file does not exist.
  			Object[]  choices = { "Exit", "Cancel" };
  			int  retVal = JOptionPane.showOptionDialog(	
  					frame, "The text has not been saved.", MiniTextEditor.APP_NAME + " File", 
  					JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, choices, choices[1] );
  			if( retVal == 1  ||  retVal == JOptionPane.DEFAULT_OPTION )
  				return; // Cancel
  			}
			else
				{
  			// Pop up a dialog box if the file does not exist.
  			Object[]  choices = { "Save", "Exit", "Cancel" };
  			int  retVal = JOptionPane.showOptionDialog(	
  					frame, "The text has changed.", MiniTextEditor.APP_NAME + " File", 
  					JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, choices, choices[2] );
  			// if( retVal == 0 ) then go ahead and open it anyway.
  			if( retVal == 0  )
  				save();
  			else if( retVal == 2  ||  retVal == JOptionPane.DEFAULT_OPTION )
  				return; // Cancel
  			}
			}
	  System.exit( 0 );
		}

	
	/**
	 * Create the GUI and show it. For thread safety, this method should be
	 * invoked from the event dispatch thread.
	 */
	private static void createAndShowGUI()
		{
		// Create and set up the window.
		frame = new MiniTextEditor();
		frame.addWindowListener( frame );
		frame.setDefaultCloseOperation( JFrame.DO_NOTHING_ON_CLOSE );

		// Display the window.
		frame.pack();
		frame.setVisible( true );
		}

	// The standard main method.
	public static void main( String[] args )
		{
		// Schedule a job for the event dispatch thread:
		// creating and showing this application's GUI.
		SwingUtilities.invokeLater( new Runnable()
			{
				public void run()
					{
					// Turn off metal's use of bold fonts
					UIManager.put( "swing.boldMetal", Boolean.FALSE );
					createAndShowGUI();
					}
			} );
		}

	}
