/*
 * JaamSim Discrete Event Simulation
 * Copyright (C) 2002-2011 Ausenco Engineering Canada Inc.
 * Copyright (C) 2016-2020 JaamSim Software Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jaamsim.ui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.FocusEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Rectangle2D;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.net.URL;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.prefs.Preferences;
import java.util.regex.Pattern;

import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JProgressBar;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.JWindow;
import javax.swing.KeyStroke;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import javax.swing.filechooser.FileNameExtensionFilter;

import com.jaamsim.Commands.Command;
import com.jaamsim.Commands.CoordinateCommand;
import com.jaamsim.Commands.DefineCommand;
import com.jaamsim.Commands.DefineViewCommand;
import com.jaamsim.Commands.DeleteCommand;
import com.jaamsim.Commands.KeywordCommand;
import com.jaamsim.Commands.RenameCommand;
import com.jaamsim.DisplayModels.TextModel;
import com.jaamsim.Graphics.BillboardText;
import com.jaamsim.Graphics.DirectedEntity;
import com.jaamsim.Graphics.DisplayEntity;
import com.jaamsim.Graphics.EntityLabel;
import com.jaamsim.Graphics.FillEntity;
import com.jaamsim.Graphics.LineEntity;
import com.jaamsim.Graphics.OverlayEntity;
import com.jaamsim.Graphics.OverlayText;
import com.jaamsim.Graphics.Region;
import com.jaamsim.Graphics.TextBasics;
import com.jaamsim.Graphics.TextEntity;
import com.jaamsim.Graphics.View;
import com.jaamsim.ProbabilityDistributions.RandomStreamUser;
import com.jaamsim.SubModels.CompoundEntity;
import com.jaamsim.basicsim.Entity;
import com.jaamsim.basicsim.ErrorException;
import com.jaamsim.basicsim.GUIListener;
import com.jaamsim.basicsim.JaamSimModel;
import com.jaamsim.basicsim.Simulation;
import com.jaamsim.controllers.RateLimiter;
import com.jaamsim.controllers.RenderManager;
import com.jaamsim.datatypes.IntegerVector;
import com.jaamsim.events.EventManager;
import com.jaamsim.events.EventTimeListener;
import com.jaamsim.input.ColourInput;
import com.jaamsim.input.Input;
import com.jaamsim.input.InputAgent;
import com.jaamsim.input.InputErrorException;
import com.jaamsim.input.KeywordIndex;
import com.jaamsim.input.Parser;
import com.jaamsim.math.Color4d;
import com.jaamsim.math.MathUtils;
import com.jaamsim.math.Vec3d;
import com.jaamsim.rng.MRG1999a;
import com.jaamsim.units.DimensionlessUnit;
import com.jaamsim.units.DistanceUnit;
import com.jaamsim.units.TimeUnit;
import com.jaamsim.units.Unit;

/**
 * The main window for a Graphical Simulation.  It provides the controls for managing then
 * EventManager (run, pause, ...) and the graphics (zoom, pan, ...)
 */
public class GUIFrame extends OSFixJFrame implements EventTimeListener, GUIListener {
	private static GUIFrame instance;

	private static JaamSimModel sim;

	private final ArrayList<View> views = new ArrayList<>();
	private int nextViewID = 1;

	// global shutdown flag
	static private AtomicBoolean shuttingDown;

	private JMenu fileMenu;
	private JMenu editMenu;
	private JMenu toolsMenu;
	private JMenu viewsMenu;
	private JMenu optionMenu;
	private JMenu unitsMenu;
	private JMenu helpMenu;
	private JToggleButton snapToGrid;
	private JToggleButton xyzAxis;
	private JToggleButton grid;
	private JCheckBoxMenuItem alwaysTop;
	private JCheckBoxMenuItem graphicsDebug;
	private JMenuItem printInputItem;
	private JMenuItem saveConfigurationMenuItem;  // "Save"
	private JLabel clockDisplay;
	private JLabel speedUpDisplay;
	private JLabel remainingDisplay;

	private JMenuItem undoMenuItem;
	private JMenuItem redoMenuItem;
	private JMenuItem copyMenuItem;
	private JMenuItem pasteMenuItem;
	private JMenuItem deleteMenuItem;

	private JToggleButton controlRealTime;
	private JSpinner spinner;

	private JButton fileSave;
	private JButton undo;
	private JButton redo;
	private JButton undoDropdown;
	private JButton redoDropdown;
	private final ArrayList<Command> undoList = new ArrayList<>();
	private final ArrayList<Command> redoList = new ArrayList<>();

	private JToggleButton showLabels;
	private JToggleButton showSubModels;

	private JToggleButton showLinks;
	private JToggleButton createLinks;
	private JButton nextButton;
	private JButton prevButton;
	private JToggleButton reverseButton;

	private JButton copyButton;
	private JButton pasteButton;
	private JToggleButton find;

	private JTextField dispModel;
	private JButton modelSelector;
	private JButton editDmButton;

	private JButton clearButton;

	private Entity selectedEntity;
	private ButtonGroup alignmentGroup;
	private JToggleButton alignLeft;
	private JToggleButton alignCentre;
	private JToggleButton alignRight;

	private JToggleButton bold;
	private JToggleButton italic;
	private JTextField font;
	private JButton fontSelector;
	private JTextField textHeight;
	private JButton largerText;
	private JButton smallerText;
	private ColorIcon colourIcon;
	private JButton fontColour;

	private JButton increaseZ;
	private JButton decreaseZ;

	private JToggleButton outline;
	private JSpinner lineWidth;
	private ColorIcon lineColourIcon;
	private JButton lineColour;

	private JToggleButton fill;
	private ColorIcon fillColourIcon;
	private JButton fillColour;

	private RoundToggleButton controlStartResume;
	private ImageIcon runPressedIcon;
	private ImageIcon pausePressedIcon;
	private RoundToggleButton controlStop;
	private JTextField pauseTime;
	private JTextField gridSpacing;

	private JLabel locatorPos;

	//JButton toolButtonIsometric;
	private JToggleButton lockViewXYPlane;

	private int lastValue = -1;
	private JProgressBar progressBar;
	private static Image iconImage;

	private static final RateLimiter rateLimiter;

	private static boolean SAFE_GRAPHICS;

	// Collection of default window parameters
	int DEFAULT_GUI_WIDTH;
	int COL1_WIDTH;
	int COL2_WIDTH;
	int COL3_WIDTH;
	int COL4_WIDTH;
	int COL1_START;
	int COL2_START;
	int COL3_START;
	int COL4_START;
	int HALF_TOP;
	int HALF_BOTTOM;
	int TOP_START;
	int BOTTOM_START;
	int LOWER_HEIGHT;
	int LOWER_START;
	int VIEW_HEIGHT;
	int VIEW_WIDTH;

	int VIEW_OFFSET = 50;

	private static final String LAST_USED_FOLDER = "";
	private static final String LAST_USED_3D_FOLDER = "3D_FOLDER";
	private static final String LAST_USED_IMAGE_FOLDER = "IMAGE_FOLDER";

	private static final String RUN_TOOLTIP = GUIFrame.formatToolTip("Run (space key)", "Starts or resumes the simulation run.");
	private static final String PAUSE_TOOLTIP = "<html><b>Pause</b></html>";  // Use a small tooltip for Pause so that it does not block the simulation time display

	static {
		try {
			if (OSFix.isMac())
				UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
			else
				UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		}
		catch (Exception e) {
			LogBox.logLine("Unable to change look and feel.");
		}

		try {
			URL file = GUIFrame.class.getResource("/resources/images/icon.png");
			iconImage = Toolkit.getDefaultToolkit().getImage(file);
		}
		catch (Exception e) {
			LogBox.logLine("Unable to load icon file.");
			iconImage = null;
		}

		shuttingDown = new AtomicBoolean(false);
		rateLimiter = RateLimiter.create(60);
	}

	private GUIFrame() {
		super();

		getContentPane().setLayout( new BorderLayout() );
		setDefaultCloseOperation( JFrame.DO_NOTHING_ON_CLOSE );

		// Initialize the working environment
		initializeMenus();
		initializeButtonBar();
		initializeMainToolBars();

		this.setIconImage(GUIFrame.getWindowIcon());

		//Set window size
		setResizable( true );  //FIXME should be false, but this causes the window to be sized
		                       //      and positioned incorrectly in the Windows 7 Aero theme
		pack();

		controlStartResume.requestFocusInWindow();

		controlStartResume.setSelected( false );
		controlStartResume.setEnabled( false );
		controlStop.setSelected( false );
		controlStop.setEnabled( false );
		setProgress( 0 );
		ToolTipManager.sharedInstance().setLightWeightPopupEnabled( false );
		JPopupMenu.setDefaultLightWeightPopupEnabled( false );

		this.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				close();
			}

			@Override
			public void windowDeiconified(WindowEvent e) {
				showWindows();
			}

			@Override
			public void windowIconified(WindowEvent e) {
				updateUI();
			}

			@Override
			public void windowActivated(WindowEvent e) {
				showWindows();
			}
		});

		addComponentListener(new ComponentAdapter() {
			@Override
			public void componentResized(ComponentEvent e) {
				if (sim.getSimulation() == null)
					return;
				sim.getSimulation().setControlPanelWidth(getSize().width);
			}

			@Override
			public void componentMoved(ComponentEvent e) {
				Simulation simulation = sim.getSimulation();
				if (simulation == null)
					return;
				windowOffset = new Point(getLocation().x - initLocation.x,
						getLocation().y - initLocation.y);
				updateToolLocations(simulation);
				updateViewLocations();
			}
		});
	}

	private Point windowOffset = new Point();
	private Point initLocation = new Point(getX(), getY()); // bypass the OSFix correction

	public Point getRelativeLocation(int x, int y) {
		return new Point(x - windowOffset.x, y - windowOffset.y);
	}

	public Point getGlobalLocation(int x, int y) {
		return new Point(x + windowOffset.x, y + windowOffset.y);
	}

	public static JaamSimModel getJaamSimModel() {
		return sim;
	}

	@Override
	public Dimension getPreferredSize() {
		Point fix = OSFix.getSizeAdustment();
		return new Dimension(DEFAULT_GUI_WIDTH + fix.x, super.getPreferredSize().height);
	}

	public static synchronized GUIFrame getInstance() {
		return instance;
	}

	private static synchronized GUIFrame createInstance() {
		instance = new GUIFrame();
		UIUpdater updater = new UIUpdater(instance);
		GUIFrame.registerCallback(new Runnable() {
			@Override
			public void run() {
				SwingUtilities.invokeLater(updater);
			}
		});
		return instance;
	}

	public static final void registerCallback(Runnable r) {
		rateLimiter.registerCallback(r);
	}

	public static final void updateUI() {
		rateLimiter.queueUpdate();
	}

	public void showWindows() {
		if (!RenderManager.isGood())
			return;

		// Identity the view window that is active
		View activeView = RenderManager.inst().getActiveView();

		// Re-open the view windows
		for (int i = 0; i < views.size(); i++) {
			View v = views.get(i);
			if (v != null && v.showWindow() && v != activeView)
				RenderManager.inst().createWindow(v);
		}

		// Re-open the active view window last
		if (activeView != null)
			RenderManager.inst().createWindow(activeView);

		// Re-open the tools
		showActiveTools(sim.getSimulation());
		updateUI();
	}

	/**
	 * Perform exit window duties
	 */
	void close() {
		// check for unsaved changes
		if (sim.isSessionEdited()) {
			boolean confirmed = GUIFrame.showSaveChangesDialog(this);
			if (!confirmed)
				return;
		}
		sim.closeLogFile();
		GUIFrame.shutdown(0);
	}

	/**
	 * Clears the simulation and user interface prior to loading a new model
	 */
	public void clear() {

		// Close the tool and view windows
		FrameBox.clear();
		EntityPallet.clear();
		RenderManager.clear();

		this.updateForSimulationState(GUIFrame.SIM_STATE_LOADED);

		// Clear the title bar
		setTitle(sim.getSimulation().getModelName());

		// Clear the status bar
		tickUpdate(0L);
		setProgress( 0 );
		speedUpDisplay.setText("0");
		remainingDisplay.setText("-");
		locatorPos.setText( "-" );

		// Clear the buttons
		clearButtons();

		// Build a completely new simulation model
		sim.clear();
		sim.autoLoad();
		setWindowDefaults(sim.getSimulation());

		EntityPallet.update();

		clearUndoRedo();
	}

	/**
	 * Sets up the Control Panel's menu bar.
	 */
	private void initializeMenus() {

		// Set up the individual menus
		this.initializeFileMenu();
		this.initializeEditMenu();
		this.initializeToolsMenu();
		this.initializeWindowMenu();
		this.initializeOptionsMenu();
		this.initializeUnitsMenu();
		this.initializeHelpMenu();

		// Add the individual menu to the main menu
		JMenuBar mainMenuBar = new JMenuBar();
		mainMenuBar.add( fileMenu );
		mainMenuBar.add( editMenu );
		mainMenuBar.add( toolsMenu );
		mainMenuBar.add( viewsMenu );
		mainMenuBar.add( optionMenu );
		mainMenuBar.add( unitsMenu );
		mainMenuBar.add( helpMenu );

		// Add main menu to the window
		setJMenuBar( mainMenuBar );
	}

	// ******************************************************************************************************
	// MENU BAR
	// ******************************************************************************************************

	/**
	 * Sets up the File menu in the Control Panel's menu bar.
	 */
	private void initializeFileMenu() {

		// File menu creation
		fileMenu = new JMenu( "File" );
		fileMenu.setMnemonic(KeyEvent.VK_F);
		fileMenu.setEnabled( false );

		// 1) "New" menu item
		JMenuItem newMenuItem = new JMenuItem( "New" );
		newMenuItem.setIcon( new ImageIcon(
				GUIFrame.class.getResource("/resources/images/New-16.png")) );
		newMenuItem.setMnemonic(KeyEvent.VK_N);
		newMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK));
		newMenuItem.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				GUIFrame.this.newModel();
			}
		} );
		fileMenu.add( newMenuItem );

		// 2) "Open" menu item
		JMenuItem configMenuItem = new JMenuItem( "Open..." );
		configMenuItem.setIcon( new ImageIcon(
				GUIFrame.class.getResource("/resources/images/Open-16.png")) );
		configMenuItem.setMnemonic(KeyEvent.VK_O);
		configMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK));
		configMenuItem.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				GUIFrame.this.load();
			}
		} );
		fileMenu.add( configMenuItem );

		// 3) "Save" menu item
		saveConfigurationMenuItem = new JMenuItem( "Save" );
		saveConfigurationMenuItem.setIcon( new ImageIcon(
				GUIFrame.class.getResource("/resources/images/Save-16.png")) );
		saveConfigurationMenuItem.setMnemonic(KeyEvent.VK_S);
		saveConfigurationMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK));
		saveConfigurationMenuItem.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				GUIFrame.this.save();
			}
		} );
		fileMenu.add( saveConfigurationMenuItem );

		// 4) "Save As..." menu item
		JMenuItem saveConfigurationAsMenuItem = new JMenuItem( "Save As..." );
		saveConfigurationAsMenuItem.setMnemonic(KeyEvent.VK_V);
		saveConfigurationAsMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.ALT_MASK + ActionEvent.CTRL_MASK));
		saveConfigurationAsMenuItem.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				GUIFrame.this.saveAs();

			}
		} );
		fileMenu.add( saveConfigurationAsMenuItem );
		fileMenu.addSeparator();

		// 5) "Import..." menu item
		JMenu importGraphicsMenuItem = new JMenu( "Import..." );
		importGraphicsMenuItem.setMnemonic(KeyEvent.VK_I);

		JMenuItem importImages = new JMenuItem( "Images..." );
		importImages.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				DisplayEntityFactory.importImages(GUIFrame.this);
			}
		} );
		importGraphicsMenuItem.add( importImages );

		JMenuItem import3D = new JMenuItem( "3D Assets..." );
		import3D.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				DisplayEntityFactory.import3D(GUIFrame.this);
			}
		} );
		importGraphicsMenuItem.add( import3D );

		fileMenu.add( importGraphicsMenuItem );

		// 6) "Print Input Report" menu item
		printInputItem = new JMenuItem( "Print Input Report" );
		printInputItem.setMnemonic(KeyEvent.VK_I);
		printInputItem.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				InputAgent.printInputFileKeywords(sim);
			}
		} );
		fileMenu.add( printInputItem );

		// 7) "Exit" menu item
		JMenuItem exitMenuItem = new JMenuItem( "Exit" );
		exitMenuItem.setMnemonic(KeyEvent.VK_X);
		exitMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, ActionEvent.ALT_MASK));
		exitMenuItem.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				close();
			}
		});
		fileMenu.addSeparator();
		fileMenu.add( exitMenuItem );
	}

	/**
	 * Sets up the Edit menu in the Control Panel's menu bar.
	 */
	private void initializeEditMenu() {

		// Edit menu creation
		editMenu = new JMenu( "Edit" );
		editMenu.setMnemonic(KeyEvent.VK_E);

		// 1) "Undo" menu item
		undoMenuItem = new JMenuItem("Undo");
		undoMenuItem.setIcon( new ImageIcon(
				GUIFrame.class.getResource("/resources/images/Undo-16.png")) );
		undoMenuItem.setMnemonic(KeyEvent.VK_U);
		undoMenuItem.setAccelerator(KeyStroke.getKeyStroke(
		        KeyEvent.VK_Z, ActionEvent.CTRL_MASK));
		undoMenuItem.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				undo();
			}
		} );
		editMenu.add( undoMenuItem );

		// 2) "Redo" menu item
		redoMenuItem = new JMenuItem("Redo");
		redoMenuItem.setIcon( new ImageIcon(
				GUIFrame.class.getResource("/resources/images/Redo-16.png")) );
		redoMenuItem.setMnemonic(KeyEvent.VK_R);
		redoMenuItem.setAccelerator(KeyStroke.getKeyStroke(
		        KeyEvent.VK_Y, ActionEvent.CTRL_MASK));
		redoMenuItem.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				redo();
			}
		} );
		editMenu.add( redoMenuItem );
		editMenu.addSeparator();

		// 3) "Copy" menu item
		copyMenuItem = new JMenuItem("Copy");
		copyMenuItem.setIcon( new ImageIcon(
				GUIFrame.class.getResource("/resources/images/Copy-16.png")) );
		copyMenuItem.setMnemonic(KeyEvent.VK_C);
		copyMenuItem.setAccelerator(KeyStroke.getKeyStroke(
		        KeyEvent.VK_C, ActionEvent.CTRL_MASK));
		copyMenuItem.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				if (selectedEntity == null)
					return;
				copyToClipboard(selectedEntity);
			}
		} );
		editMenu.add( copyMenuItem );

		// 4) "Paste" menu item
		pasteMenuItem = new JMenuItem("Paste");
		pasteMenuItem.setIcon( new ImageIcon(
				GUIFrame.class.getResource("/resources/images/Paste-16.png")) );
		pasteMenuItem.setMnemonic(KeyEvent.VK_P);
		pasteMenuItem.setAccelerator(KeyStroke.getKeyStroke(
		        KeyEvent.VK_V, ActionEvent.CTRL_MASK));
		pasteMenuItem.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				pasteEntityFromClipboard();
			}
		} );
		editMenu.add( pasteMenuItem );

		// 5) "Delete" menu item
		deleteMenuItem = new JMenuItem("Delete");
		deleteMenuItem.setMnemonic(KeyEvent.VK_D);
		deleteMenuItem.setAccelerator(KeyStroke.getKeyStroke(
		        KeyEvent.VK_DELETE, 0));
		deleteMenuItem.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				if (selectedEntity == null)
					return;
				try {
					deleteEntity(selectedEntity);
					FrameBox.setSelectedEntity(null, false);
				}
				catch (ErrorException e) {
					GUIFrame.showErrorDialog("User Error", e.getMessage());
				}
			}
		} );
		editMenu.add( deleteMenuItem );
		editMenu.addSeparator();

		// 6) "Find" menu item
		JMenuItem findMenuItem = new JMenuItem("Find");
		findMenuItem.setIcon( new ImageIcon(
				GUIFrame.class.getResource("/resources/images/Find-16.png")) );
		findMenuItem.setMnemonic(KeyEvent.VK_F);
		findMenuItem.setAccelerator(KeyStroke.getKeyStroke(
		        KeyEvent.VK_F, ActionEvent.CTRL_MASK));
		findMenuItem.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				FindBox.getInstance().showDialog();
			}
		} );
		editMenu.add( findMenuItem );
	}

	/**
	 * Sets up the Tools menu in the Control Panel's menu bar.
	 */
	private void initializeToolsMenu() {

		// Tools menu creation
		toolsMenu = new JMenu( "Tools" );
		toolsMenu.setMnemonic(KeyEvent.VK_T);

		// 1) "Show Basic Tools" menu item
		JMenuItem showBasicToolsMenuItem = new JMenuItem( "Show Basic Tools" );
		showBasicToolsMenuItem.setMnemonic(KeyEvent.VK_B);
		showBasicToolsMenuItem.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				EntityPallet.getInstance().toFront();
				ObjectSelector.getInstance().toFront();
				EditBox.getInstance().toFront();
				OutputBox.getInstance().toFront();
				KeywordIndex[] kws = new KeywordIndex[4];
				kws[0] = InputAgent.formatBoolean("ShowModelBuilder", true);
				kws[1] = InputAgent.formatBoolean("ShowObjectSelector", true);
				kws[2] = InputAgent.formatBoolean("ShowInputEditor", true);
				kws[3] = InputAgent.formatBoolean("ShowOutputViewer", true);
				InputAgent.storeAndExecute(new KeywordCommand(sim.getSimulation(), kws));
			}
		} );
		toolsMenu.add( showBasicToolsMenuItem );

		// 2) "Close All Tools" menu item
		JMenuItem closeAllToolsMenuItem = new JMenuItem( "Close All Tools" );
		closeAllToolsMenuItem.setMnemonic(KeyEvent.VK_C);
		closeAllToolsMenuItem.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				KeywordIndex[] kws = new KeywordIndex[7];
				kws[0] = InputAgent.formatBoolean("ShowModelBuilder", false);
				kws[1] = InputAgent.formatBoolean("ShowObjectSelector", false);
				kws[2] = InputAgent.formatBoolean("ShowInputEditor", false);
				kws[3] = InputAgent.formatBoolean("ShowOutputViewer", false);
				kws[4] = InputAgent.formatBoolean("ShowPropertyViewer", false);
				kws[5] = InputAgent.formatBoolean("ShowLogViewer", false);
				kws[6] = InputAgent.formatBoolean("ShowEventViewer", false);
				InputAgent.storeAndExecute(new KeywordCommand(sim.getSimulation(), kws));
			}
		} );
		toolsMenu.add( closeAllToolsMenuItem );

		// 3) "Model Builder" menu item
		JMenuItem objectPalletMenuItem = new JMenuItem( "Model Builder" );
		objectPalletMenuItem.setMnemonic(KeyEvent.VK_O);
		objectPalletMenuItem.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				EntityPallet.getInstance().toFront();
				KeywordIndex kw = InputAgent.formatBoolean("ShowModelBuilder", true);
				InputAgent.storeAndExecute(new KeywordCommand(sim.getSimulation(), kw));
			}
		} );
		toolsMenu.addSeparator();
		toolsMenu.add( objectPalletMenuItem );

		// 4) "Object Selector" menu item
		JMenuItem objectSelectorMenuItem = new JMenuItem( "Object Selector" );
		objectSelectorMenuItem.setMnemonic(KeyEvent.VK_S);
		objectSelectorMenuItem.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				ObjectSelector.getInstance().toFront();
				KeywordIndex kw = InputAgent.formatBoolean("ShowObjectSelector", true);
				InputAgent.storeAndExecute(new KeywordCommand(sim.getSimulation(), kw));
			}
		} );
		toolsMenu.add( objectSelectorMenuItem );

		// 5) "Input Editor" menu item
		JMenuItem inputEditorMenuItem = new JMenuItem( "Input Editor" );
		inputEditorMenuItem.setMnemonic(KeyEvent.VK_I);
		inputEditorMenuItem.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				EditBox.getInstance().toFront();
				KeywordIndex kw = InputAgent.formatBoolean("ShowInputEditor", true);
				InputAgent.storeAndExecute(new KeywordCommand(sim.getSimulation(), kw));
			}
		} );
		toolsMenu.add( inputEditorMenuItem );

		// 6) "Output Viewer" menu item
		JMenuItem outputMenuItem = new JMenuItem( "Output Viewer" );
		outputMenuItem.setMnemonic(KeyEvent.VK_U);
		outputMenuItem.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				OutputBox.getInstance().toFront();
				KeywordIndex kw = InputAgent.formatBoolean("ShowOutputViewer", true);
				InputAgent.storeAndExecute(new KeywordCommand(sim.getSimulation(), kw));
			}
		} );
		toolsMenu.add( outputMenuItem );

		// 7) "Property Viewer" menu item
		JMenuItem propertiesMenuItem = new JMenuItem( "Property Viewer" );
		propertiesMenuItem.setMnemonic(KeyEvent.VK_P);
		propertiesMenuItem.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				PropertyBox.getInstance().toFront();
				KeywordIndex kw = InputAgent.formatBoolean("ShowPropertyViewer", true);
				InputAgent.storeAndExecute(new KeywordCommand(sim.getSimulation(), kw));
			}
		} );
		toolsMenu.add( propertiesMenuItem );

		// 8) "Log Viewer" menu item
		JMenuItem logMenuItem = new JMenuItem( "Log Viewer" );
		logMenuItem.setMnemonic(KeyEvent.VK_L);
		logMenuItem.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				LogBox.getInstance().toFront();
				KeywordIndex kw = InputAgent.formatBoolean("ShowLogViewer", true);
				InputAgent.storeAndExecute(new KeywordCommand(sim.getSimulation(), kw));
			}
		} );
		toolsMenu.add( logMenuItem );

		// 9) "Event Viewer" menu item
		JMenuItem eventsMenuItem = new JMenuItem( "Event Viewer" );
		eventsMenuItem.setMnemonic(KeyEvent.VK_E);
		eventsMenuItem.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				EventViewer.getInstance().toFront();
				KeywordIndex kw = InputAgent.formatBoolean("ShowEventViewer", true);
				InputAgent.storeAndExecute(new KeywordCommand(sim.getSimulation(), kw));
			}
		} );
		toolsMenu.add( eventsMenuItem );

		// 10) "Reset Positions and Sizes" menu item
		JMenuItem resetItem = new JMenuItem( "Reset Positions and Sizes" );
		resetItem.setMnemonic(KeyEvent.VK_R);
		resetItem.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed( ActionEvent e ) {
				sim.getSimulation().resetWindowPositionsAndSizes();
			}
		} );
		toolsMenu.addSeparator();
		toolsMenu.add( resetItem );
	}

	/**
	 * Sets up the Views menu in the Control Panel's menu bar.
	 */
	private void initializeWindowMenu() {
		viewsMenu = new JMenu("Views");
		viewsMenu.setMnemonic(KeyEvent.VK_V);
		viewsMenu.addMenuListener(new MenuListener() {

			@Override
			public void menuSelected(MenuEvent e) {

				// 1) Select from the available view windows
				for (View view : getInstance().getViews()) {
					JMenuItem item = new JMenuItem(view.getName());
					item.addActionListener(new ActionListener() {
						@Override
						public void actionPerformed(ActionEvent e) {
							if (!RenderManager.isGood()) {
								if (RenderManager.canInitialize()) {
									RenderManager.initialize(SAFE_GRAPHICS);
								} else {
									// A fatal error has occurred, don't try to initialize again
									return;
								}
							}
							KeywordIndex kw = InputAgent.formatBoolean("ShowWindow", true);
							InputAgent.storeAndExecute(new KeywordCommand(view, kw));
							FrameBox.setSelectedEntity(view, false);
						}
					});
					viewsMenu.add(item);
				}

				// 2) "Define New View" menu item
				JMenuItem defineItem = new JMenuItem("Define New View");
				defineItem.addActionListener(new ActionListener() {
					@Override
					public void actionPerformed(ActionEvent e) {
						if (!RenderManager.isGood()) {
							if (RenderManager.canInitialize()) {
								RenderManager.initialize(SAFE_GRAPHICS);
							} else {
								// A fatal error has occurred, don't try to initialize again
								return;
							}
						}

						String name = InputAgent.getUniqueName(sim, "View", "");
						IntegerVector winPos = null;
						Vec3d pos = null;
						Vec3d center = null;
						ArrayList<View> viewList = getInstance().getViews();
						if (!viewList.isEmpty()) {
							View lastView = viewList.get(viewList.size() - 1);
							winPos = (IntegerVector) lastView.getInput("WindowPosition").getValue();
							winPos = new IntegerVector(winPos);
							winPos.set(0, winPos.get(0) + VIEW_OFFSET);
							pos = lastView.getViewPosition();
							center = lastView.getViewCenter();
						}
						InputAgent.storeAndExecute(new DefineViewCommand(sim, name, pos, center, winPos));
					}
				});
				viewsMenu.addSeparator();
				viewsMenu.add(defineItem);

				// 3) "Reset Positions and Sizes" menu item
				JMenuItem resetItem = new JMenuItem( "Reset Positions and Sizes" );
				resetItem.setMnemonic(KeyEvent.VK_R);
				resetItem.addActionListener(new ActionListener() {
					@Override
					public void actionPerformed( ActionEvent e ) {
						for (View v : getInstance().getViews()) {
							KeywordIndex posKw = InputAgent.formatArgs("WindowPosition");
							KeywordIndex sizeKw = InputAgent.formatArgs("WindowSize");
							InputAgent.storeAndExecute(new KeywordCommand(v, posKw, sizeKw));
						}
					}
				} );
				viewsMenu.addSeparator();
				viewsMenu.add(resetItem);
			}

			@Override
			public void menuCanceled(MenuEvent arg0) {
			}

			@Override
			public void menuDeselected(MenuEvent arg0) {
				viewsMenu.removeAll();
			}
		});
	}

	/**
	 * Sets up the Options menu in the Control Panel's menu bar.
	 */
	private void initializeOptionsMenu() {

		optionMenu = new JMenu( "Options" );
		optionMenu.setMnemonic(KeyEvent.VK_O);

		// 1) "Always on top" check box
		alwaysTop = new JCheckBoxMenuItem( "Always on top", false );
		alwaysTop.setMnemonic(KeyEvent.VK_A);
		optionMenu.add( alwaysTop );
		alwaysTop.addActionListener( new ActionListener() {
			@Override
			public void actionPerformed( ActionEvent e ) {
				if( GUIFrame.this.isAlwaysOnTop() ) {
					GUIFrame.this.setAlwaysOnTop( false );
				}
				else {
					GUIFrame.this.setAlwaysOnTop( true );
				}
			}
		} );

		// 2) "Graphics Debug Info" check box
		graphicsDebug = new JCheckBoxMenuItem( "Graphics Debug Info", false );
		graphicsDebug.setMnemonic(KeyEvent.VK_D);
		optionMenu.add( graphicsDebug );
		graphicsDebug.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				RenderManager.setDebugInfo(((JCheckBoxMenuItem)e.getSource()).getState());
			}
		});
	}

	/**
	 * Sets up the Units menu in the Control Panel's menu bar.
	 */
	private void initializeUnitsMenu() {

		unitsMenu = new JMenu( "Units" );
		unitsMenu.setMnemonic(KeyEvent.VK_U);

		unitsMenu.addMenuListener( new MenuListener() {

			@Override
			public void menuCanceled(MenuEvent arg0) {}

			@Override
			public void menuDeselected(MenuEvent arg0) {
				unitsMenu.removeAll();
			}

			@Override
			public void menuSelected(MenuEvent arg0) {
				UnitsSelector.populateMenu(unitsMenu);
				unitsMenu.setVisible(true);
			}
		});
	}

	/**
	 * Sets up the Help menu in the Control Panel's menu bar.
	 */
	private void initializeHelpMenu() {

		// Help menu creation
		helpMenu = new JMenu( "Help" );
		helpMenu.setMnemonic(KeyEvent.VK_H);

		// 1) "About" menu item
		JMenuItem aboutMenu = new JMenuItem( "About" );
		aboutMenu.setMnemonic(KeyEvent.VK_A);
		aboutMenu.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				AboutBox.instance().setVisible(true);
			}
		} );
		helpMenu.add( aboutMenu );
	}

	/**
	 * Returns the pixel length of the string with specified font
	 */
	private static int getPixelWidthOfString_ForFont(String str, Font font) {
		FontMetrics metrics = new FontMetrics(font) {};
		Rectangle2D bounds = metrics.getStringBounds(str, null);
		return (int)bounds.getWidth();
	}

	// ******************************************************************************************************
	// BUTTON BAR
	// ******************************************************************************************************

	/**
	 * Sets up the Control Panel's button bar.
	 */
	public void initializeButtonBar() {

		Insets noMargin = new Insets( 0, 0, 0, 0 );
		Insets smallMargin = new Insets( 1, 1, 1, 1 );
		Dimension separatorDim = new Dimension(11, 20);
		Dimension gapDim = new Dimension(5, separatorDim.height);

		JToolBar buttonBar = new JToolBar();
		buttonBar.setMargin( smallMargin );
		buttonBar.setFloatable(false);
		buttonBar.setLayout( new FlowLayout( FlowLayout.LEFT, 0, 0 ) );

		getContentPane().add( buttonBar, BorderLayout.NORTH );

		// File new, open, and save buttons
		buttonBar.add(Box.createRigidArea(gapDim));
		addFileNewButton(buttonBar, noMargin);

		buttonBar.add(Box.createRigidArea(gapDim));
		addFileOpenButton(buttonBar, noMargin);

		buttonBar.add(Box.createRigidArea(gapDim));
		addFileSaveButton(buttonBar, noMargin);

		// Undo and redo buttons
		buttonBar.addSeparator(separatorDim);
		addUndoButtons(buttonBar, noMargin);

		// 2D, axes, and grid buttons
		buttonBar.addSeparator(separatorDim);
		add2dButton(buttonBar, smallMargin);

		buttonBar.add(Box.createRigidArea(gapDim));
		addShowAxesButton(buttonBar, noMargin);

		buttonBar.add(Box.createRigidArea(gapDim));
		addShowGridButton(buttonBar, noMargin);

		// Show labels button
		buttonBar.add(Box.createRigidArea(gapDim));
		addShowLabelsButton(buttonBar, noMargin);

		// Show sub-models button
		buttonBar.add(Box.createRigidArea(gapDim));
		addShowSubModelsButton(buttonBar, noMargin);

		// Snap-to-grid button and field
		buttonBar.addSeparator(separatorDim);
		addSnapToGridButton(buttonBar, noMargin);

		buttonBar.add(Box.createRigidArea(gapDim));
		addSnapToGridField(buttonBar, noMargin);

		// Show and create links buttons
		buttonBar.addSeparator(separatorDim);
		addShowLinksButton(buttonBar, noMargin);

		buttonBar.add(Box.createRigidArea(gapDim));
		addCreateLinksButton(buttonBar, noMargin);

		// Previous and Next buttons
		buttonBar.add(Box.createRigidArea(gapDim));
		addPreviousButton(buttonBar, noMargin);
		buttonBar.add(Box.createRigidArea(gapDim));
		addNextButton(buttonBar, noMargin);
		buttonBar.add(Box.createRigidArea(gapDim));
		addReverseButton(buttonBar, noMargin);

		// Show Copy and Paste buttons
		buttonBar.addSeparator(separatorDim);
		addCopyButton(buttonBar, noMargin);
		addPasteButton(buttonBar, noMargin);

		// Show Entity Finder button
		buttonBar.add(Box.createRigidArea(gapDim));
		addEntityFinderButton(buttonBar, noMargin);

		// DisplayModel field and button
		buttonBar.addSeparator(separatorDim);
		addDisplayModelSelector(buttonBar, noMargin);
		buttonBar.add(Box.createRigidArea(gapDim));
		addEditDisplayModelButton(buttonBar, noMargin);

		// Clear formatting button
		buttonBar.add(Box.createRigidArea(gapDim));
		addClearFormattingButton(buttonBar, noMargin);

		// Font selector and text height field
		buttonBar.addSeparator(separatorDim);
		addFontSelector(buttonBar, noMargin);
		buttonBar.add(Box.createRigidArea(gapDim));
		addTextHeightField(buttonBar, noMargin);

		// Larger and smaller text height buttons
		buttonBar.add(Box.createRigidArea(gapDim));
		addTextHeightButtons(buttonBar, noMargin);

		// Bold and Italic buttons
		buttonBar.add(Box.createRigidArea(gapDim));
		addFontStyleButtons(buttonBar, noMargin);

		// Font colour button
		buttonBar.add(Box.createRigidArea(gapDim));
		addFontColourButton(buttonBar, noMargin);

		// Text alignment buttons
		buttonBar.add(Box.createRigidArea(gapDim));
		addTextAlignmentButtons(buttonBar, noMargin);

		// Z-coordinate buttons
		buttonBar.addSeparator(separatorDim);
		addZButtons(buttonBar, noMargin);

		// Line buttons
		buttonBar.addSeparator(separatorDim);
		addOutlineButton(buttonBar, noMargin);
		addLineWidthSpinner(buttonBar, noMargin);
		addLineColourButton(buttonBar, noMargin);

		// Fill buttons
		buttonBar.addSeparator(separatorDim);
		addFillButton(buttonBar, noMargin);
		addFillColourButton(buttonBar, noMargin);
	}

	private void addFileNewButton(JToolBar buttonBar, Insets margin) {
		JButton fileNew = new JButton( new ImageIcon(
				GUIFrame.class.getResource("/resources/images/New-16.png")) );
		fileNew.setMargin(margin);
		fileNew.setFocusPainted(false);
		fileNew.setToolTipText(formatToolTip("New (Ctrl+N)", "Starts a new model."));
		fileNew.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				GUIFrame.this.newModel();
				controlStartResume.requestFocusInWindow();
			}
		} );
		buttonBar.add( fileNew );
	}

	private void addFileOpenButton(JToolBar buttonBar, Insets margin) {
		JButton fileOpen = new JButton( new ImageIcon(
				GUIFrame.class.getResource("/resources/images/Open-16.png")) );
		fileOpen.setMargin(margin);
		fileOpen.setFocusPainted(false);
		fileOpen.setToolTipText(formatToolTip("Open... (Ctrl+O)", "Opens a model."));
		fileOpen.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				GUIFrame.this.load();
				controlStartResume.requestFocusInWindow();
			}
		} );
		buttonBar.add( fileOpen );
	}

	private void addFileSaveButton(JToolBar buttonBar, Insets margin) {
		fileSave = new JButton( new ImageIcon(
				GUIFrame.class.getResource("/resources/images/Save-16.png")) );
		fileSave.setMargin(margin);
		fileSave.setFocusPainted(false);
		fileSave.setToolTipText(formatToolTip("Save (Ctrl+S)", "Saves the present model."));
		fileSave.setEnabled(false);
		fileSave.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				GUIFrame.this.save();
				controlStartResume.requestFocusInWindow();
			}
		} );
		buttonBar.add( fileSave );
	}

	private void addUndoButtons(JToolBar buttonBar, Insets margin) {

		// Undo button
		undo = new JButton( new ImageIcon(
				GUIFrame.class.getResource("/resources/images/Undo-16.png")) );
		undo.setMargin(margin);
		undo.setFocusPainted(false);
		undo.setRequestFocusEnabled(false);
		undo.setToolTipText(formatToolTip("Undo (Ctrl+Z)", "Reverses the last change to the model."));
		undo.setEnabled(!undoList.isEmpty());
		undo.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				undo();
				controlStartResume.requestFocusInWindow();
			}
		} );
		buttonBar.add( undo );

		// Undo Dropdown Menu
		undoDropdown = new JButton(new ImageIcon(
				GUIFrame.class.getResource("/resources/images/dropdown.png")));
		undoDropdown.setMargin(margin);
		undoDropdown.setFocusPainted(false);
		undoDropdown.setRequestFocusEnabled(false);
		undoDropdown.setEnabled(!undoList.isEmpty());
		undoDropdown.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				ScrollablePopupMenu menu = new ScrollablePopupMenu("UndoMenu");
				synchronized (undoList) {
					for (int i = 1; i <= undoList.size(); i++) {
						Command cmd = undoList.get(undoList.size() - i);
						final int num = i;
						JMenuItem item = new JMenuItem(cmd.toString());
						item.addActionListener( new ActionListener() {

							@Override
							public void actionPerformed( ActionEvent event ) {
								undo(num);
								controlStartResume.requestFocusInWindow();
							}
						} );
						menu.add(item);
					}
					menu.show(undoDropdown, 0, undoDropdown.getHeight());
				}
			}
		} );
		buttonBar.add( undoDropdown );

		// Redo button
		redo = new JButton( new ImageIcon(
				GUIFrame.class.getResource("/resources/images/Redo-16.png")) );
		redo.setMargin(margin);
		redo.setFocusPainted(false);
		redo.setRequestFocusEnabled(false);
		redo.setToolTipText(formatToolTip("Redo (Ctrl+Y)", "Re-performs the last change to the model that was undone."));
		redo.setEnabled(!redoList.isEmpty());
		redo.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				redo();
				controlStartResume.requestFocusInWindow();
			}
		} );
		buttonBar.add( redo );

		// Redo Dropdown Menu
		redoDropdown = new JButton(new ImageIcon(
				GUIFrame.class.getResource("/resources/images/dropdown.png")));
		redoDropdown.setMargin(margin);
		redoDropdown.setFocusPainted(false);
		redoDropdown.setRequestFocusEnabled(false);
		redoDropdown.setEnabled(!redoList.isEmpty());
		redoDropdown.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				ScrollablePopupMenu menu = new ScrollablePopupMenu("RedoMenu");
				synchronized (undoList) {
					for (int i = 1; i <= redoList.size(); i++) {
						Command cmd = redoList.get(redoList.size() - i);
						final int num = i;
						JMenuItem item = new JMenuItem(cmd.toString());
						item.addActionListener( new ActionListener() {

							@Override
							public void actionPerformed( ActionEvent event ) {
								redo(num);
								controlStartResume.requestFocusInWindow();
							}
						} );
						menu.add(item);
					}
					menu.show(redoDropdown, 0, redoDropdown.getHeight());
				}
			}
		} );
		buttonBar.add( redoDropdown );
	}

	private void add2dButton(JToolBar buttonBar, Insets margin) {
		lockViewXYPlane = new JToggleButton( "2D" );
		lockViewXYPlane.setMargin(margin);
		lockViewXYPlane.setFocusPainted(false);
		lockViewXYPlane.setRequestFocusEnabled(false);
		lockViewXYPlane.setToolTipText(formatToolTip("2D View",
				"Sets the camera position to show a bird's eye view of the 3D scene."));
		lockViewXYPlane.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				boolean bLock2D = (((JToggleButton)event.getSource()).isSelected());

				if (RenderManager.isGood()) {
					View currentView = RenderManager.inst().getActiveView();
					if (currentView != null) {
						currentView.setLock2D(bLock2D);
					}
				}
				controlStartResume.requestFocusInWindow();
			}
		} );
		buttonBar.add( lockViewXYPlane );
	}

	private void addShowAxesButton(JToolBar buttonBar, Insets margin) {
		xyzAxis = new JToggleButton( new ImageIcon(
				GUIFrame.class.getResource("/resources/images/Axes-16.png")) );
		xyzAxis.setMargin(margin);
		xyzAxis.setFocusPainted(false);
		xyzAxis.setRequestFocusEnabled(false);
		xyzAxis.setToolTipText(formatToolTip("Show Axes",
				"Shows the unit vectors for the x, y, and z axes."));
		xyzAxis.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				DisplayEntity ent = (DisplayEntity) sim.getNamedEntity("XYZ-Axis");
				if (ent != null) {
					KeywordIndex kw = InputAgent.formatBoolean("Show", xyzAxis.isSelected());
					InputAgent.storeAndExecute(new KeywordCommand(ent, kw));
				}
				controlStartResume.requestFocusInWindow();
			}
		} );
		buttonBar.add( xyzAxis );
	}

	private void addShowGridButton(JToolBar buttonBar, Insets margin) {
		grid = new JToggleButton( new ImageIcon(
				GUIFrame.class.getResource("/resources/images/Grid-16.png")) );
		grid.setMargin(margin);
		grid.setFocusPainted(false);
		grid.setRequestFocusEnabled(false);
		grid.setToolTipText(formatToolTip("Show Grid",
				"Shows the coordinate grid on the x-y plane."));
		grid.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				DisplayEntity ent = (DisplayEntity) sim.getNamedEntity("XY-Grid");
				if (ent == null && sim.getNamedEntity("Grid100x100") != null) {
					InputAgent.storeAndExecute(new DefineCommand(sim, DisplayEntity.class, "XY-Grid"));
					ent = (DisplayEntity) sim.getNamedEntity("XY-Grid");
					KeywordIndex dmKw = InputAgent.formatArgs("DisplayModel", "Grid100x100");
					KeywordIndex sizeKw = InputAgent.formatArgs("Size", "100", "100", "0", "m");
					InputAgent.storeAndExecute(new KeywordCommand(ent, dmKw, sizeKw));
					grid.setSelected(true);
				}
				if (ent != null) {
					KeywordIndex kw = InputAgent.formatBoolean("Show", grid.isSelected());
					InputAgent.storeAndExecute(new KeywordCommand(ent, kw));
				}
				controlStartResume.requestFocusInWindow();
			}
		} );
		buttonBar.add( grid );
	}

	private void addShowLabelsButton(JToolBar buttonBar, Insets margin) {
		showLabels = new JToggleButton( new ImageIcon(
				GUIFrame.class.getResource("/resources/images/ShowLabels-16.png")) );
		showLabels.setMargin(margin);
		showLabels.setFocusPainted(false);
		showLabels.setRequestFocusEnabled(false);
		showLabels.setToolTipText(formatToolTip("Show Labels",
				"Displays the label for every entity in the model."));
		showLabels.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				boolean bool = showLabels.isSelected();
				KeywordIndex kw = InputAgent.formatBoolean("ShowLabels", bool);
				InputAgent.storeAndExecute(new KeywordCommand(sim.getSimulation(), kw));
				setShowLabels(bool);
				controlStartResume.requestFocusInWindow();
			}
		} );
		buttonBar.add( showLabels );
	}

	private void addShowSubModelsButton(JToolBar buttonBar, Insets margin) {
		showSubModels = new JToggleButton( new ImageIcon(
				GUIFrame.class.getResource("/resources/images/ShowSubModels-16.png")) );
		showSubModels.setMargin(margin);
		showSubModels.setFocusPainted(false);
		showSubModels.setRequestFocusEnabled(false);
		showSubModels.setToolTipText(formatToolTip("Show SubModels",
				"Displays the components of each sub-model."));
		showSubModels.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				boolean bool = showSubModels.isSelected();
				KeywordIndex kw = InputAgent.formatBoolean("ShowSubModels", bool);
				InputAgent.storeAndExecute(new KeywordCommand(sim.getSimulation(), kw));
				setShowSubModels(bool);
				controlStartResume.requestFocusInWindow();
			}
		} );
		buttonBar.add( showSubModels );
	}

	private void addSnapToGridButton(JToolBar buttonBar, Insets margin) {
		snapToGrid = new JToggleButton( new ImageIcon(
				GUIFrame.class.getResource("/resources/images/Snap-16.png")) );
		snapToGrid.setMargin(margin);
		snapToGrid.setFocusPainted(false);
		snapToGrid.setRequestFocusEnabled(false);
		snapToGrid.setToolTipText(formatToolTip("Snap to Grid",
				"During repositioning, objects are forced to the nearest grid point."));
		snapToGrid.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				KeywordIndex kw = InputAgent.formatBoolean("SnapToGrid", snapToGrid.isSelected());
				InputAgent.storeAndExecute(new KeywordCommand(sim.getSimulation(), kw));
				gridSpacing.setEnabled(snapToGrid.isSelected());
				controlStartResume.requestFocusInWindow();
			}
		} );

		buttonBar.add( snapToGrid );
	}

	private void addSnapToGridField(JToolBar buttonBar, Insets margin) {

		gridSpacing = new JTextField("1000000 m") {
			@Override
			protected void processFocusEvent(FocusEvent fe) {
				if (fe.getID() == FocusEvent.FOCUS_LOST) {
					GUIFrame.this.setSnapGridSpacing(this.getText().trim());
				}
				else if (fe.getID() == FocusEvent.FOCUS_GAINED) {
					gridSpacing.selectAll();
				}
				super.processFocusEvent( fe );
			}
		};

		gridSpacing.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent evt) {
				GUIFrame.this.setSnapGridSpacing(gridSpacing.getText().trim());
				controlStartResume.requestFocusInWindow();
			}
		});

		gridSpacing.setMaximumSize(gridSpacing.getPreferredSize());
		int hght = snapToGrid.getPreferredSize().height;
		gridSpacing.setPreferredSize(new Dimension(gridSpacing.getPreferredSize().width, hght));

		gridSpacing.setHorizontalAlignment(JTextField.RIGHT);
		gridSpacing.setToolTipText(formatToolTip("Snap Grid Spacing",
				"Distance between adjacent grid points, e.g. 0.1 m, 10 km, etc."));

		gridSpacing.setEnabled(snapToGrid.isSelected());

		buttonBar.add(gridSpacing);
	}

	private void addShowLinksButton(JToolBar buttonBar, Insets margin) {
		showLinks = new JToggleButton(new ImageIcon(GUIFrame.class.getResource("/resources/images/ShowLinks-16.png")));
		showLinks.setToolTipText(formatToolTip("Show Entity Flow",
				"When selected, arrows are shown between objects to indicate the flow of entities."));
		showLinks.setMargin(margin);
		showLinks.setFocusPainted(false);
		showLinks.setRequestFocusEnabled(false);
		showLinks.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed( ActionEvent event ) {
				boolean bShow = (((JToggleButton)event.getSource()).isSelected());
				if (RenderManager.isGood()) {
					RenderManager.inst().setShowLinks(bShow);
					RenderManager.redraw();
				}
				controlStartResume.requestFocusInWindow();
			}

		});
		buttonBar.add( showLinks );
	}

	private void addCreateLinksButton(JToolBar buttonBar, Insets margin) {
		createLinks = new JToggleButton(new ImageIcon(GUIFrame.class.getResource("/resources/images/MakeLinks-16.png")));
		createLinks.setToolTipText(formatToolTip("Create Entity Links",
				"When this is enabled, entities are linked when selection is changed."));
		createLinks.setMargin(margin);
		createLinks.setFocusPainted(false);
		createLinks.setRequestFocusEnabled(false);
		createLinks.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed( ActionEvent event ) {

				boolean bCreate = (((JToggleButton)event.getSource()).isSelected());
				if (RenderManager.isGood()) {
					if (bCreate) {
						FrameBox.setSelectedEntity(null, false);
						showLinks.setSelected(true);
						RenderManager.inst().setShowLinks(true);
					}
					RenderManager.inst().setCreateLinks(bCreate);
					RenderManager.redraw();
				}
				controlStartResume.requestFocusInWindow();
			}

		});
		buttonBar.add( createLinks );
	}

	private void addPreviousButton(JToolBar buttonBar, Insets margin) {
		prevButton = new JButton(new ImageIcon(GUIFrame.class.getResource("/resources/images/Previous-16.png")));
		prevButton.setToolTipText(formatToolTip("Previous",
				"Selects the previous object in the chain of linked objects."));
		prevButton.setMargin(margin);
		prevButton.setFocusPainted(false);
		prevButton.setRequestFocusEnabled(false);
		prevButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed( ActionEvent event ) {
				if (createLinks.isSelected())
					createLinks.doClick();
				if (selectedEntity != null && selectedEntity instanceof DisplayEntity) {
					DisplayEntity selectedDEnt = (DisplayEntity) selectedEntity;
					boolean dir = !reverseButton.isSelected();
					ArrayList<DirectedEntity> list = selectedDEnt.getPreviousList(dir);
					if (list.isEmpty())
						return;
					if (list.size() == 1) {
						setSelectedDEnt(list.get(0));
						return;
					}
					ScrollablePopupMenu menu = new ScrollablePopupMenu();
					for (DirectedEntity de : list) {
						JMenuItem item = new JMenuItem(de.toString());
						item.addActionListener( new ActionListener() {
							@Override
							public void actionPerformed( ActionEvent event ) {
								setSelectedDEnt(de);
								controlStartResume.requestFocusInWindow();
							}
						} );
						menu.add(item);
					}
					menu.show(prevButton, 0, prevButton.getHeight());
				}
				controlStartResume.requestFocusInWindow();
			}
		});
		buttonBar.add( prevButton );
	}

	private void addNextButton(JToolBar buttonBar, Insets margin) {
		nextButton = new JButton(new ImageIcon(GUIFrame.class.getResource("/resources/images/Next-16.png")));
		nextButton.setToolTipText(formatToolTip("Next",
				"Selects the next object in the chain of linked objects."));
		nextButton.setMargin(margin);
		nextButton.setFocusPainted(false);
		nextButton.setRequestFocusEnabled(false);
		nextButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed( ActionEvent event ) {
				if (createLinks.isSelected())
					createLinks.doClick();
				if (selectedEntity != null && selectedEntity instanceof DisplayEntity) {
					DisplayEntity selectedDEnt = (DisplayEntity) selectedEntity;
					boolean dir = !reverseButton.isSelected();
					ArrayList<DirectedEntity> list = selectedDEnt.getNextList(dir);
					if (list.isEmpty())
						return;
					if (list.size() == 1) {
						setSelectedDEnt(list.get(0));
						return;
					}
					ScrollablePopupMenu menu = new ScrollablePopupMenu();
					for (DirectedEntity de : list) {
						JMenuItem item = new JMenuItem(de.toString());
						item.addActionListener( new ActionListener() {
							@Override
							public void actionPerformed( ActionEvent event ) {
								setSelectedDEnt(de);
								controlStartResume.requestFocusInWindow();
							}
						} );
						menu.add(item);
					}
					menu.show(nextButton, 0, nextButton.getHeight());
				}
				controlStartResume.requestFocusInWindow();
			}
		});
		buttonBar.add( nextButton );
	}

	private void setSelectedDEnt(DirectedEntity de) {
		FrameBox.setSelectedEntity(de.entity, false);
		if (!reverseButton.isSelected() == de.direction)
			return;
		reverseButton.doClick();
	}

	private void addReverseButton(JToolBar buttonBar, Insets margin) {
		reverseButton = new JToggleButton(new ImageIcon(GUIFrame.class.getResource("/resources/images/Reverse-16.png")));
		reverseButton.setToolTipText(formatToolTip("Reverse Direction",
				"When enabled, the link arrows are shown for entities travelling in the reverse "
				+ "direction, and the Next and Previous buttons select the next/previous links "
				+ "for that direction."));
		reverseButton.setMargin(margin);
		reverseButton.setFocusPainted(false);
		reverseButton.setRequestFocusEnabled(false);
		reverseButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed( ActionEvent event ) {
				if (createLinks.isSelected())
					createLinks.doClick();
				boolean bool = (((JToggleButton)event.getSource()).isSelected());
				if (RenderManager.isGood()) {
					RenderManager.inst().setLinkDirection(!bool);
					RenderManager.redraw();
				}
				updateUI();
			}
		});
		// Reverse button is not needed in the open source JaamSim
		//buttonBar.add( reverseButton );
	}

	private void addCopyButton(JToolBar buttonBar, Insets margin) {
		copyButton = new JButton(new ImageIcon(GUIFrame.class.getResource("/resources/images/Copy-16.png")));
		copyButton.setToolTipText(formatToolTip("Copy (Ctrl+C)",
				"Copies the selected entity to the clipboard."));
		copyButton.setMargin(margin);
		copyButton.setFocusPainted(false);
		copyButton.setRequestFocusEnabled(false);
		copyButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed( ActionEvent event ) {
				if (selectedEntity != null)
					copyToClipboard(selectedEntity);
				controlStartResume.requestFocusInWindow();
			}
		});
		buttonBar.add( copyButton );
	}

	private void addPasteButton(JToolBar buttonBar, Insets margin) {
		pasteButton = new JButton(new ImageIcon(GUIFrame.class.getResource("/resources/images/Paste-16.png")));
		pasteButton.setToolTipText(formatToolTip("Paste (Ctrl+V)",
				"Pastes a copy of an entity from the clipboard to the location of the most recent "
				+ "mouse click."));
		pasteButton.setMargin(margin);
		pasteButton.setFocusPainted(false);
		pasteButton.setRequestFocusEnabled(false);
		pasteButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed( ActionEvent event ) {
				pasteEntityFromClipboard();
				controlStartResume.requestFocusInWindow();
			}
		});
		buttonBar.add( pasteButton );
	}

	private void addEntityFinderButton(JToolBar buttonBar, Insets margin) {
		find = new JToggleButton(new ImageIcon(GUIFrame.class.getResource("/resources/images/Find-16.png")));
		find.setToolTipText(formatToolTip("Entity Finder (Ctrl+F)",
				"Searches for an entity with a given name."));
		find.setMargin(margin);
		find.setFocusPainted(false);
		find.setRequestFocusEnabled(false);
		find.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed( ActionEvent event ) {
				if (find.isSelected()) {
					FindBox.getInstance().showDialog();
				}
				else {
					FindBox.getInstance().setVisible(false);
				}
				controlStartResume.requestFocusInWindow();
			}
		});
		buttonBar.add( find );
	}

	private void addDisplayModelSelector(JToolBar buttonBar, Insets margin) {

		dispModel = new JTextField("");
		dispModel.setEditable(false);
		dispModel.setHorizontalAlignment(JTextField.CENTER);
		dispModel.setPreferredSize(new Dimension(120, fileSave.getPreferredSize().height));
		dispModel.setToolTipText(formatToolTip("DisplayModel", "Sets the default appearance of the entity. "
				+ "A DisplayModel is analogous to a text style in a word processor."));
		buttonBar.add(dispModel);

		modelSelector = new JButton(new ImageIcon(
				GUIFrame.class.getResource("/resources/images/dropdown.png")));
		modelSelector.setMargin(margin);
		modelSelector.setFocusPainted(false);
		modelSelector.setRequestFocusEnabled(false);
		modelSelector.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				if (!(selectedEntity instanceof DisplayEntity))
					return;
				DisplayEntity dispEnt = (DisplayEntity) selectedEntity;
				if (!dispEnt.isGraphicsNominal() || dispEnt.getDisplayModelList().size() != 1)
					return;
				final String presentModelName = dispEnt.getDisplayModelList().get(0).getName();
				Input<?> in = dispEnt.getInput("DisplayModel");
				ArrayList<String> choices = in.getValidOptions(selectedEntity);
				PreviewablePopupMenu menu = new PreviewablePopupMenu(presentModelName, choices, true) {

					@Override
					public void setValue(String str) {
						dispModel.setText(str);
						KeywordIndex kw = InputAgent.formatArgs("DisplayModel", str);
						InputAgent.storeAndExecute(new KeywordCommand(dispEnt, kw));
					}

				};
				menu.show(dispModel, 0, dispModel.getPreferredSize().height);
				controlStartResume.requestFocusInWindow();
			}
		});

		buttonBar.add(modelSelector);
	}

	private void addEditDisplayModelButton(JToolBar buttonBar, Insets margin) {
		editDmButton = new JButton(new ImageIcon(
				GUIFrame.class.getResource("/resources/images/Edit-16.png")));
		editDmButton.setToolTipText(formatToolTip("Edit DisplayModel",
				"Selects the present DisplayModel so that its inputs can be edited."));
		editDmButton.setMargin(margin);
		editDmButton.setFocusPainted(false);
		editDmButton.setRequestFocusEnabled(false);
		editDmButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed( ActionEvent event ) {
				if (!(selectedEntity instanceof DisplayEntity))
					return;
				DisplayEntity dEnt = (DisplayEntity) selectedEntity;
				if (dEnt.getDisplayModelList().size() != 1)
					return;
				FrameBox.setSelectedEntity(dEnt.getDisplayModelList().get(0), false);
				controlStartResume.requestFocusInWindow();
			}
		});
		buttonBar.add( editDmButton );
	}

	private void addClearFormattingButton(JToolBar buttonBar, Insets margin) {
		clearButton = new JButton(new ImageIcon(
				GUIFrame.class.getResource("/resources/images/Clear-16.png")));
		clearButton.setToolTipText(formatToolTip("Clear Formatting",
				"Resets the format inputs for the selected Entity or DisplayModel to their default "
				+ "values."));
		clearButton.setMargin(margin);
		clearButton.setFocusPainted(false);
		clearButton.setRequestFocusEnabled(false);
		clearButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed( ActionEvent event ) {
				if (selectedEntity == null)
					return;
				ArrayList<KeywordIndex> kwList = new ArrayList<>();
				for (Input<?> in : selectedEntity.getEditableInputs()) {
					String cat = in.getCategory();
					if (in.isDefault() || !cat.equals(Entity.FORMAT) && !cat.equals(Entity.FONT))
						continue;
					KeywordIndex kw = InputAgent.formatArgs(in.getKeyword());
					kwList.add(kw);
				}
				if (kwList.isEmpty())
					return;
				KeywordIndex[] kws = new KeywordIndex[kwList.size()];
				kwList.toArray(kws);
				InputAgent.storeAndExecute(new KeywordCommand(selectedEntity, kws));
				controlStartResume.requestFocusInWindow();
			}
		});
		buttonBar.add( clearButton );
	}

	private void addTextAlignmentButtons(JToolBar buttonBar, Insets margin) {

		ActionListener alignListener = new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				if (!(selectedEntity instanceof TextBasics))
					return;
				TextBasics textEnt = (TextBasics) selectedEntity;
				Vec3d align = textEnt.getAlignment();
				double prevAlign = align.x;
				align.x = alignLeft.isSelected() ? -0.5d : align.x;
				align.x = alignCentre.isSelected() ? 0.0d : align.x;
				align.x = alignRight.isSelected() ? 0.5d : align.x;
				if (align.x == textEnt.getAlignment().x)
					return;
				KeywordIndex kw = InputAgent.formatVec3dInput("Alignment", align, DimensionlessUnit.class);

				Vec3d pos = textEnt.getPosition();
				Vec3d size = textEnt.getSize();
				pos.x += (align.x - prevAlign) * size.x;
				KeywordIndex posKw = InputAgent.formatVec3dInput("Position", pos, DistanceUnit.class);

				InputAgent.storeAndExecute(new KeywordCommand(textEnt, kw, posKw));
				controlStartResume.requestFocusInWindow();
			}
		};

		alignLeft = new JToggleButton(new ImageIcon(
				GUIFrame.class.getResource("/resources/images/AlignLeft-16.png")));
		alignLeft.setMargin(margin);
		alignLeft.setFocusPainted(false);
		alignLeft.setRequestFocusEnabled(false);
		alignLeft.setToolTipText(formatToolTip("Align Left",
				"Aligns the text to the left margin."));
		alignLeft.addActionListener( alignListener );

		alignCentre = new JToggleButton(new ImageIcon(
				GUIFrame.class.getResource("/resources/images/AlignCentre-16.png")));
		alignCentre.setMargin(margin);
		alignCentre.setFocusPainted(false);
		alignCentre.setRequestFocusEnabled(false);
		alignCentre.setToolTipText(formatToolTip("Align Centre",
				"Centres the text between the right and left margins."));
		alignCentre.addActionListener( alignListener );

		alignRight = new JToggleButton(new ImageIcon(
				GUIFrame.class.getResource("/resources/images/AlignRight-16.png")));
		alignRight.setMargin(margin);
		alignRight.setFocusPainted(false);
		alignRight.setRequestFocusEnabled(false);
		alignRight.setToolTipText(formatToolTip("Align Right",
				"Aligns the text to the right margin."));
		alignRight.addActionListener( alignListener );

		alignmentGroup = new ButtonGroup();
		alignmentGroup.add(alignLeft);
		alignmentGroup.add(alignCentre);
		alignmentGroup.add(alignRight);

		buttonBar.add( alignLeft );
		buttonBar.add( alignCentre );
		buttonBar.add( alignRight );
	}

	private void addFontStyleButtons(JToolBar buttonBar, Insets margin) {

		ActionListener alignmentListener = new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				if (!(selectedEntity instanceof TextEntity))
					return;
				TextEntity textEnt = (TextEntity) selectedEntity;
				if (textEnt.isBold() == bold.isSelected()
						&& textEnt.isItalic() && italic.isSelected())
					return;
				ArrayList<String> stylesList = new ArrayList<>(2);
				if (bold.isSelected())
					stylesList.add("BOLD");
				if (italic.isSelected())
					stylesList.add("ITALIC");
				String[] styles = stylesList.toArray(new String[stylesList.size()]);
				KeywordIndex kw = InputAgent.formatArgs("FontStyle", styles);
				InputAgent.storeAndExecute(new KeywordCommand((Entity)textEnt, kw));
				controlStartResume.requestFocusInWindow();
			}
		};

		bold = new JToggleButton(new ImageIcon(
				GUIFrame.class.getResource("/resources/images/Bold-16.png")));
		bold.setMargin(margin);
		bold.setFocusPainted(false);
		bold.setRequestFocusEnabled(false);
		bold.setToolTipText(formatToolTip("Bold", "Makes the text bold."));
		bold.addActionListener( alignmentListener );

		italic = new JToggleButton(new ImageIcon(
				GUIFrame.class.getResource("/resources/images/Italic-16.png")));
		italic.setMargin(margin);
		italic.setFocusPainted(false);
		italic.setRequestFocusEnabled(false);
		italic.setToolTipText(formatToolTip("Italic", "Italicizes the text."));
		italic.addActionListener( alignmentListener );

		buttonBar.add( bold );
		buttonBar.add( italic );
	}

	private void addFontSelector(JToolBar buttonBar, Insets margin) {

		font = new JTextField("");
		font.setEditable(false);
		font.setHorizontalAlignment(JTextField.CENTER);
		font.setPreferredSize(new Dimension(120, fileSave.getPreferredSize().height));
		font.setToolTipText(formatToolTip("Font", "Sets the font for the text."));
		buttonBar.add(font);

		fontSelector = new JButton(new ImageIcon(
				GUIFrame.class.getResource("/resources/images/dropdown.png")));
		fontSelector.setMargin(margin);
		fontSelector.setFocusPainted(false);
		fontSelector.setRequestFocusEnabled(false);
		fontSelector.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				if (!(selectedEntity instanceof TextEntity))
					return;
				final TextEntity textEnt = (TextEntity) selectedEntity;
				final String presentFontName = textEnt.getFontName();
				ArrayList<String> valuesInUse = GUIFrame.getFontsInUse(sim);
				ArrayList<String> choices = TextModel.validFontNames;
				PreviewablePopupMenu fontMenu = new PreviewablePopupMenu(presentFontName,
						valuesInUse, choices, true) {

					@Override
					public void setValue(String str) {
						font.setText(str);
						String name = Parser.addQuotesIfNeeded(str);
						KeywordIndex kw = InputAgent.formatInput("FontName", name);
						InputAgent.storeAndExecute(new KeywordCommand(selectedEntity, kw));
					}

				};
				fontMenu.show(font, 0, font.getPreferredSize().height);
				controlStartResume.requestFocusInWindow();
			}
		});

		buttonBar.add(fontSelector);
	}

	private void addTextHeightField(JToolBar buttonBar, Insets margin) {

		textHeight = new JTextField("1000000 m") {
			@Override
			protected void processFocusEvent(FocusEvent fe) {
				if (fe.getID() == FocusEvent.FOCUS_LOST) {
					GUIFrame.this.setTextHeight(this.getText().trim());
				}
				super.processFocusEvent( fe );
			}
		};

		textHeight.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent evt) {
				GUIFrame.this.setTextHeight(textHeight.getText().trim());
				controlStartResume.requestFocusInWindow();
			}
		});

		textHeight.setMaximumSize(textHeight.getPreferredSize());
		textHeight.setPreferredSize(new Dimension(textHeight.getPreferredSize().width,
				fileSave.getPreferredSize().height));

		textHeight.setHorizontalAlignment(JTextField.RIGHT);
		textHeight.setToolTipText(formatToolTip("Text Height",
				"Sets the height of the text, e.g. 0.1 m, 200 cm, etc."));

		buttonBar.add(textHeight);
	}

	private void addTextHeightButtons(JToolBar buttonBar, Insets margin) {

		ActionListener textHeightListener = new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				if (!(selectedEntity instanceof TextEntity))
					return;
				TextEntity textEnt = (TextEntity) selectedEntity;

				double height = textEnt.getTextHeight();
				double spacing = sim.getSimulation().getSnapGridSpacing();
				if (textEnt instanceof OverlayText || textEnt instanceof BillboardText)
					spacing = 1.0d;
				height = Math.round(height/spacing) * spacing;

				if (event.getActionCommand().equals("LargerText")) {
					height += spacing;
				}
				else if (event.getActionCommand().equals("SmallerText")) {
					height -= spacing;
					height = Math.max(spacing, height);
				}

				String format = "%.1f  m";
				if (textEnt instanceof OverlayText || textEnt instanceof BillboardText)
					format = "%.0f";
				String str = String.format(format, height);
				textHeight.setText(str);
				KeywordIndex kw = InputAgent.formatInput("TextHeight", str);
				InputAgent.storeAndExecute(new KeywordCommand((Entity)textEnt, kw));
				controlStartResume.requestFocusInWindow();
			}
		};

		largerText = new JButton(new ImageIcon(
				GUIFrame.class.getResource("/resources/images/LargerText-16.png")));
		largerText.setMargin(margin);
		largerText.setFocusPainted(false);
		largerText.setRequestFocusEnabled(false);
		largerText.setToolTipText(formatToolTip("Larger Text",
				"Increases the text height to the next higher multiple of the snap grid spacing."));
		largerText.setActionCommand("LargerText");
		largerText.addActionListener( textHeightListener );

		smallerText = new JButton(new ImageIcon(
				GUIFrame.class.getResource("/resources/images/SmallerText-16.png")));
		smallerText.setMargin(margin);
		smallerText.setFocusPainted(false);
		smallerText.setRequestFocusEnabled(false);
		smallerText.setToolTipText(formatToolTip("Smaller Text",
				"Decreases the text height to the next lower multiple of the snap grid spacing."));
		smallerText.setActionCommand("SmallerText");
		smallerText.addActionListener( textHeightListener );

		buttonBar.add( largerText );
		buttonBar.add( smallerText );
	}

	private void addFontColourButton(JToolBar buttonBar, Insets margin) {

		colourIcon = new ColorIcon(16, 16);
		colourIcon.setFillColor(Color.LIGHT_GRAY);
		colourIcon.setOutlineColor(Color.LIGHT_GRAY);
		fontColour = new JButton(colourIcon);
		fontColour.setMargin(margin);
		fontColour.setFocusPainted(false);
		fontColour.setRequestFocusEnabled(false);
		fontColour.setToolTipText(formatToolTip("Font Colour", "Sets the colour of the text."));
		fontColour.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				if (!(selectedEntity instanceof TextEntity))
					return;
				final TextEntity textEnt = (TextEntity) selectedEntity;
				final Color4d presentColour = textEnt.getFontColor();
				ArrayList<Color4d> coloursInUse = GUIFrame.getFontColoursInUse(sim);
				ColourMenu fontMenu = new ColourMenu(presentColour, coloursInUse, true) {

					@Override
					public void setColour(String colStr) {
						KeywordIndex kw = InputAgent.formatInput("FontColour", colStr);
						InputAgent.storeAndExecute(new KeywordCommand(selectedEntity, kw));
					}

				};
				fontMenu.show(fontColour, 0, fontColour.getPreferredSize().height);
				controlStartResume.requestFocusInWindow();
			}
		});

		buttonBar.add( fontColour );
	}

	private void addZButtons(JToolBar buttonBar, Insets margin) {

		ActionListener actionListener = new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				if (!(selectedEntity instanceof DisplayEntity)
						|| selectedEntity instanceof OverlayEntity)
					return;
				DisplayEntity dispEnt = (DisplayEntity) selectedEntity;

				double delta = sim.getSimulation().getSnapGridSpacing()/100.0d;
				Vec3d pos = dispEnt.getPosition();
				ArrayList<Vec3d> points = dispEnt.getPoints();
				Vec3d offset = new Vec3d();

				if (event.getActionCommand().equals("Up")) {
					pos.z += delta;
					offset.z += delta;
				}
				else if (event.getActionCommand().equals("Down")) {
					pos.z -= delta;
					offset.z -= delta;
				}

				// Normal object
				KeywordIndex posKw = InputAgent.formatVec3dInput("Position", pos, DistanceUnit.class);
				if (!dispEnt.usePointsInput()) {
					InputAgent.storeAndExecute(new KeywordCommand(dispEnt, posKw));
					controlStartResume.requestFocusInWindow();
					return;
				}

				// Polyline object
				KeywordIndex ptsKw = InputAgent.formatPointsInputs("Points", points, offset);
				InputAgent.storeAndExecute(new KeywordCommand(dispEnt, posKw, ptsKw));
				controlStartResume.requestFocusInWindow();
			}
		};

		increaseZ = new JButton(new ImageIcon(
				GUIFrame.class.getResource("/resources/images/PlusZ-16.png")));
		increaseZ.setMargin(margin);
		increaseZ.setFocusPainted(false);
		increaseZ.setRequestFocusEnabled(false);
		increaseZ.setToolTipText(formatToolTip("Move Up",
				"Increases the selected object's z-coordinate by one hundredth of the snap-grid "
				+ "spacing. By moving the object closer to the camera, it will appear on top of "
				+ "other objects with smaller z-coordinates."));
		increaseZ.setActionCommand("Up");
		increaseZ.addActionListener( actionListener );

		decreaseZ = new JButton(new ImageIcon(
				GUIFrame.class.getResource("/resources/images/MinusZ-16.png")));
		decreaseZ.setMargin(margin);
		decreaseZ.setFocusPainted(false);
		decreaseZ.setRequestFocusEnabled(false);
		decreaseZ.setToolTipText(formatToolTip("Move Down",
				"Decreases the selected object's z-coordinate by one hundredth of the snap-grid "
				+ "spacing. By moving the object farther from the camera, it will appear below "
				+ "other objects with larger z-coordinates."));
		decreaseZ.setActionCommand("Down");
		decreaseZ.addActionListener( actionListener );

		buttonBar.add( increaseZ );
		buttonBar.add( decreaseZ );
	}

	private void addOutlineButton(JToolBar buttonBar, Insets margin) {
		outline = new JToggleButton(new ImageIcon(
				GUIFrame.class.getResource("/resources/images/Outline-16.png")));
		outline.setMargin(margin);
		outline.setFocusPainted(false);
		outline.setRequestFocusEnabled(false);
		outline.setToolTipText(formatToolTip("Show Outline", "Shows the outline."));
		outline.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				if (!(selectedEntity instanceof LineEntity))
					return;
				LineEntity lineEnt = (LineEntity) selectedEntity;
				lineWidth.setEnabled(outline.isSelected());
				lineColour.setEnabled(outline.isSelected());
				if (lineEnt.isOutlined() == outline.isSelected())
					return;
				KeywordIndex kw = InputAgent.formatBoolean("Outlined", outline.isSelected());
				InputAgent.storeAndExecute(new KeywordCommand((Entity)lineEnt, kw));
				controlStartResume.requestFocusInWindow();
			}
		});

		buttonBar.add( outline );
	}

	private void addLineWidthSpinner(JToolBar buttonBar, Insets margin) {
		SpinnerNumberModel numberModel = new SpinnerNumberModel(1, 1, 10, 1);
		lineWidth = new JSpinner(numberModel);
		lineWidth.addChangeListener(new ChangeListener() {

			@Override
			public void stateChanged( ChangeEvent e ) {
				if (!(selectedEntity instanceof LineEntity))
					return;
				LineEntity lineEnt = (LineEntity) selectedEntity;
				int val = (int) lineWidth.getValue();
				if (val == lineEnt.getLineWidth())
					return;
				KeywordIndex kw = InputAgent.formatIntegers("LineWidth", val);
				InputAgent.storeAndExecute(new KeywordCommand((Entity)lineEnt, kw));
			}
		});

		lineWidth.setToolTipText(formatToolTip("Line Width",
				"Sets the width of the line in pixels."));

		buttonBar.add( lineWidth );
	}

	private void addLineColourButton(JToolBar buttonBar, Insets margin) {

		lineColourIcon = new ColorIcon(16, 16);
		lineColourIcon.setFillColor(Color.LIGHT_GRAY);
		lineColourIcon.setOutlineColor(Color.LIGHT_GRAY);
		lineColour = new JButton(lineColourIcon);
		lineColour.setMargin(margin);
		lineColour.setFocusPainted(false);
		lineColour.setRequestFocusEnabled(false);
		lineColour.setToolTipText(formatToolTip("Line Colour",
				"Sets the colour of the line."));
		lineColour.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				if (!(selectedEntity instanceof LineEntity))
					return;
				final LineEntity lineEnt = (LineEntity) selectedEntity;
				final Color4d presentColour = lineEnt.getLineColour();
				ArrayList<Color4d> coloursInUse = GUIFrame.getLineColoursInUse(sim);
				ColourMenu menu = new ColourMenu(presentColour, coloursInUse, true) {

					@Override
					public void setColour(String colStr) {
						KeywordIndex kw = InputAgent.formatInput("LineColour", colStr);
						InputAgent.storeAndExecute(new KeywordCommand(selectedEntity, kw));
					}

				};
				menu.show(lineColour, 0, lineColour.getPreferredSize().height);
				controlStartResume.requestFocusInWindow();
			}
		});

		buttonBar.add( lineColour );
	}

	private void addFillButton(JToolBar buttonBar, Insets margin) {
		fill = new JToggleButton(new ImageIcon(
				GUIFrame.class.getResource("/resources/images/Fill-16.png")));
		fill.setMargin(margin);
		fill.setFocusPainted(false);
		fill.setRequestFocusEnabled(false);
		fill.setToolTipText(formatToolTip("Show Fill",
				"Fills the entity with the selected colour."));
		fill.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				if (!(selectedEntity instanceof FillEntity))
					return;
				FillEntity fillEnt = (FillEntity) selectedEntity;
				fillColour.setEnabled(fill.isSelected());
				if (fillEnt.isFilled() == fill.isSelected())
					return;
				KeywordIndex kw = InputAgent.formatBoolean("Filled", fill.isSelected());
				InputAgent.storeAndExecute(new KeywordCommand((Entity)fillEnt, kw));
				controlStartResume.requestFocusInWindow();
			}
		});

		buttonBar.add( fill );
	}

	private void addFillColourButton(JToolBar buttonBar, Insets margin) {

		fillColourIcon = new ColorIcon(16, 16);
		fillColourIcon.setFillColor(Color.LIGHT_GRAY);
		fillColourIcon.setOutlineColor(Color.LIGHT_GRAY);
		fillColour = new JButton(fillColourIcon);
		fillColour.setMargin(margin);
		fillColour.setFocusPainted(false);
		fillColour.setRequestFocusEnabled(false);
		fillColour.setToolTipText(formatToolTip("Fill Colour",
				"Sets the colour of the fill."));
		fillColour.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				if (!(selectedEntity instanceof FillEntity))
					return;
				final FillEntity fillEnt = (FillEntity) selectedEntity;
				final Color4d presentColour = fillEnt.getFillColour();
				ArrayList<Color4d> coloursInUse = GUIFrame.getFillColoursInUse(sim);
				ColourMenu menu = new ColourMenu(presentColour, coloursInUse, true) {

					@Override
					public void setColour(String colStr) {
						KeywordIndex kw = InputAgent.formatInput("FillColour", colStr);
						InputAgent.storeAndExecute(new KeywordCommand(selectedEntity, kw));
					}

				};
				menu.show(fillColour, 0, fillColour.getPreferredSize().height);
				controlStartResume.requestFocusInWindow();
			}
		});

		buttonBar.add( fillColour );
	}

	// ******************************************************************************************************
	// TOOL BAR
	// ******************************************************************************************************

	/**
	 * Sets up the Control Panel's main tool bar.
	 */
	public void initializeMainToolBars() {

		// Insets used in setting the tool bar components
		Insets noMargin = new Insets( 0, 0, 0, 0 );
		Insets smallMargin = new Insets( 1, 1, 1, 1 );

		// Initialize the main tool bar
		JToolBar mainToolBar = new JToolBar();
		mainToolBar.setMargin( smallMargin );
		mainToolBar.setFloatable(false);
		mainToolBar.setLayout( new FlowLayout( FlowLayout.LEFT, 0, 0 ) );

		// Add the main tool bar to the display
		getContentPane().add( mainToolBar, BorderLayout.SOUTH );

		// Run/pause button
		addRunButton(mainToolBar, noMargin);

		Dimension separatorDim = new Dimension(11, controlStartResume.getPreferredSize().height);
		Dimension gapDim = new Dimension(5, separatorDim.height);

		// Reset button
		mainToolBar.add(Box.createRigidArea(gapDim));
		addResetButton(mainToolBar, noMargin);

		// Real time button
		mainToolBar.addSeparator(separatorDim);
		addRealTimeButton(mainToolBar, smallMargin);

		// Speed multiplier spinner
		mainToolBar.add(Box.createRigidArea(gapDim));
		addSpeedMultiplier(mainToolBar, noMargin);

		// Pause time field
		mainToolBar.addSeparator(separatorDim);
		mainToolBar.add(new JLabel("Pause Time:"));
		mainToolBar.add(Box.createRigidArea(gapDim));
		addPauseTime(mainToolBar, noMargin);

		// Simulation time display
		mainToolBar.addSeparator(separatorDim);
		addSimulationTime(mainToolBar, noMargin);

		// Run progress bar
		mainToolBar.add(Box.createRigidArea(gapDim));
		addRunProgress(mainToolBar, noMargin);

		// Remaining time display
		mainToolBar.add(Box.createRigidArea(gapDim));
		addRemainingTime(mainToolBar, noMargin);

		// Achieved speed multiplier
		mainToolBar.addSeparator(separatorDim);
		mainToolBar.add(new JLabel("Speed:"));
		addAchievedSpeedMultiplier(mainToolBar, noMargin);

		// Cursor position
		mainToolBar.addSeparator(separatorDim);
		mainToolBar.add(new JLabel("Position:"));
		addCursorPosition(mainToolBar, noMargin);
	}

	private void addRunButton(JToolBar mainToolBar, Insets margin) {
		runPressedIcon = new ImageIcon(GUIFrame.class.getResource("/resources/images/run-pressed-24.png"));
		pausePressedIcon = new ImageIcon(GUIFrame.class.getResource("/resources/images/pause-pressed-24.png"));

		controlStartResume = new RoundToggleButton(new ImageIcon(GUIFrame.class.getResource("/resources/images/run-24.png")));
		controlStartResume.setRolloverEnabled(true);
		controlStartResume.setRolloverIcon(new ImageIcon(GUIFrame.class.getResource("/resources/images/run-rollover-24.png")));
		controlStartResume.setPressedIcon(runPressedIcon);
		controlStartResume.setSelectedIcon(
				new ImageIcon(GUIFrame.class.getResource("/resources/images/pause-24.png")));
		controlStartResume.setRolloverSelectedIcon(
				new ImageIcon(GUIFrame.class.getResource("/resources/images/pause-rollover-24.png")));
		controlStartResume.setToolTipText(RUN_TOOLTIP);
		controlStartResume.setMargin(margin);
		controlStartResume.setEnabled( false );
		controlStartResume.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				JToggleButton startResume = (JToggleButton)event.getSource();
				startResume.setEnabled(false);
				if(startResume.isSelected()) {
					boolean bool = GUIFrame.this.startSimulation();
					if (bool) {
						controlStartResume.setPressedIcon(pausePressedIcon);
					}
					else {
						startResume.setSelected(false);
						startResume.setEnabled(true);
					}
				}
				else {
					GUIFrame.this.pauseSimulation();
					controlStartResume.setPressedIcon(runPressedIcon);
				}
				controlStartResume.requestFocusInWindow();
			}
		} );
		mainToolBar.add( controlStartResume );

		// Listen for keyboard shortcuts for simulation speed
		controlStartResume.addKeyListener(new KeyListener() {
			@Override
			public void keyTyped(KeyEvent e) {}
			@Override
			public void keyPressed(KeyEvent e) {
				if (e.getKeyCode() == KeyEvent.VK_PERIOD) {   // same as the '>' key
					if (!spinner.isEnabled())
						return;
					spinner.setValue(spinner.getNextValue());
					return;
				}
				if (e.getKeyCode() == KeyEvent.VK_COMMA) {    // same as the '<' key
					if (!spinner.isEnabled())
						return;
					spinner.setValue(spinner.getPreviousValue());
					return;
				}
			}
			@Override
			public void keyReleased(KeyEvent e) {}
		});
	}

	private void addResetButton(JToolBar mainToolBar, Insets margin) {
		controlStop = new RoundToggleButton(new ImageIcon(GUIFrame.class.getResource("/resources/images/reset-16.png")));
		controlStop.setToolTipText(formatToolTip("Reset",
				"Resets the simulation run time to zero."));
		controlStop.setPressedIcon(new ImageIcon(GUIFrame.class.getResource("/resources/images/reset-pressed-16.png")));
		controlStop.setRolloverEnabled( true );
		controlStop.setRolloverIcon(new ImageIcon(GUIFrame.class.getResource("/resources/images/reset-rollover-16.png")));
		controlStop.setMargin(margin);
		controlStop.setEnabled( false );
		controlStop.addActionListener( new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				if( getSimState() == SIM_STATE_RUNNING ) {
					GUIFrame.this.pauseSimulation();
				}
				controlStartResume.requestFocusInWindow();
				boolean confirmed = GUIFrame.showConfirmStopDialog();
				if (!confirmed)
					return;

				GUIFrame.this.stopSimulation();
				initSpeedUp(0.0d);
				tickUpdate(0L);
			}
		} );
		mainToolBar.add( controlStop );
	}

	private void addRealTimeButton(JToolBar mainToolBar, Insets margin) {
		controlRealTime = new JToggleButton( " Real Time " );
		controlRealTime.setToolTipText(formatToolTip("Real Time Mode",
				"When selected, the simulation runs at a fixed multiple of wall clock time."));
		controlRealTime.setMargin(margin);
		controlRealTime.setFocusPainted(false);
		controlRealTime.setRequestFocusEnabled(false);
		controlRealTime.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed( ActionEvent event ) {
				boolean bool = ((JToggleButton)event.getSource()).isSelected();
				KeywordIndex kw = InputAgent.formatBoolean("RealTime", bool);
				InputAgent.storeAndExecute(new KeywordCommand(sim.getSimulation(), kw));
				controlStartResume.requestFocusInWindow();
			}
		});
		mainToolBar.add( controlRealTime );
	}

	private void addSpeedMultiplier(JToolBar mainToolBar, Insets margin) {
		SpinnerNumberModel numberModel =
				new SpinnerModel(Simulation.DEFAULT_REAL_TIME_FACTOR,
				   Simulation.MIN_REAL_TIME_FACTOR, Simulation.MAX_REAL_TIME_FACTOR, 1);
		spinner = new JSpinner(numberModel);

		// show up to 6 decimal places
		JSpinner.NumberEditor numberEditor = new JSpinner.NumberEditor(spinner,"0.######");
		spinner.setEditor(numberEditor);

		// make sure spinner TextField is no wider than 9 digits
		int diff =
			((JSpinner.DefaultEditor)spinner.getEditor()).getTextField().getPreferredSize().width -
			getPixelWidthOfString_ForFont("9", spinner.getFont()) * 9;
		Dimension dim = spinner.getPreferredSize();
		dim.width -= diff;
		spinner.setPreferredSize(dim);
		spinner.addChangeListener(new ChangeListener() {

			@Override
			public void stateChanged( ChangeEvent e ) {
				Double val = (Double)((JSpinner)e.getSource()).getValue();
				if (MathUtils.near(val, sim.getSimulation().getRealTimeFactor()))
					return;
				NumberFormat nf = NumberFormat.getNumberInstance(Locale.US);
				DecimalFormat df = (DecimalFormat)nf;
				df.applyPattern("0.######");
				KeywordIndex kw = InputAgent.formatArgs("RealTimeFactor", df.format(val));
				InputAgent.storeAndExecute(new KeywordCommand(sim.getSimulation(), kw));
				controlStartResume.requestFocusInWindow();
			}
		});

		spinner.setToolTipText(formatToolTip("Speed Multiplier (&lt and &gt keys)",
				"Target ratio of simulation time to wall clock time when Real Time mode is selected."));
		spinner.setEnabled(false);
		mainToolBar.add( spinner );
	}

	private void addPauseTime(JToolBar mainToolBar, Insets margin) {
		pauseTime = new JTextField("0000-00-00T00:00:00") {
			@Override
			protected void processFocusEvent(FocusEvent fe) {
				if (fe.getID() == FocusEvent.FOCUS_LOST) {
					GUIFrame.this.setPauseTime(this.getText());
				}
				else if (fe.getID() == FocusEvent.FOCUS_GAINED) {
					pauseTime.selectAll();
				}
				super.processFocusEvent( fe );
			}
		};

		pauseTime.setPreferredSize(new Dimension(pauseTime.getPreferredSize().width,
				pauseTime.getPreferredSize().height));

		pauseTime.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent evt) {
				GUIFrame.this.setPauseTime(pauseTime.getText());
				controlStartResume.requestFocusInWindow();
			}
		});

		pauseTime.setText("");
		pauseTime.setHorizontalAlignment(JTextField.RIGHT);
		pauseTime.setToolTipText(formatToolTip("Pause Time",
				"Time at which to pause the run, e.g. 3 h, 10 s, etc."));

		mainToolBar.add(pauseTime);
	}

	private void addSimulationTime(JToolBar mainToolBar, Insets margin) {
		clockDisplay = new JLabel( "", JLabel.CENTER );
		clockDisplay.setPreferredSize( new Dimension( 110, 16 ) );
		clockDisplay.setForeground( new Color( 1.0f, 0.0f, 0.0f ) );
		clockDisplay.setToolTipText(formatToolTip("Simulation Time",
				"The present simulation time"));
		mainToolBar.add( clockDisplay );
	}

	private void addRunProgress(JToolBar mainToolBar, Insets margin) {
		progressBar = new JProgressBar( 0, 100 );
		progressBar.setPreferredSize( new Dimension( 120, controlRealTime.getPreferredSize().height ) );
		progressBar.setValue( 0 );
		progressBar.setStringPainted( true );
		progressBar.setToolTipText(formatToolTip("Run Progress",
				"Percent of the present simulation run that has been completed."));
		mainToolBar.add( progressBar );
	}

	private void addRemainingTime(JToolBar mainToolBar, Insets margin) {
		remainingDisplay = new JLabel( "", JLabel.CENTER );
		remainingDisplay.setPreferredSize( new Dimension( 110, 16 ) );
		remainingDisplay.setForeground( new Color( 1.0f, 0.0f, 0.0f ) );
		remainingDisplay.setToolTipText(formatToolTip("Remaining Time",
				"The remaining time required to complete the present simulation run."));
		mainToolBar.add( remainingDisplay );
	}

	private void addAchievedSpeedMultiplier(JToolBar mainToolBar, Insets margin) {
		speedUpDisplay = new JLabel( "", JLabel.CENTER );
		speedUpDisplay.setPreferredSize( new Dimension( 110, 16 ) );
		speedUpDisplay.setForeground( new Color( 1.0f, 0.0f, 0.0f ) );
		speedUpDisplay.setToolTipText(formatToolTip("Achieved Speed Multiplier",
				"The ratio of elapsed simulation time to elasped wall clock time that was achieved."));
		mainToolBar.add( speedUpDisplay );
	}

	private void addCursorPosition(JToolBar mainToolBar, Insets margin) {
		locatorPos = new JLabel( "", JLabel.CENTER );
		locatorPos.setPreferredSize( new Dimension( 140, 16 ) );
		locatorPos.setForeground( new Color( 1.0f, 0.0f, 0.0f ) );
		locatorPos.setToolTipText(formatToolTip("Cursor Position",
				"The coordinates of the cursor on the x-y plane."));
		mainToolBar.add( locatorPos );
	}

	// ******************************************************************************************************
	// RUN STATUS UPDATES
	// ******************************************************************************************************

	private long resumeSystemTime;
	private long lastSystemTime;
	private double lastSimTime;
	private double speedUp;

	public void initSpeedUp(double simTime) {
		resumeSystemTime = System.currentTimeMillis();
		lastSystemTime = resumeSystemTime;
		lastSimTime = simTime;
	}

	/**
	 * Sets the values for the simulation time, run progress, speedup factor,
	 * and remaining run time in the Control Panel's status bar.
	 *
	 * @param simTime - the present simulation time in seconds.
	 */
	void setClock(double simTime) {

		// Set the simulation time display
		String unit = Unit.getDisplayedUnit(TimeUnit.class);
		double factor = Unit.getDisplayedUnitFactor(TimeUnit.class);
		clockDisplay.setText(String.format("%,.2f  %s", simTime/factor, unit));

		// Set the run progress bar display
		long cTime = System.currentTimeMillis();
		Simulation simulation = sim.getSimulation();
		if (simulation == null) {
			setProgress(0);
			return;
		}
		double duration = simulation.getRunDuration() + simulation.getInitializationTime();
		double timeElapsed = simTime - simulation.getStartTime();
		int progress = (int)(timeElapsed * 100.0d / duration);
		this.setProgress(progress);

		// Do nothing further if the simulation is not executing events
		if (getSimState() != SIM_STATE_RUNNING)
			return;

		// Set the speedup factor display
		if (cTime - lastSystemTime > 5000L || cTime - resumeSystemTime < 5000L) {
			long elapsedMillis = cTime - lastSystemTime;
			double elapsedSimTime = timeElapsed - lastSimTime;

			// Determine the speed-up factor
			speedUp = (elapsedSimTime * 1000.0d) / elapsedMillis;
			setSpeedUp(speedUp);
			if (elapsedMillis > 5000L) {
				lastSystemTime = cTime;
				lastSimTime = timeElapsed;
			}
		}

		// Set the remaining time display
		setRemaining( (duration - timeElapsed)/speedUp );
	}

	/**
	 * Displays the given value on the Control Panel's progress bar.
	 *
	 * @param val - the percent of the run that has completed.
	 */
	public void setProgress( int val ) {
		if (lastValue == val)
			return;

		progressBar.setValue( val );
		progressBar.repaint(25);
		lastValue = val;

		if (getSimState() >= SIM_STATE_CONFIGURED) {
			String name = sim.getSimulation().getModelName();
			String title = String.format("%d%% %s - %s", val, name, sim.getRunName());
			setTitle(title);
		}
	}

	/**
	 * Write the given value on the Control Panel's speed up factor box.
	 *
	 * @param val - the speed up factor to write.
	 */
	public void setSpeedUp( double val ) {
		if (val == 0.0) {
			speedUpDisplay.setText("-");
		}
		else if (val >= 0.99) {
			speedUpDisplay.setText(String.format("%,.0f", val));
		}
		else {
			speedUpDisplay.setText(String.format("%,.6f", val));
		}
	}

	/**
	 * Write the given value on the Control Panel's remaining run time box.
	 *
	 * @param val - the remaining run time in seconds.
	 */
	public void setRemaining( double val ) {
		if (val == 0.0)
			remainingDisplay.setText("-");
		else if (val < 60.0)
			remainingDisplay.setText(String.format("%.0f seconds left", val));
		else if (val < 3600.0)
			remainingDisplay.setText(String.format("%.1f minutes left", val/60.0));
		else if (val < 3600.0*24.0)
			remainingDisplay.setText(String.format("%.1f hours left", val/3600.0));
		else if (val < 3600.0*8760.0)
			remainingDisplay.setText(String.format("%.1f days left", val/(3600.0*24.0)));
		else
			remainingDisplay.setText(String.format("%.1f years left", val/(3600.0*8760.0)));
	}

	// ******************************************************************************************************
	// SIMULATION CONTROLS
	// ******************************************************************************************************

	/**
	 * Starts or resumes the simulation run.
	 * @return true if the simulation was started or resumed; false if cancel or close was selected
	 */
	public boolean startSimulation() {
		if( getSimState() <= SIM_STATE_CONFIGURED ) {
			boolean confirmed = true;
			if (sim.isSessionEdited()) {
				confirmed = GUIFrame.showSaveChangesDialog(this);
			}
			if (confirmed) {
				sim.start();
			}
			return confirmed;
		}
		else if( getSimState() == SIM_STATE_PAUSED ) {
			sim.resume(sim.getSimulation().getPauseTime());
			return true;
		}
		else
			throw new ErrorException( "Invalid Simulation State for Start/Resume" );
	}

	/**
	 * Pauses the simulation run.
	 */
	private void pauseSimulation() {
		if( getSimState() == SIM_STATE_RUNNING )
			sim.pause();
		else
			throw new ErrorException( "Invalid Simulation State for pause" );
	}

	/**
	 * Stops the simulation run.
	 */
	public void stopSimulation() {
		if( getSimState() == SIM_STATE_RUNNING ||
		    getSimState() == SIM_STATE_PAUSED ||
		    getSimState() == SIM_STATE_ENDED) {
			sim.reset();
			FrameBox.stop();
			this.updateForSimulationState(GUIFrame.SIM_STATE_CONFIGURED);
		}
		else
			throw new ErrorException( "Invalid Simulation State for stop" );
	}

	/** model was executed, but no configuration performed */
	public static final int SIM_STATE_LOADED = 0;
	/** essential model elements created, no configuration performed */
	public static final int SIM_STATE_UNCONFIGURED = 1;
	/** model has been configured, not started */
	public static final int SIM_STATE_CONFIGURED = 2;
	/** model is presently executing events */
	public static final int SIM_STATE_RUNNING = 3;
	/** model has run, but presently is paused */
	public static final int SIM_STATE_PAUSED = 4;
	/** model is paused but cannot be resumed */
	public static final int SIM_STATE_ENDED = 5;

	private int simState;
	public int getSimState() {
		return simState;
	}

	public static void updateForSimState(int state) {
		GUIFrame inst = GUIFrame.getInstance();
		if (inst == null)
			return;

		inst.updateForSimulationState(state);
	}

	/**
	 * Sets the state of the simulation run to the given state value.
	 *
	 * @param state - an index that designates the state of the simulation run.
	 */
	void updateForSimulationState(int state) {
		simState = state;

		switch( getSimState() ) {
			case SIM_STATE_LOADED:
				for( int i = 0; i < fileMenu.getItemCount() - 1; i++ ) {
					if (fileMenu.getItem(i) == null)
						continue;
					fileMenu.getItem(i).setEnabled(true);
				}
				for( int i = 0; i < toolsMenu.getItemCount(); i++ ) {
					if (toolsMenu.getItem(i) == null)
						continue;
					toolsMenu.getItem(i).setEnabled(true);
				}

				speedUpDisplay.setEnabled( false );
				remainingDisplay.setEnabled( false );
				setSpeedUp(0);
				setRemaining(0);
				setProgress(0);
				controlStartResume.setEnabled( true );
				controlStartResume.setSelected( false );
				controlStartResume.setToolTipText(RUN_TOOLTIP);
				controlStop.setEnabled( false );
				controlStop.setSelected( false );
				lockViewXYPlane.setEnabled( true );
				progressBar.setEnabled( false );
				break;

			case SIM_STATE_UNCONFIGURED:
				for( int i = 0; i < fileMenu.getItemCount() - 1; i++ ) {
					if (fileMenu.getItem(i) == null)
						continue;
					fileMenu.getItem(i).setEnabled(true);
				}
				for( int i = 0; i < toolsMenu.getItemCount(); i++ ) {
					if (toolsMenu.getItem(i) == null)
						continue;
					toolsMenu.getItem(i).setEnabled(true);
				}

				speedUpDisplay.setEnabled( false );
				remainingDisplay.setEnabled( false );
				setSpeedUp(0);
				setRemaining(0);
				setProgress(0);
				controlStartResume.setEnabled( false );
				controlStartResume.setSelected( false );
				controlStop.setSelected( false );
				controlStop.setEnabled( false );
				lockViewXYPlane.setEnabled( true );
				progressBar.setEnabled( false );
				break;

			case SIM_STATE_CONFIGURED:
				for( int i = 0; i < fileMenu.getItemCount() - 1; i++ ) {
					if (fileMenu.getItem(i) == null)
						continue;
					fileMenu.getItem(i).setEnabled(true);
				}
				for( int i = 0; i < toolsMenu.getItemCount(); i++ ) {
					if (toolsMenu.getItem(i) == null)
						continue;
					toolsMenu.getItem(i).setEnabled(true);
				}

				speedUpDisplay.setEnabled( false );
				remainingDisplay.setEnabled( false );
				setSpeedUp(0);
				setRemaining(0);
				setProgress(0);
				controlStartResume.setEnabled( true );
				controlStartResume.setSelected( false );
				controlStartResume.setToolTipText(RUN_TOOLTIP);
				controlStop.setSelected( false );
				controlStop.setEnabled( false );
				lockViewXYPlane.setEnabled( true );
				progressBar.setEnabled( true );
				break;

			case SIM_STATE_RUNNING:
				speedUpDisplay.setEnabled( true );
				remainingDisplay.setEnabled( true );
				controlStartResume.setEnabled( true );
				controlStartResume.setSelected( true );
				controlStartResume.setToolTipText(PAUSE_TOOLTIP);
				controlStop.setEnabled( true );
				controlStop.setSelected( false );
				break;

			case SIM_STATE_PAUSED:
				controlStartResume.setEnabled( true );
				controlStartResume.setSelected( false );
				controlStartResume.setToolTipText(RUN_TOOLTIP);
				controlStop.setEnabled( true );
				controlStop.setSelected( false );
				break;

			case SIM_STATE_ENDED:
				controlStartResume.setEnabled( false );
				controlStartResume.setSelected( false );
				controlStartResume.setToolTipText(RUN_TOOLTIP);
				controlStop.setEnabled( true );
				controlStop.setSelected( false );
				break;

			default:
				throw new ErrorException( "Unrecognized Graphics State" );
		}
		fileMenu.setEnabled( true );
	}

	@Override
	public void updateAll() {
		GUIFrame.updateUI();
	}

	@Override
	public void updateObjectSelector() {
		ObjectSelector.allowUpdate();
		GUIFrame.updateUI();
	}

	@Override
	public void updateModelBuilder() {
		EntityPallet.update();
	}

	private void clearButtons() {
		if (showLinks.isSelected())
			showLinks.doClick();
		if (createLinks.isSelected())
			createLinks.doClick();
		if (reverseButton.isSelected())
			reverseButton.doClick();
	}

	public void updateControls() {
		Simulation simulation = sim.getSimulation();
		if (simulation == null)
			return;
		updateSaveButton();
		updateUndoButtons();
		updateForRealTime(simulation.isRealTime(), simulation.getRealTimeFactor());
		updateForPauseTime(simulation.getPauseTimeString());
		update2dButton();
		updateShowAxesButton();
		updateShowGridButton();
		updateNextPrevButtons();
		updateFindButton();
		updateFormatButtons(selectedEntity);
		updateForSnapToGrid(simulation.isSnapToGrid());
		updateForSnapGridSpacing(simulation.getSnapGridSpacingString());
		updateShowLabelsButton(simulation.isShowLabels());
		updateShowSubModelsButton(simulation.isShowSubModels());
		updateToolVisibilities(simulation);
		updateToolSizes(simulation);
		updateToolLocations(simulation);
		updateViewVisibilities();
		updateViewSizes();
		updateViewLocations();
		setControlPanelWidth(simulation.getControlPanelWidth());
	}

	private void updateSaveButton() {
		fileSave.setEnabled(sim.isSessionEdited());
	}

	/**
	 * updates RealTime button and Spinner
	 */
	private synchronized void updateForRealTime(boolean executeRT, double factorRT) {
		sim.getEventManager().setExecuteRealTime(executeRT, factorRT);
		controlRealTime.setSelected(executeRT);
		spinner.setValue(factorRT);
		spinner.setEnabled(executeRT);
	}

	/**
	 * updates PauseTime entry
	 */
	private void updateForPauseTime(String str) {
		if (pauseTime.getText().equals(str) || pauseTime.isFocusOwner())
			return;
		pauseTime.setText(str);
	}

	/**
	 * Sets the PauseTime keyword for Simulation.
	 * @param str - value to assign.
	 */
	private void setPauseTime(String str) {
		String prevVal = sim.getSimulation().getPauseTimeString();
		if (prevVal.equals(str))
			return;

		// If the time is in RFC8601 format, enclose in single quotes
		if (str.contains("-") || str.contains(":"))
			Parser.addQuotesIfNeeded(str);

		ArrayList<String> tokens = new ArrayList<>();
		Parser.tokenize(tokens, str, true);

		// if we only got one token, and it isn't RFC8601 - add a unit
		if (tokens.size() == 1 && !tokens.get(0).contains("-") && !tokens.get(0).contains(":"))
			tokens.add(Unit.getDisplayedUnit(TimeUnit.class));

		try {
			// Parse the keyword inputs
			KeywordIndex kw = new KeywordIndex("PauseTime", tokens, null);
			InputAgent.storeAndExecute(new KeywordCommand(sim.getSimulation(), kw));
		}
		catch (InputErrorException e) {
			pauseTime.setText(prevVal);
			GUIFrame.showErrorDialog("Input Error", e.getMessage());
		}
	}

	/**
	 * Assigns a new name to the given entity.
	 * @param ent - entity to be renamed
	 * @param newName - new absolute name for the entity
	 */
	@Override
	public void renameEntity(Entity ent, String newName) {

		// If the name has not changed, do nothing
		if (ent.getName().equals(newName))
			return;

		// Check that the entity was defined AFTER the RecordEdits command
		if (!ent.isAdded())
			throw new ErrorException("Cannot rename an entity that was defined before the RecordEdits command.");

		// Get the new local name
		String localName = newName;
		if (newName.contains(".")) {
			String[] names = newName.split("\\.");
			localName = names[names.length - 1];
			names = Arrays.copyOf(names, names.length - 1);
			Entity parent = sim.getEntityFromNames(names);
			if (parent != ent.getParent())
				throw new ErrorException("Cannot rename the entity's parent");
		}

		// Check that the new name is valid
		if (!InputAgent.isValidName(localName))
			throw new ErrorException(InputAgent.INP_ERR_BADNAME, localName);

		// Rename the entity
		InputAgent.storeAndExecute(new RenameCommand(ent, newName));
	}

	@Override
	public void deleteEntity(Entity ent) {

		if (ent.isGenerated())
			throw new ErrorException("Cannot delete an entity that was generated by a simulation "
					+ "object.");

		if (!ent.isAdded())
			throw new ErrorException("Cannot delete an entity that was defined prior to "
					+ "RecordEdits in the input file.");

		if (ent instanceof DisplayEntity && !((DisplayEntity) ent).isMovable())
			throw new ErrorException("Cannot delete an entity that is not movable.");

		// Delete any child entities
		for (Entity child : ent.getChildren()) {
			if (child.isGenerated() || child instanceof EntityLabel)
				child.kill();
			else
				deleteEntity(child);
		}

		// Region
		if (ent instanceof Region) {

			// Reset the Region input for the entities in this region
			KeywordIndex kw = InputAgent.formatArgs("Region");
			for (DisplayEntity e : sim.getClonesOfIterator(DisplayEntity.class)) {
				if (e == ent || e.getInput("Region").getValue() != ent)
					continue;
				InputAgent.storeAndExecute(new CoordinateCommand(e, kw));
			}
		}

		// DisplayEntity
		if (ent instanceof DisplayEntity) {
			DisplayEntity dEnt = (DisplayEntity) ent;

			// Kill the label
			EntityLabel label = EntityLabel.getLabel(dEnt);
			if (label != null)
				deleteEntity(label);

			// Reset the RelativeEntity input for entities
			KeywordIndex kw = InputAgent.formatArgs("RelativeEntity");
			for (DisplayEntity e : sim.getClonesOfIterator(DisplayEntity.class)) {
				if (e == ent || e.getInput("RelativeEntity").getValue() != ent)
					continue;
				InputAgent.storeAndExecute(new CoordinateCommand(e, kw));
			}
		}

		// Delete any references to this entity in the inputs to other entities
		for (Entity e : sim.getClonesOfIterator(Entity.class)) {
			if (e == ent)
				continue;
			ArrayList<KeywordIndex> oldKwList = new ArrayList<>();
			ArrayList<KeywordIndex> newKwList = new ArrayList<>();
			for (Input<?> in : e.getEditableInputs()) {
				ArrayList<String> oldTokens = in.getValueTokens();
				boolean changed = in.removeReferences(ent);
				if (!changed)
					continue;
				KeywordIndex oldKw = new KeywordIndex(in.getKeyword(), oldTokens, null);
				KeywordIndex newKw = new KeywordIndex(in.getKeyword(), in.getValueTokens(), null);
				oldKwList.add(oldKw);
				newKwList.add(newKw);
			}

			// Reload any inputs that have changed so that redo/undo works correctly
			if (newKwList.isEmpty())
				continue;
			KeywordIndex[] oldKws = new KeywordIndex[oldKwList.size()];
			KeywordIndex[] newKws = new KeywordIndex[newKwList.size()];
			oldKws = oldKwList.toArray(oldKws);
			newKws = newKwList.toArray(newKws);
			InputAgent.storeAndExecute(new KeywordCommand(e, 0, oldKws, newKws));
		}

		// Execute the delete command
		InputAgent.storeAndExecute(new DeleteCommand(ent));
	}

	@Override
	public void storeAndExecute(Command cmd) {
		synchronized (undoList) {
			if (!cmd.isChange())
				return;

			// Execute the command and catch an error if it occurs
			cmd.execute();

			// Attempt to merge the command with the previous one
			Command mergedCmd = null;
			if (!undoList.isEmpty()) {
				Command lastCmd = undoList.get(undoList.size() - 1);
				mergedCmd = lastCmd.tryMerge(cmd);
			}

			// If the new command can be combined, then change the entry for previous command
			if (mergedCmd != null) {
				if (mergedCmd.isChange())
					undoList.set(undoList.size() - 1, mergedCmd);
				else
					undoList.remove(undoList.size() - 1);
			}

			// If the new command cannot be combined, then add it to the undo list
			else {
				undoList.add(cmd);
			}

			// Clear the re-do list
			redoList.clear();
		}
		updateUI();
	}

	public void undo() {
		synchronized (undoList) {
			if (undoList.isEmpty())
				return;
			Command cmd = undoList.remove(undoList.size() - 1);
			redoList.add(cmd);
			cmd.undo();
		}
		updateUI();
	}

	public void undo(Entity ent, String keyword) {
		synchronized (undoList) {
			if (undoList.isEmpty())
				return;
			Command cmd = undoList.get(undoList.size() - 1);
			if (!(cmd instanceof KeywordCommand))
				return;
			KeywordCommand kwCmd = (KeywordCommand) cmd;
			if (kwCmd.getEntity() != ent || kwCmd.getKws().length != 1
					|| !kwCmd.getKws()[0].keyword.equals(keyword))
				return;
			undo();
		}
	}

	public void redo() {
		synchronized (undoList) {
			if (redoList.isEmpty())
				return;
			Command cmd = redoList.remove(redoList.size() - 1);
			undoList.add(cmd);
			cmd.execute();
		}
		updateUI();
	}

	public void undo(int n) {
		synchronized (undoList) {
			for (int i = 0; i < n; i++) {
				undo();
			}
		}
	}

	public void redo(int n) {
		synchronized (undoList) {
			for (int i = 0; i < n; i++) {
				redo();
			}
		}
	}

	public void invokeUndo() {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				undo();
			}
		});
	}

	public void invokeRedo() {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				redo();
			}
		});
	}

	public void updateUndoButtons() {
		synchronized (undoList) {
			undo.setEnabled(!undoList.isEmpty());
			undoDropdown.setEnabled(!undoList.isEmpty());
			undoMenuItem.setEnabled(!undoList.isEmpty());

			redo.setEnabled(!redoList.isEmpty());
			redoDropdown.setEnabled(!redoList.isEmpty());
			redoMenuItem.setEnabled(!redoList.isEmpty());
		}
	}

	public void clearUndoRedo() {
		synchronized (undoList) {
			undoList.clear();
			redoList.clear();
		}
		updateUI();
	}

	private void updateForSnapGridSpacing(String str) {
		if (gridSpacing.getText().equals(str) || gridSpacing.hasFocus())
			return;
		gridSpacing.setText(str);
	}

	private void setSnapGridSpacing(String str) {
		Input<?> in = sim.getSimulation().getInput("SnapGridSpacing");
		String prevVal = in.getValueString();
		if (prevVal.equals(str))
			return;

		if (str.isEmpty()) {
			gridSpacing.setText(prevVal);
			return;
		}

		try {
			KeywordIndex kw = InputAgent.formatInput("SnapGridSpacing", str);
			InputAgent.storeAndExecute(new KeywordCommand(sim.getSimulation(), kw));
		}
		catch (InputErrorException e) {
			gridSpacing.setText(prevVal);
			GUIFrame.showErrorDialog("Input Error", e.getMessage());
		}
	}

	private void updateForSnapToGrid(boolean bool) {
		snapToGrid.setSelected(bool);
		gridSpacing.setEnabled(bool);
	}

	private void update2dButton() {
		if (!RenderManager.isGood())
			return;
		View view = RenderManager.inst().getActiveView();
		if (view == null)
			return;
		lockViewXYPlane.setSelected(view.is2DLocked());
	}

	private void updateShowAxesButton() {
		DisplayEntity ent = (DisplayEntity) sim.getNamedEntity("XYZ-Axis");
		xyzAxis.setSelected(ent != null && ent.getShow());
	}

	private void updateShowGridButton() {
		DisplayEntity ent = (DisplayEntity) sim.getNamedEntity("XY-Grid");
		grid.setSelected(ent != null && ent.getShow());
	}

	private void updateNextPrevButtons() {
		if (selectedEntity != null && selectedEntity instanceof DisplayEntity) {
			boolean dir = !reverseButton.isSelected();
			DisplayEntity selectedDEnt = (DisplayEntity) selectedEntity;
			prevButton.setEnabled(!selectedDEnt.getPreviousList(dir).isEmpty());
			nextButton.setEnabled(!selectedDEnt.getNextList(dir).isEmpty());
			return;
		}
		prevButton.setEnabled(false);
		nextButton.setEnabled(false);
	}

	private void updateFindButton() {
		boolean bool = FindBox.getInstance().isVisible();
		find.setSelected(bool);
	}

	public static void setSelectedEntity(Entity ent) {
		if (instance == null)
			return;
		instance.setSelectedEnt(ent);
	}

	public void setSelectedEnt(Entity ent) {
		selectedEntity = ent;
	}

	private void updateFormatButtons(Entity ent) {
		updateEditButtons(ent);
		updateDisplayModelButtons(ent);
		updateClearFormattingButton(ent);
		updateTextButtons(ent);
		updateZButtons(ent);
		updateLineButtons(ent);
		updateFillButtons(ent);
	}

	private void updateEditButtons(Entity ent) {
		boolean bool = (ent != null && ent != sim.getSimulation());
		copyButton.setEnabled(bool);
		copyMenuItem.setEnabled(bool);
		deleteMenuItem.setEnabled(bool);
	}

	public void updateDisplayModelButtons(Entity ent) {
		boolean bool = ent instanceof DisplayEntity
				&& ((DisplayEntity)ent).isGraphicsNominal()
				&& ((DisplayEntity)ent).getDisplayModelList().size() == 1;

		dispModel.setEnabled(bool);
		modelSelector.setEnabled(bool);
		editDmButton.setEnabled(bool);
		if (!bool) {
			dispModel.setText("");
			return;
		}

		DisplayEntity dispEnt = (DisplayEntity) ent;
		String name = dispEnt.getDisplayModelList().get(0).getName();
		if (!dispModel.getText().equals(name))
			dispModel.setText(name);
	}

	public void updateClearFormattingButton(Entity ent) {
		if (ent == null) {
			clearButton.setEnabled(false);
			return;
		}
		boolean bool = false;
		for (Input<?> in : ent.getEditableInputs()) {
			String cat = in.getCategory();
			if (!cat.equals(Entity.FORMAT) && !cat.equals(Entity.FONT))
				continue;
			if (!in.isDefault()) {
				bool = true;
				break;
			}
		}
		clearButton.setEnabled(bool);
	}

	private void updateTextButtons(Entity ent) {
		boolean bool = ent instanceof TextEntity;

		boolean isAlignable = bool && ent instanceof DisplayEntity
				&& !(ent instanceof OverlayText) && !(ent instanceof BillboardText);
		alignLeft.setEnabled(isAlignable);
		alignCentre.setEnabled(isAlignable);
		alignRight.setEnabled(isAlignable);
		if (!isAlignable) {
			alignmentGroup.clearSelection();
		}

		bold.setEnabled(bool);
		italic.setEnabled(bool);
		font.setEnabled(bool);
		fontSelector.setEnabled(bool);
		textHeight.setEnabled(bool);
		largerText.setEnabled(bool);
		smallerText.setEnabled(bool);
		fontColour.setEnabled(bool);
		if (!bool) {
			font.setText("");
			textHeight.setText(null);
			bold.setSelected(false);
			italic.setSelected(false);
			colourIcon.setFillColor(Color.LIGHT_GRAY);
			colourIcon.setOutlineColor(Color.LIGHT_GRAY);
			return;
		}

		if (isAlignable) {
			int val = (int) Math.signum(((DisplayEntity) ent).getAlignment().x);
			alignLeft.setSelected(val == -1);
			alignCentre.setSelected(val == 0);
			alignRight.setSelected(val == 1);
		}

		TextEntity textEnt = (TextEntity) ent;
		bold.setSelected(textEnt.isBold());
		italic.setSelected(textEnt.isItalic());
		String fontName = textEnt.getFontName();
		if (!font.getText().equals(fontName))
			font.setText(fontName);
		updateTextHeight(textEnt.getTextHeightString());

		Color4d col = textEnt.getFontColor();
		colourIcon.setFillColor(new Color((float)col.r, (float)col.g, (float)col.b, (float)col.a));
		colourIcon.setOutlineColor(Color.DARK_GRAY);
		fontColour.repaint();
	}

	private void updateTextHeight(String str) {
		if (textHeight.getText().equals(str) || textHeight.hasFocus())
			return;
		textHeight.setText(str);
	}

	private void updateZButtons(Entity ent) {
		boolean bool = ent instanceof DisplayEntity;
		bool = bool && !(ent instanceof OverlayEntity);
		bool = bool && !(ent instanceof BillboardText);
		increaseZ.setEnabled(bool);
		decreaseZ.setEnabled(bool);
	}

	private void updateLineButtons(Entity ent) {
		boolean bool = ent instanceof LineEntity;
		outline.setEnabled(bool && ent instanceof FillEntity);
		lineWidth.setEnabled(bool);
		lineColour.setEnabled(bool);
		if (!bool) {
			lineWidth.setValue(1);
			lineColourIcon.setFillColor(Color.LIGHT_GRAY);
			lineColourIcon.setOutlineColor(Color.LIGHT_GRAY);
			return;
		}

		LineEntity lineEnt = (LineEntity) ent;
		outline.setSelected(lineEnt.isOutlined());
		lineWidth.setEnabled(lineEnt.isOutlined());
		lineColour.setEnabled(lineEnt.isOutlined());

		lineWidth.setValue(Integer.valueOf(lineEnt.getLineWidth()));

		Color4d col = lineEnt.getLineColour();
		lineColourIcon.setFillColor(new Color((float)col.r, (float)col.g, (float)col.b, (float)col.a));
		lineColourIcon.setOutlineColor(Color.DARK_GRAY);
		lineColour.repaint();
	}

	private void updateFillButtons(Entity ent) {
		boolean bool = ent instanceof FillEntity;
		fill.setEnabled(bool);
		fillColour.setEnabled(bool);
		if (!bool) {
			fillColourIcon.setFillColor(Color.LIGHT_GRAY);
			fillColourIcon.setOutlineColor(Color.LIGHT_GRAY);
			return;
		}

		FillEntity fillEnt = (FillEntity) ent;
		fill.setSelected(fillEnt.isFilled());
		fillColour.setEnabled(fillEnt.isFilled());

		Color4d col = fillEnt.getFillColour();
		fillColourIcon.setFillColor(new Color((float)col.r, (float)col.g, (float)col.b, (float)col.a));
		fillColourIcon.setOutlineColor(Color.DARK_GRAY);
		fillColour.repaint();
	}

	private void setTextHeight(String str) {
		if (!(selectedEntity instanceof TextEntity))
			return;
		TextEntity textEnt = (TextEntity) selectedEntity;
		if (str.equals(textEnt.getTextHeightString()))
			return;

		try {
			KeywordIndex kw = InputAgent.formatInput("TextHeight", str);
			InputAgent.storeAndExecute(new KeywordCommand((Entity)textEnt, kw));
		}
		catch (InputErrorException e) {
			textHeight.setText(textEnt.getTextHeightString());
			GUIFrame.showErrorDialog("Input Error", e.getMessage());
		}
	}

	public static Image getWindowIcon() {
		return iconImage;
	}

	public void copyLocationToClipBoard(Vec3d pos) {
		String data = String.format("(%.3f, %.3f, %.3f)", pos.x, pos.y, pos.z);
		StringSelection stringSelection = new StringSelection(data);
		Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
		clipboard.setContents( stringSelection, null );
	}

	public static void showLocatorPosition(Vec3d pos) {
		GUIFrame inst = GUIFrame.getInstance();
		if (inst == null)
			return;

		inst.showLocator(pos);
	}

	private void showLocator(Vec3d pos) {
		if( pos == null ) {
			locatorPos.setText( "-" );
			return;
		}

		String unit = Unit.getDisplayedUnit(DistanceUnit.class);
		double factor = Unit.getDisplayedUnitFactor(DistanceUnit.class);
		locatorPos.setText(String.format((Locale)null, "%.3f  %.3f  %.3f  %s",
				pos.x/factor, pos.y/factor, pos.z/factor, unit));
	}

	public void enableSave(boolean bool) {
		saveConfigurationMenuItem.setEnabled(bool);
	}

	/**
	 * Sets variables used to determine the position and size of various
	 * windows based on the size of the computer display being used.
	 */
	private void calcWindowDefaults() {
		Dimension guiSize = this.getSize();
		Rectangle winSize = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();

		DEFAULT_GUI_WIDTH = winSize.width;
		COL1_WIDTH = 220;
		COL4_WIDTH = 520;
		int middleWidth = DEFAULT_GUI_WIDTH - COL1_WIDTH - COL4_WIDTH;
		COL2_WIDTH = Math.max(520, middleWidth / 2);
		COL3_WIDTH = Math.max(420, middleWidth - COL2_WIDTH);
		VIEW_WIDTH = DEFAULT_GUI_WIDTH - COL1_WIDTH;

		COL1_START = this.getX();
		COL2_START = COL1_START + COL1_WIDTH;
		COL3_START = COL2_START + COL2_WIDTH;
		COL4_START = Math.min(COL3_START + COL3_WIDTH, winSize.width - COL4_WIDTH);

		HALF_TOP = (winSize.height - guiSize.height) / 2;
		HALF_BOTTOM = (winSize.height - guiSize.height - HALF_TOP);
		LOWER_HEIGHT = Math.min(250, (winSize.height - guiSize.height) / 3);
		VIEW_HEIGHT = winSize.height - guiSize.height - LOWER_HEIGHT;

		TOP_START = this.getY() + guiSize.height;
		BOTTOM_START = TOP_START + HALF_TOP;
		LOWER_START = TOP_START + VIEW_HEIGHT;
	}

	public void setShowLabels(boolean bool) {
		for (DisplayEntity ent : sim.getClonesOfIterator(DisplayEntity.class)) {
			if (!EntityLabel.canLabel(ent))
				continue;
			EntityLabel.showTemporaryLabel(ent, bool);
		}
	}

	public void setShowSubModels(boolean bool) {
		for (CompoundEntity submodel : sim.getClonesOfIterator(CompoundEntity.class)) {
			submodel.showTemporaryComponents(bool);
		}
	}

	private void updateShowLabelsButton(boolean bool) {
		if (showLabels.isSelected() == bool)
			return;
		showLabels.setSelected(bool);
		setShowLabels(bool);
		updateUI();
	}

	private void updateShowSubModelsButton(boolean bool) {
		if (showSubModels.isSelected() == bool)
			return;
		showSubModels.setSelected(bool);
		setShowSubModels(bool);
		updateUI();
	}

	/**
	 * Re-open any Tools windows that have been closed temporarily.
	 */
	public void showActiveTools(Simulation simulation) {
		EntityPallet.getInstance().setVisible(simulation.isModelBuilderVisible());
		ObjectSelector.getInstance().setVisible(simulation.isObjectSelectorVisible());
		EditBox.getInstance().setVisible(simulation.isInputEditorVisible());
		OutputBox.getInstance().setVisible(simulation.isOutputViewerVisible());
		PropertyBox.getInstance().setVisible(simulation.isPropertyViewerVisible());
		LogBox.getInstance().setVisible(simulation.isLogViewerVisible());

		if (!simulation.isEventViewerVisible()) {
			if (EventViewer.hasInstance())
				EventViewer.getInstance().dispose();
			return;
		}
		EventViewer.getInstance().setVisible(true);
	}

	/**
	 * Closes all the Tools windows temporarily.
	 */
	public void closeAllTools() {
		EntityPallet.getInstance().setVisible(false);
		ObjectSelector.getInstance().setVisible(false);
		EditBox.getInstance().setVisible(false);
		OutputBox.getInstance().setVisible(false);
		PropertyBox.getInstance().setVisible(false);
		LogBox.getInstance().setVisible(false);
		if (EventViewer.hasInstance())
			EventViewer.getInstance().setVisible(false);
	}

	public boolean isIconified() {
		return getExtendedState() == Frame.ICONIFIED;
	}

	private void updateToolVisibilities(Simulation simulation) {
		boolean iconified = isIconified();
		setFrameVisibility(EntityPallet.getInstance(), !iconified && simulation.isModelBuilderVisible());
		setFrameVisibility(ObjectSelector.getInstance(), !iconified && simulation.isObjectSelectorVisible());
		setFrameVisibility(EditBox.getInstance(), !iconified && simulation.isInputEditorVisible());
		setFrameVisibility(OutputBox.getInstance(), !iconified && simulation.isOutputViewerVisible());
		setFrameVisibility(PropertyBox.getInstance(), !iconified && simulation.isPropertyViewerVisible());
		setFrameVisibility(LogBox.getInstance(), !iconified && simulation.isLogViewerVisible());

		if (!simulation.isEventViewerVisible()) {
			if (EventViewer.hasInstance())
				EventViewer.getInstance().dispose();
			return;
		}
		setFrameVisibility(EventViewer.getInstance(), !iconified);
	}

	private void setFrameVisibility(JFrame frame, boolean bool) {
		if (frame.isVisible() == bool)
			return;
		frame.setVisible(bool);
		if (bool)
			frame.toFront();
	}

	public void updateToolSizes(Simulation simulation) {
		EntityPallet.getInstance().setSize(simulation.getModelBuilderSize().get(0),
				simulation.getModelBuilderSize().get(1));
		ObjectSelector.getInstance().setSize(simulation.getObjectSelectorSize().get(0),
				simulation.getObjectSelectorSize().get(1));
		EditBox.getInstance().setSize(simulation.getInputEditorSize().get(0),
				simulation.getInputEditorSize().get(1));
		OutputBox.getInstance().setSize(simulation.getOutputViewerSize().get(0),
				simulation.getOutputViewerSize().get(1));
		PropertyBox.getInstance().setSize(simulation.getPropertyViewerSize().get(0),
				simulation.getPropertyViewerSize().get(1));
		LogBox.getInstance().setSize(simulation.getLogViewerSize().get(0),
				simulation.getLogViewerSize().get(1));
		if (EventViewer.hasInstance()) {
			EventViewer.getInstance().setSize(simulation.getEventViewerSize().get(0),
					simulation.getEventViewerSize().get(1));
		}
	}

	public void updateToolLocations(Simulation simulation) {
		setToolLocation(EntityPallet.getInstance(), simulation.getModelBuilderPos().get(0),
				simulation.getModelBuilderPos().get(1));
		setToolLocation(ObjectSelector.getInstance(), simulation.getObjectSelectorPos().get(0),
				simulation.getObjectSelectorPos().get(1));
		setToolLocation(EditBox.getInstance(), simulation.getInputEditorPos().get(0),
				simulation.getInputEditorPos().get(1));
		setToolLocation(OutputBox.getInstance(), simulation.getOutputViewerPos().get(0),
				simulation.getOutputViewerPos().get(1));
		setToolLocation(PropertyBox.getInstance(), simulation.getPropertyViewerPos().get(0),
				simulation.getPropertyViewerPos().get(1));
		setToolLocation(LogBox.getInstance(), simulation.getLogViewerPos().get(0),
				simulation.getLogViewerPos().get(1));
		if (EventViewer.hasInstance()) {
			setToolLocation(EventViewer.getInstance(), simulation.getEventViewerPos().get(0),
					simulation.getEventViewerPos().get(1));
		}
	}

	public void setToolLocation(JFrame tool, int x, int y) {
		Point pt = getGlobalLocation(x, y);
		tool.setLocation(pt);
	}

	private void updateViewVisibilities() {
		if (!RenderManager.isGood())
			return;
		boolean iconified = isIconified();
		for (View v : views) {
			boolean isVisible = RenderManager.inst().isVisible(v);
			if (!iconified && v.showWindow()) {
				if (!isVisible) {
					RenderManager.inst().createWindow(v);
				}
			}
			else {
				if (isVisible) {
					RenderManager.inst().closeWindow(v);
				}
			}
		}
	}

	private void updateViewSizes() {
		for (View v : views) {
			final Frame window = RenderManager.getOpenWindowForView(v);
			if (window == null)
				continue;
			IntegerVector size = getWindowSize(v);
			window.setSize(size.get(0), size.get(1));
		}
	}

	public void updateViewLocations() {
		for (View v : views) {
			final Frame window = RenderManager.getOpenWindowForView(v);
			if (window == null)
				continue;
			IntegerVector pos = getWindowPos(v);
			window.setLocation(pos.get(0), pos.get(1));
		}
	}

	public void setControlPanelWidth(int width) {
		int height = getSize().height;
		setSize(width, height);
	}

	public void setWindowDefaults(Simulation simulation) {
		// Set the defaults from the AWT thread to avoid synchronization problems with updateUI and
		// the SizePosAdapter for the tool windows
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				simulation.setModelBuilderDefaults(   COL1_START, TOP_START,     COL1_WIDTH, HALF_TOP    );
				simulation.setObjectSelectorDefaults( COL1_START, BOTTOM_START,  COL1_WIDTH, HALF_BOTTOM );
				simulation.setInputEditorDefaults(    COL2_START, LOWER_START,   COL2_WIDTH, LOWER_HEIGHT);
				simulation.setOutputViewerDefaults(   COL3_START, LOWER_START,   COL3_WIDTH, LOWER_HEIGHT);
				simulation.setPropertyViewerDefaults( COL4_START, LOWER_START,   COL4_WIDTH, LOWER_HEIGHT);
				simulation.setLogViewerDefaults(      COL4_START, LOWER_START,   COL4_WIDTH, LOWER_HEIGHT);
				simulation.setEventViewerDefaults(    COL4_START, LOWER_START,   COL4_WIDTH, LOWER_HEIGHT);
				simulation.setControlPanelWidthDefault(DEFAULT_GUI_WIDTH);
				View.setDefaultPosition(COL2_START, TOP_START);
				View.setDefaultSize(VIEW_WIDTH, VIEW_HEIGHT);
				updateControls();
				clearUndoRedo();
			}
		});
	}

	public ArrayList<View> getViews() {
		synchronized (views) {
			return views;
		}
	}

	@Override
	public void addView(View v) {
		synchronized (views) {
			views.add(v);
		}
	}

	@Override
	public void removeView(View v) {
		synchronized (views) {
			views.remove(v);
		}
	}

	@Override
	public void createWindow(View v) {
		if (!RenderManager.isGood())
			return;
		RenderManager.inst().createWindow(v);
	}

	@Override
	public void closeWindow(View v) {
		if (!RenderManager.isGood())
			return;
		RenderManager.inst().closeWindow(v);
	}

	@Override
	public int getNextViewID() {
		nextViewID++;
		return nextViewID;
	}

	public IntegerVector getWindowPos(View v) {
		Point fix = OSFix.getLocationAdustment();  //FIXME
		IntegerVector ret = new IntegerVector(v.getWindowPos());
		Point pt = getGlobalLocation(ret.get(0), ret.get(1));
		ret.set(0, pt.x + fix.x);
		ret.set(1, pt.y + fix.y);
		return ret;
	}

	public IntegerVector getWindowSize(View v) {
		Point fix = OSFix.getSizeAdustment();  //FIXME
		IntegerVector ret = new IntegerVector(v.getWindowSize());
		ret.addAt(fix.x, 0);
		ret.addAt(fix.y, 1);
		return ret;
	}

	public void setWindowPos(View v, int x, int y, int width, int height) {
		Point posFix = OSFix.getLocationAdustment();
		Point sizeFix = OSFix.getSizeAdustment();
		Point pt = getRelativeLocation(x - posFix.x, y - posFix.y);
		v.setWindowPos(pt.x, pt.y, width - sizeFix.x, height - sizeFix.y);
	}

	@Override
	public Vec3d getPOI(View v) {
		if (!RenderManager.isGood())
			return new Vec3d();
		return RenderManager.inst().getPOI(v);
	}

	// ******************************************************************************************************
	// MAIN
	// ******************************************************************************************************

	public static void main( String args[] ) {
		// Process the input arguments and filter out directives
		ArrayList<String> configFiles = new ArrayList<>(args.length);
		boolean batch = false;
		boolean minimize = false;
		boolean quiet = false;
		boolean scriptMode = false;
		boolean headless = false;

		for (String each : args) {
			// Batch mode
			if (each.equalsIgnoreCase("-b") ||
			    each.equalsIgnoreCase("-batch")) {
				batch = true;
				continue;
			}
			// Script mode (command line I/O)
			if (each.equalsIgnoreCase("-s") ||
			    each.equalsIgnoreCase("-script")) {
				scriptMode = true;
				continue;
			}
			// z-buffer offset
			if (each.equalsIgnoreCase("-z") ||
			    each.equalsIgnoreCase("-zbuffer")) {
				// Parse the option, but do nothing
				continue;
			}
			// Minimize model window
			if (each.equalsIgnoreCase("-m") ||
			    each.equalsIgnoreCase("-minimize")) {
				minimize = true;
				continue;
			}
			// Minimize model window
			if (each.equalsIgnoreCase("-h") ||
			    each.equalsIgnoreCase("-headless")) {
				headless = true;
				batch = true;
				continue;
			}
			// Do not open default windows
			if (each.equalsIgnoreCase("-q") ||
			    each.equalsIgnoreCase("-quiet")) {
				quiet = true;
				continue;
			}
			if (each.equalsIgnoreCase("-sg") ||
			    each.equalsIgnoreCase("-safe_graphics")) {
				SAFE_GRAPHICS = true;
				continue;
			}
			// Not a program directive, add to list of config files
			configFiles.add(each);
		}

		// If not running in batch mode, create the splash screen
		JWindow splashScreen = null;
		if (!batch) {
			URL splashImage = GUIFrame.class.getResource("/resources/images/splashscreen.png");
			ImageIcon imageIcon = new ImageIcon(splashImage);
			Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
			int splashX = (screen.width - imageIcon.getIconWidth()) / 2;
			int splashY = (screen.height - imageIcon.getIconHeight()) / 2;

			// Set the window's bounds, centering the window
			splashScreen = new JWindow();
			splashScreen.setAlwaysOnTop(true);
			splashScreen.setBounds(splashX, splashY, imageIcon.getIconWidth(), imageIcon.getIconHeight());

			// Build the splash screen
			splashScreen.getContentPane().add(new JLabel(imageIcon));

			// Display it
			splashScreen.setVisible(true);
		}

		// create a graphic simulation
		LogBox.logLine("Loading Simulation Environment ... ");
		sim = new JaamSimModel("GUI_Model");

		GUIFrame gui = null;
		if (!headless) {
			gui = GUIFrame.createInstance();
			gui.updateForSimulationState(SIM_STATE_LOADED);
			sim.setTimeListener(gui);
			sim.setGUIListener(gui);

			if (minimize)
				gui.setExtendedState(JFrame.ICONIFIED);
			// This is only here to initialize the static cache in the MRG1999a class to avoid future latency
			// when initializing other objects in drag+drop
			@SuppressWarnings("unused")
			MRG1999a cacher = new MRG1999a();
		}

		if (!batch && !headless) {
			// Begin initializing the rendering system
			RenderManager.initialize(SAFE_GRAPHICS);
		}

		LogBox.logLine("Simulation Environment Loaded");

		sim.setBatchRun(batch);
		sim.setScriptMode(scriptMode);

		// Load the autoload file
		sim.autoLoad();
		Simulation simulation = sim.getSimulation();

		// Show the Control Panel
		if (gui != null) {
			gui.setTitle(simulation.getModelName());
			gui.setVisible(true);
			gui.calcWindowDefaults();
			gui.setLocation(gui.getX(), gui.getY());  //FIXME remove when setLocation is fixed for Windows 10
			gui.setWindowDefaults(simulation);
			EntityPallet.update();
		}

		// Resolve all input arguments against the current working directory
		File user = new File(System.getProperty("user.dir"));
		// Process any configuration files passed on command line
		// (Multiple configuration files are not supported at present)
		for (int i = 0; i < configFiles.size(); i++) {
			//InputAgent.configure(gui, new File(configFiles.get(i)));
			File abs = new File((File)null, configFiles.get(i));
			File loadFile;
			if (abs.exists())
				loadFile = abs.getAbsoluteFile();
			else
				loadFile = new File(user, configFiles.get(i));

			Throwable t = GUIFrame.configure(loadFile);
			if (t != null) {
				// Hide the splash screen
				if (splashScreen != null) {
					splashScreen.dispose();
					splashScreen = null;
				}
				handleConfigError(t, loadFile);
			}
		}

		// If in script mode, load a configuration file from standard in
		if (scriptMode) {
			BufferedReader buf = new BufferedReader(new InputStreamReader(System.in));
			InputAgent.readBufferedStream(sim, buf, null, "");
		}

		// If no configuration files were specified on the command line, then load the default configuration file
		if (configFiles.size() == 0 && !scriptMode) {
			// Load the default model from the AWT thread to avoid synchronization problems with updateUI and
			// setWindowDefaults
			SwingUtilities.invokeLater(new Runnable() {
				@Override
				public void run() {
					sim.setRecordEdits(true);
					InputAgent.loadDefault(sim);
					GUIFrame.updateForSimState(GUIFrame.SIM_STATE_CONFIGURED);
				}
			});
		}

		// If in batch or quiet mode, close the any tools that were opened
		if (quiet || batch) {
			if (gui != null)
				gui.closeAllTools();
		}

		// Set RecordEdits mode (if it has not already been set in the configuration file)
		sim.setRecordEdits(true);

		// Start the model if in batch mode
		if (batch) {
			if (sim.getNumErrors() > 0)
				GUIFrame.shutdown(0);
			sim.start();
			return;
		}

		// Hide the splash screen
		if (splashScreen != null) {
			splashScreen.dispose();
			splashScreen = null;
		}

		// Bring the Control Panel to the front (along with any open Tools)
		if (gui != null)
			gui.toFront();

		// Set the selected entity to the Simulation object
		FrameBox.setSelectedEntity(simulation, false);
	}

	/*
	 * this class is created so the next value will be value * 2 and the
	 * previous value will be value / 2
	 */
	public static class SpinnerModel extends SpinnerNumberModel {
		private double value;
		public SpinnerModel( double val, double min, double max, double stepSize) {
			super(val, min, max, stepSize);
		}

		@Override
		public Object getPreviousValue() {
			value = this.getNumber().doubleValue() / 2.0;
			if (value >= 1.0)
				value = Math.floor(value);

			// Avoid going beyond limit
			Double min = (Double)this.getMinimum();
			if (min.doubleValue() > value) {
				return min;
			}
			return value;
		}

		@Override
		public Object getNextValue() {
			value = this.getNumber().doubleValue() * 2.0;
			if (value >= 1.0)
				value = Math.floor(value);

			// Avoid going beyond limit
			Double max = (Double)this.getMaximum();
			if (max.doubleValue() < value) {
				return max;
			}
			return value;
		}
	}

	public static boolean getShuttingDownFlag() {
		return shuttingDown.get();
	}

	public static void shutdown(int errorCode) {

		shuttingDown.set(true);
		if (RenderManager.isGood()) {
			RenderManager.inst().shutdown();
		}

		System.exit(errorCode);
	}

	@Override
	public void exit(int errorCode) {
		shutdown(errorCode);
	}

	volatile long simTicks;

	private static class UIUpdater implements Runnable {
		private final GUIFrame frame;

		UIUpdater(GUIFrame gui) {
			frame = gui;
		}

		@Override
		public void run() {
			EventManager evt = GUIFrame.getJaamSimModel().getEventManager();
			double callBackTime = evt.ticksToSeconds(frame.simTicks);

			frame.setClock(callBackTime);
			frame.updateControls();
			FrameBox.updateEntityValues(callBackTime);
		}
	}

	@Override
	public void tickUpdate(long tick) {
		if (tick == simTicks)
			return;

		simTicks = tick;
		RenderManager.updateTime(tick);
		GUIFrame.updateUI();
	}

	@Override
	public void timeRunning() {
		EventManager evt = EventManager.current();
		long tick = evt.getTicks();
		boolean running = evt.isRunning();
		if (running) {
			initSpeedUp(evt.ticksToSeconds(tick));
			updateForSimulationState(SIM_STATE_RUNNING);
		}
		else {
			int state = SIM_STATE_PAUSED;
			if (!sim.getSimulation().canResume(simTicks))
				state = SIM_STATE_ENDED;
			updateForSimulationState(state);
		}
	}

	@Override
	public void handleInputError(Throwable t, Entity ent) {
		InputAgent.logMessage(sim, "Validation Error - %s: %s", ent.getName(), t.getMessage());
		GUIFrame.showErrorDialog("Input Error",
				"JaamSim has detected the following input error during validation:",
				String.format("%s: %-70s", ent.getName(), t.getMessage()),
				"The error must be corrected before the simulation can be started.");

		GUIFrame.updateForSimState(GUIFrame.SIM_STATE_CONFIGURED);
	}

	@Override
	public void handleError(Throwable t) {
		if (t instanceof OutOfMemoryError) {
			OutOfMemoryError e = (OutOfMemoryError)t;
			InputAgent.logMessage(sim, "Out of Memory use the -Xmx flag during execution for more memory");
			InputAgent.logMessage(sim, "Further debug information:");
			InputAgent.logMessage(sim, "%s", e.getMessage());
			InputAgent.logStackTrace(sim, t);
			GUIFrame.shutdown(1);
			return;
		}
		else {
			EventManager evt = EventManager.current();
			long currentTick = evt.getTicks();
			double curSec = evt.ticksToSeconds(currentTick);
			InputAgent.logMessage(sim, "EXCEPTION AT TIME: %f s", curSec);
			InputAgent.logMessage(sim, "%s", t.getMessage());
			if (t.getCause() != null) {
				InputAgent.logMessage(sim, "Call Stack of original exception:");
				InputAgent.logStackTrace(sim, t.getCause());
			}
			InputAgent.logMessage(sim, "Thrown exception call stack:");
			InputAgent.logStackTrace(sim, t);
		}

		String msg = t.getMessage();
		if (msg == null)
			msg = "null";
		String source = "";
		int pos = -1;
		if (t instanceof InputErrorException) {
			source = ((InputErrorException) t).source;
			pos = ((InputErrorException) t).position;
		}
		if (t instanceof ErrorException) {
			source = ((ErrorException) t).source;
			pos = ((ErrorException) t).position;
		}
		GUIFrame.showErrorDialog("Runtime Error",
				source,
				pos,
				"JaamSim has detected the following runtime error condition:",
				msg,
				"Programmers can find more information by opening the Log Viewer.\n"
						+ "The simulation run must be reset to zero simulation time before it "
						+ "can be restarted.");
	}

	void newModel() {
		sim.pause();

		// check for unsaved changes
		if (sim.isSessionEdited()) {
			boolean confirmed = GUIFrame.showSaveChangesDialog(GUIFrame.this);
			if (!confirmed) {
				return;
			}
		}

		clear();
		sim.setRecordEdits(true);
		InputAgent.loadDefault(sim);
		FrameBox.setSelectedEntity(sim.getSimulation(), false);
	}

	void load() {
		sim.pause();

		// check for unsaved changes
		if (sim.isSessionEdited()) {
			boolean confirmed = GUIFrame.showSaveChangesDialog(GUIFrame.this);
			if (!confirmed) {
				return;
			}
		}

		LogBox.logLine("Loading...");

		// Create a file chooser
		final JFileChooser chooser = new JFileChooser(getConfigFolder());

		// Set the file extension filters
		chooser.setAcceptAllFileFilterUsed(true);
		FileNameExtensionFilter cfgFilter =
				new FileNameExtensionFilter("JaamSim Configuration File (*.cfg)", "CFG");
		chooser.addChoosableFileFilter(cfgFilter);
		chooser.setFileFilter(cfgFilter);

		// Show the file chooser and wait for selection
		int returnVal = chooser.showOpenDialog(this);

		// Load the selected file
		if (returnVal == JFileChooser.APPROVE_OPTION) {
			clear();
			File chosenfile = chooser.getSelectedFile();
			new Thread(new Runnable() {
				@Override
				public void run() {
					sim.setRecordEdits(false);

					Throwable ret = GUIFrame.configure(chosenfile);
					if (ret != null)
						handleConfigError(ret, chosenfile);

					sim.setRecordEdits(true);
					FrameBox.setSelectedEntity(sim.getSimulation(), false);
				}
			}).start();

			setConfigFolder(chosenfile.getParent());
		}
	}

	static Throwable configure(File file) {
		GUIFrame.updateForSimState(GUIFrame.SIM_STATE_UNCONFIGURED);

		Throwable ret = null;
		try {
			sim.configure(file);
		}
		catch (Throwable t) {
			ret = t;
		}

		if (ret == null)
			LogBox.logLine("Configuration File Loaded");
		else
			LogBox.logLine("Configuration File Loaded - errors found");

		// show the present state in the user interface
		GUIFrame gui = GUIFrame.getInstance();
		if (gui != null) {
			gui.setProgress(0);
			gui.setTitle( sim.getSimulation().getModelName() + " - " + sim.getRunName() );
			gui.updateForSimulationState(GUIFrame.SIM_STATE_CONFIGURED);
			gui.enableSave(sim.isRecordEditsFound());
		}
		return ret;
	}

	static void handleConfigError(Throwable t, File file) {
		if (t instanceof InputErrorException) {
			InputAgent.logMessage(sim, "Input Error: %s", t.getMessage());
			GUIFrame.showErrorOptionDialog("Input Error",
					String.format("Input errors were detected while loading file: '%s'\n\n"
							+ "%s\n\n"
							+ "Open '%s' with Log Viewer?",
							file.getName(), t.getMessage(), sim.getRunName() + ".log"));
			return;
		}

		InputAgent.logMessage(sim, "Fatal Error while loading file '%s': %s\n", file.getName(), t.getMessage());
		GUIFrame.showErrorDialog("Fatal Error",
				String.format("A fatal error has occured while loading the file '%s':", file.getName()),
				t.getMessage(),
				"");
	}

	/**
	 * Saves the configuration file.
	 * @param file = file to be saved
	 */
	private void setSaveFile(File file) {
		try {
			sim.save(file);

			// Set the title bar to match the new run name
			this.setTitle( sim.getSimulation().getModelName() + " - " + sim.getRunName() );
		}
		catch (Exception e) {
			GUIFrame.showErrorDialog("File Error", e.getMessage());
		}
	}

	boolean save() {
		LogBox.logLine("Saving...");
		if( sim.getConfigFile() != null ) {
			setSaveFile(sim.getConfigFile());
			updateUI();
			return true;
		}

		boolean confirmed = saveAs();
		return confirmed;
	}

	boolean saveAs() {
		LogBox.logLine("Save As...");

		// Create a file chooser
		final JFileChooser chooser = new JFileChooser(getConfigFolder());

		// Set the file extension filters
		chooser.setAcceptAllFileFilterUsed(true);
		FileNameExtensionFilter cfgFilter =
				new FileNameExtensionFilter("JaamSim Configuration File (*.cfg)", "CFG");
		chooser.addChoosableFileFilter(cfgFilter);
		chooser.setFileFilter(cfgFilter);
		chooser.setSelectedFile(sim.getConfigFile());

		// Show the file chooser and wait for selection
		int returnVal = chooser.showSaveDialog(this);

		if (returnVal != JFileChooser.APPROVE_OPTION)
			return false;

		File file = chooser.getSelectedFile();

		// Add the file extension ".cfg" if needed
		String filePath = file.getPath();
		filePath = filePath.trim();
		if (file.getName().trim().indexOf('.') == -1) {
			filePath = filePath.concat(".cfg");
			file = new File(filePath);
		}

		// Confirm overwrite if file already exists
		if (file.exists()) {
			boolean confirmed = GUIFrame.showSaveAsDialog(file.getName());
			if (!confirmed) {
				return false;
			}
		}

		// Save the configuration file
		setSaveFile(file);

		setConfigFolder(file.getParent());
		updateUI();
		return true;
	}

	public void copyToClipboard(Entity ent) {
		if (ent == sim.getSimulation())
			return;
		Clipboard clpbrd = Toolkit.getDefaultToolkit().getSystemClipboard();
		clpbrd.setContents(new StringSelection(ent.getName()), null);
	}

	public Entity getEntityFromClipboard() {
		Clipboard clpbrd = Toolkit.getDefaultToolkit().getSystemClipboard();
		try {
			String name = (String)clpbrd.getData(DataFlavor.stringFlavor);
			return sim.getNamedEntity(name);
		}
		catch (Throwable err) {
			return null;
		}
	}

	public void pasteEntityFromClipboard() {
		Entity ent = getEntityFromClipboard();
		if (ent == null || ent == sim.getSimulation())
			return;

		// Identify the region for the new entity
		Region region = null;
		if (selectedEntity != null && selectedEntity instanceof DisplayEntity
				&& !(ent instanceof OverlayEntity)) {
			if (selectedEntity instanceof Region)
				region = (Region) selectedEntity;
			else
				region = ((DisplayEntity) selectedEntity).getCurrentRegion();
		}

		// Create the new entity
		String copyName = ent.getName();
		if (region != null && region.getParent() != sim.getSimulation())
			copyName = region.getParent().getName() + "." + ent.getLocalName();
		copyName = InputAgent.getUniqueName(sim, copyName, "_Copy");
		InputAgent.storeAndExecute(new DefineCommand(sim, ent.getClass(), copyName));

		// Copy the inputs
		Entity copiedEnt = sim.getNamedEntity(copyName);
		copiedEnt.copyInputs(ent);

		// Ensure that a random generator has a unique stream number
		if (copiedEnt instanceof RandomStreamUser) {
			RandomStreamUser rsu = (RandomStreamUser) copiedEnt;
			setUniqueRandomSeed(rsu);
		}

		// Set the region
		if (region != null)
			InputAgent.applyArgs(copiedEnt, "Region", region.getName());

		// Set the position
		if (ent instanceof DisplayEntity) {
			DisplayEntity dEnt = (DisplayEntity) copiedEnt;

			// If an entity is not selected, paste the new entity at the point of interest
			if (selectedEntity == null || !(selectedEntity instanceof DisplayEntity)
					|| selectedEntity instanceof Region) {
				if (RenderManager.isGood())
					RenderManager.inst().dragEntityToMousePosition(dEnt);
			}

			// If an entity is selected, paste the new entity next to the selected one
			else {
				int x = 0;
				int y = 0;
				if (selectedEntity instanceof OverlayEntity) {
					OverlayEntity olEnt = (OverlayEntity) selectedEntity;
					x = olEnt.getScreenPosition().get(0) + 10;
					y = olEnt.getScreenPosition().get(1) + 10;
				}
				DisplayEntity selectedDispEnt = (DisplayEntity) selectedEntity;
				Vec3d pos = selectedDispEnt.getGlobalPosition();
				pos.x += 0.5d * selectedDispEnt.getSize().x;
				pos.y -= 0.5d * selectedDispEnt.getSize().y;
				pos = dEnt.getLocalPosition(pos);
				if (sim.getSimulation().isSnapToGrid())
					pos = sim.getSimulation().getSnapGridPosition(pos);
				try {
					dEnt.dragged(x, y, pos);
				}
				catch (InputErrorException e) {}
			}

			// Add a label if required
			if (sim.getSimulation().isShowLabels() && EntityLabel.canLabel(dEnt))
				EntityLabel.showTemporaryLabel(dEnt, true);
		}

		// Copy the children
		copyChildren(ent, copiedEnt);

		// Select the new entity
		FrameBox.setSelectedEntity(copiedEnt, false);
	}

	public void copyChildren(Entity parent0, Entity parent1) {

		// Create the copied children
		for (Entity child : parent0.getChildren()) {
			if (child.isGenerated() || child instanceof EntityLabel)
				continue;

			// Construct the new child's name
			String localName = child.getLocalName();
			String name = parent1.getName() + "." + localName;

			// Create the new child
			InputAgent.storeAndExecute(new DefineCommand(sim, child.getClass(), name));

			// Add a label if necessary
			if (child instanceof DisplayEntity) {
				Entity copiedChild = parent1.getChild(localName);
				EntityLabel label = EntityLabel.getLabel((DisplayEntity) child);
				if (label != null) {
					EntityLabel newLabel = EntityLabel.createLabel((DisplayEntity) copiedChild);
					InputAgent.applyBoolean(newLabel, "Show", label.getShowInput());
					newLabel.setShow(label.getShow());
				}
			}
		}

		// Set the early and normal inputs for each child
		for (int seq = 0; seq < 2; seq++) {
			for (Entity child : parent0.getChildren()) {
				String localName = child.getLocalName();
				Entity copiedChild = parent1.getChild(localName);
				copiedChild.copyInputs(child, seq, false);
			}
		}

		// Ensure that any random stream inputs have a unique stream number
		for (Entity copiedChild : parent1.getChildren()) {
			if (!(copiedChild instanceof RandomStreamUser))
				continue;
			RandomStreamUser rsu = (RandomStreamUser) copiedChild;
			setUniqueRandomSeed(rsu);
		}

		// Copy each child's children
		for (Entity child : parent0.getChildren()) {
			String localName = child.getLocalName();
			Entity copiedChild = parent1.getChild(localName);
			copyChildren(child, copiedChild);
		}
	}

	public void setUniqueRandomSeed(RandomStreamUser rsu) {
		Simulation simulation = sim.getSimulation();
		int seed = rsu.getStreamNumber();
		if (seed >= 0 && simulation.getRandomStreamUsers(seed).size() <= 1)
			return;
		seed = simulation.getLargestStreamNumber() + 1;
		String key = rsu.getStreamNumberKeyword();
		InputAgent.applyIntegers((Entity) rsu, key, seed);
	}

	public void invokeNew() {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				newModel();
			}
		});
	}

	public void invokeOpen() {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				load();
			}
		});
	}

	public void invokeSave() {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				save();
			}
		});
	}

	public void invokeSaveAs() {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				saveAs();
			}
		});
	}

	public void invokeExit() {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				close();
			}
		});
	}

	public void invokeCopy(Entity ent) {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				copyToClipboard(ent);
			}
		});
	}

	public void invokePaste() {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				pasteEntityFromClipboard();
			}
		});
	}

	public void invokeFind() {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				FindBox.getInstance().showDialog();
			}
		});
	}

	public void invokeRunPause() {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				controlStartResume.doClick();
			}
		});
	}

	public void invokeSimSpeedUp() {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				if (!spinner.isEnabled())
					return;
				spinner.setValue(spinner.getNextValue());
			}
		});
	}

	public void invokeSimSpeedDown() {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				if (!spinner.isEnabled())
					return;
				spinner.setValue(spinner.getPreviousValue());
			}
		});
	}

	public static String getConfigFolder() {
		Preferences prefs = Preferences.userRoot().node(instance.getClass().getName());
		return prefs.get(LAST_USED_FOLDER, new File(".").getAbsolutePath());
	}

	public static void setConfigFolder(String path) {
		Preferences prefs = Preferences.userRoot().node(instance.getClass().getName());
		prefs.put(LAST_USED_FOLDER, path);
	}

	public static String getImageFolder() {
		Preferences prefs = Preferences.userRoot().node(instance.getClass().getName());
		return prefs.get(LAST_USED_IMAGE_FOLDER, getConfigFolder());
	}

	public static void setImageFolder(String path) {
		Preferences prefs = Preferences.userRoot().node(instance.getClass().getName());
		prefs.put(LAST_USED_IMAGE_FOLDER, path);
	}

	public static String get3DFolder() {
		Preferences prefs = Preferences.userRoot().node(instance.getClass().getName());
		return prefs.get(LAST_USED_3D_FOLDER, getConfigFolder());
	}

	public static void set3DFolder(String path) {
		Preferences prefs = Preferences.userRoot().node(instance.getClass().getName());
		prefs.put(LAST_USED_3D_FOLDER, path);
	}

	// ******************************************************************************************************
	// DIALOG BOXES
	// ******************************************************************************************************

	/**
	 * Shows the "Confirm Save As" dialog box
	 * @param fileName - name of the file to be saved
	 * @return true if the file is to be overwritten.
	 */
	public static boolean showSaveAsDialog(String fileName) {
		int userOption = JOptionPane.showConfirmDialog(null,
				String.format("The file '%s' already exists.\n" +
						"Do you want to replace it?", fileName),
				"Confirm Save As",
				JOptionPane.YES_NO_OPTION,
				JOptionPane.WARNING_MESSAGE);
		return (userOption == JOptionPane.YES_OPTION);
	}

	/**
	 * Shows the "Confirm Stop" dialog box.
	 * @return true if the run is to be stopped.
	 */
	public static boolean showConfirmStopDialog() {
		int userOption = JOptionPane.showConfirmDialog( null,
				"WARNING: Are you sure you want to reset the simulation time to 0?",
				"Confirm Reset",
				JOptionPane.YES_OPTION,
				JOptionPane.WARNING_MESSAGE );
		return (userOption == JOptionPane.YES_OPTION);
	}

	/**
	 * Shows the "Save Changes" dialog box
	 * @return true for any response other than Cancel or Close.
	 */
	public static boolean showSaveChangesDialog(GUIFrame gui) {

		String message;
		if (sim.getConfigFile() == null)
			message = "Do you want to save the changes you made?";
		else
			message = String.format("Do you want to save the changes you made to '%s'?", sim.getConfigFile().getName());

		Object[] options = {"Save", "Don't Save", "Cancel"};
		int userOption = JOptionPane.showOptionDialog( null,
				message,
				"Save Changes",
				JOptionPane.YES_NO_CANCEL_OPTION,
				JOptionPane.WARNING_MESSAGE,
				null,
				options,
				options[0]);

		if (userOption == JOptionPane.YES_OPTION) {
			boolean confirmed = gui.save();
			return confirmed;
		}
		else if (userOption == JOptionPane.NO_OPTION)
			return true;

		return false;
	}

	private static String getErrorMessage(String source, int position, String pre, String message, String post) {
		StringBuilder sb = new StringBuilder();
		sb.append("<html>");

		// Initial text prior to the message
		if (!pre.isEmpty()) {
			sb.append(html_replace(pre)).append("<br><br>");
		}

		// Error message
		sb.append(html_replace(message)).append("<br>");

		// Append the source expression and an 'arrow' pointing at the error
		if (source != null && !source.isEmpty()) {
			sb.append("<pre><font color=\"red\">");
			sb.append(html_replace(source)).append("<br>");
			for (int i = 0; i < position; ++i) {
				sb.append(" ");
			}
			sb.append("<b>^</b></font></pre>");
		}

		// Final text after the message
		if (!post.isEmpty()) {
			if (source == null || source.isEmpty()) {
				sb.append("<br>");
			}
			sb.append(html_replace(post));
		}

		sb.append("</html>");
		return sb.toString();
	}

	/**
	 * Displays the Error Message dialog box
	 * @param title - text for the dialog box name
	 * @param source - expression that cause the error (if applicable)
	 * @param position - location of the error in the expression (if applicable)
	 * @param pre - text to appear prior to the error message
	 * @param message - error message
	 * @param post - text to appear after the error message
	 */
	public static void showErrorDialog(String title, String source, int position, String pre, String message, String post) {
		if (sim == null || sim.isBatchRun())
			GUIFrame.shutdown(1);
		String msg = GUIFrame.getErrorMessage(source, position, pre, message, post);
		JOptionPane.showMessageDialog(null, msg, title, JOptionPane.ERROR_MESSAGE);
	}

	public static void showErrorDialog(String title, String pre, String message, String post) {
		GUIFrame.showErrorDialog(title, "", -1, pre, message, post);
	}

	public static void showErrorDialog(String title, String message) {
		GUIFrame.showErrorDialog(title, "", -1, "", message, "");
	}

	@Override
	public void invokeErrorDialogBox(String title, String msg) {
		GUIFrame.invokeErrorDialog(title, msg);
	}

	/**
	 * Shows the Error Message dialog box from a non-Swing thread
	 * @param title - text for the dialog box name
	 * @param msg - error message
	 */
	public static void invokeErrorDialog(String title, String msg) {
		SwingUtilities.invokeLater(new RunnableError(title, msg));
	}

	private static class RunnableError implements Runnable {
		private final String title;
		private final String message;

		public RunnableError(String t, String m) {
			title = t;
			message = m;
		}

		@Override
		public void run() {
			GUIFrame.showErrorDialog(title, message);
		}
	}

	/**
	 * Shows the Error Message dialog box with option to open the Log Viewer
	 * @param title - text for the dialog box name
	 * @param msg - error message
	 */
	public static void showErrorOptionDialog(String title, String msg) {
		if (sim == null || sim.isBatchRun())
			GUIFrame.shutdown(1);

		Object[] options = {"Yes", "No"};
		int userOption = JOptionPane.showOptionDialog(null,
				msg,
				title,
				JOptionPane.YES_NO_OPTION,
				JOptionPane.ERROR_MESSAGE,
				null,
				options,
				options[0]);

		if (userOption == JOptionPane.YES_OPTION) {
			KeywordIndex kw = InputAgent.formatBoolean("ShowLogViewer", true);
			InputAgent.storeAndExecute(new KeywordCommand(sim.getSimulation(), kw));
		}
	}

	/**
	 * Shows the Error Message dialog box for the Input Editor
	 * @param title - text for the dialog box name
	 * @param source - input text
	 * @param pos - index of the error in the input text
	 * @param pre - text to appear before the error message
	 * @param msg - error message
	 * @param post - text to appear after the error message
	 * @return true if the input is to be re-edited
	 */
	public static boolean showErrorEditDialog(String title, String source, int pos, String pre, String msg, String post) {
		String message = GUIFrame.getErrorMessage(source, pos, pre, msg, post);
		String[] options = { "Edit", "Reset" };
		int reply = JOptionPane.showOptionDialog(null,
				message,
				title,
				JOptionPane.OK_CANCEL_OPTION,
				JOptionPane.ERROR_MESSAGE,
				null,
				options,
				options[0]);
		return (reply == JOptionPane.OK_OPTION);
	}

	// ******************************************************************************************************
	// TOOL TIPS
	// ******************************************************************************************************
	private static final Pattern amp = Pattern.compile("&");
	private static final Pattern lt = Pattern.compile("<");
	private static final Pattern gt = Pattern.compile(">");
	private static final Pattern br = Pattern.compile("\n");

	public static final String html_replace(String str) {
		String desc = str;
		desc = amp.matcher(desc).replaceAll("&amp;");
		desc = lt.matcher(desc).replaceAll("&lt;");
		desc = gt.matcher(desc).replaceAll("&gt;");
		desc = br.matcher(desc).replaceAll("<BR>");
		return desc;
	}

	/**
	 * Returns the HTML code for pop-up tooltips in the Model Builder and Control Panel.
	 * @param name - name of the item whose tooltip is to be generated
	 * @param description - text describing the item's function
	 * @return HTML for the tooltip
	 */
	public static String formatToolTip(String name, String desc) {
		return String.format("<html><p width=\"200px\"><b>%s</b><br>%s</p></html>",
				name, desc);
	}

	/**
	 * Returns the HTML code for a keyword's pop-up tooltip in the Input Editor.
	 * @param className - object whose keyword tooltip is to be displayed
	 * @param keyword - name of the keyword
	 * @param description - description of the keyword
	 * @param exampleList - a list of examples that show how the keyword can be used
	 * @return HTML for the tooltip
	 */
	public static String formatKeywordToolTip(String className, String keyword,
			String description, String validInputs, String... exampleList) {

		StringBuilder sb = new StringBuilder("<html><p width=\"350px\">");

		// Keyword name
		String key = html_replace(keyword);
		sb.append("<b>").append(key).append("</b><br>");

		// Description
		String desc = html_replace(description);
		sb.append(desc).append("<br><br>");

		// Valid Inputs
		if (validInputs != null) {
			sb.append(validInputs).append("<br><br>");
		}

		// Examples
		if (exampleList.length > 0) {
			sb.append("<u>Examples:</u>");
		}
		for (int i=0; i<exampleList.length; i++) {
			String item = html_replace(exampleList[i]);
			sb.append("<br>").append(item);
		}

		sb.append("</p></html>");
		return sb.toString();
	}

	/**
	 * Returns the HTML code for an output's pop-up tooltip in the Output Viewer.
	 * @param name - name of the output
	 * @param description - description of the output
	 * @return HTML for the tooltip
	 */
	public static String formatOutputToolTip(String name, String description) {
		String desc = html_replace(description);
		return String.format("<html><p width=\"250px\"><b>%s</b><br>%s</p></html>",
				name, desc);
	}


	static ArrayList<Color4d> getFillColoursInUse(JaamSimModel simModel) {
		ArrayList<Color4d> ret = new ArrayList<>();
		for (DisplayEntity ent : simModel.getClonesOfIterator(DisplayEntity.class, FillEntity.class)) {
			FillEntity fillEnt = (FillEntity) ent;
			if (ret.contains(fillEnt.getFillColour()))
				continue;
			ret.add(fillEnt.getFillColour());
		}
		Collections.sort(ret, ColourInput.colourComparator);
		return ret;
	}

	static ArrayList<Color4d> getLineColoursInUse(JaamSimModel simModel) {
		ArrayList<Color4d> ret = new ArrayList<>();
		for (DisplayEntity ent : simModel.getClonesOfIterator(DisplayEntity.class, LineEntity.class)) {
			LineEntity lineEnt = (LineEntity) ent;
			if (ret.contains(lineEnt.getLineColour()))
				continue;
			ret.add(lineEnt.getLineColour());
		}
		Collections.sort(ret, ColourInput.colourComparator);
		return ret;
	}

	static ArrayList<String> getFontsInUse(JaamSimModel simModel) {
		ArrayList<String> ret = new ArrayList<>();
		for (DisplayEntity ent : simModel.getClonesOfIterator(DisplayEntity.class, TextEntity.class)) {
			TextEntity textEnt = (TextEntity) ent;
			if (ret.contains(textEnt.getFontName()))
				continue;
			ret.add(textEnt.getFontName());
		}
		Collections.sort(ret);
		return ret;
	}

	static ArrayList<Color4d> getFontColoursInUse(JaamSimModel simModel) {
		ArrayList<Color4d> ret = new ArrayList<>();
		for (DisplayEntity ent : simModel.getClonesOfIterator(DisplayEntity.class, TextEntity.class)) {
			TextEntity textEnt = (TextEntity) ent;
			if (ret.contains(textEnt.getFontColor()))
				continue;
			ret.add(textEnt.getFontColor());
		}
		Collections.sort(ret, ColourInput.colourComparator);
		return ret;
	}
}