/*
 * The tracker package defines a set of video/image analysis tools
 * built on the Open Source Physics framework by Wolfgang Christian.
 *
 * Copyright (c) 2019  Douglas Brown
 *
 * Tracker is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Tracker is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Tracker; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
 * or view the license online at <http://www.gnu.org/copyleft/gpl.html>
 *
 * For additional Tracker information and documentation, please see
 * <http://physlets.org/tracker/>.
 */
package org.opensourcephysics.cabrillo.tracker;

import java.beans.*;
import java.text.NumberFormat;
import java.util.*;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.event.*;

import javax.swing.*;

import org.opensourcephysics.cabrillo.tracker.PageTView.TabView;
import org.opensourcephysics.controls.OSPLog;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControl;
import org.opensourcephysics.desktop.OSPDesktop;
import org.opensourcephysics.display.ResizableIcon;
import org.opensourcephysics.media.core.ClipControl;
import org.opensourcephysics.media.core.ClipInspector;
import org.opensourcephysics.media.core.MediaRes;
import org.opensourcephysics.media.core.TPoint;
import org.opensourcephysics.media.core.VideoClip;
import org.opensourcephysics.tools.FontSizer;

/**
 * This is the main toolbar for Tracker.
 *
 * @author Douglas Brown
 */
public class TToolBar extends JToolBar implements PropertyChangeListener {

  // static fields
  protected static Map<TrackerPanel, TToolBar> toolbars = new HashMap<TrackerPanel, TToolBar>();

  protected static int[] trailLengths = {1,4,15,0};
  protected static String[] trailLengthNames = {"none","short","long","full"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
  protected static Icon newTrackIcon;
  protected static Icon trackControlIcon, trackControlOnIcon, trackControlDisabledIcon;
  protected static Icon zoomIcon;
  protected static Icon clipOffIcon, clipOnIcon;
  protected static Icon axesOffIcon, axesOnIcon;
  protected static Icon calibrationToolsOffIcon, calibrationToolsOnIcon;
  protected static Icon calibrationToolsOffRolloverIcon, calibrationToolsOnRolloverIcon;
  protected static Icon pointsOffIcon, pointsOnIcon;
  protected static Icon velocOffIcon, velocOnIcon;
  protected static Icon accelOffIcon, accelOnIcon;
  protected static Icon traceOffIcon, traceOnIcon;
  protected static Icon labelsOffIcon, labelsOnIcon;
  protected static Icon stretchOffIcon, stretchOnIcon;
  protected static Icon xmassOffIcon, xmassOnIcon;
  protected static Icon fontSmallerIcon, fontBiggerIcon, fontSmallerDisabledIcon, fontBiggerDisabledIcon;
  protected static Icon autotrackerOffIcon, autotrackerOnIcon, autotrackerDisabledIcon;
  protected static Icon infoIcon, refreshIcon, htmlIcon, htmlDisabledIcon;
  protected static Icon[] trailIcons = new Icon[4];
  protected static int[] stretchValues = new int[] {1,2,3,4,6,8,12,16,24,32};
  protected static Icon separatorIcon;
  protected static Icon checkboxOffIcon, checkboxOnIcon;
  protected static ResizableIcon checkboxOnDisabledIcon;
  protected static Icon pencilOffIcon, pencilOnIcon, pencilOffRolloverIcon, pencilOnRolloverIcon;
  protected static NumberFormat zoomFormat = NumberFormat.getNumberInstance();
	
	// instance fields
  protected TrackerPanel trackerPanel; // manages & displays track data
  protected boolean refreshing; // true when refreshing toolbar
  protected WindowListener infoListener;
  protected int vStretch = 1, aStretch = 1;
  protected JButton openButton, openBrowserButton, saveButton, saveZipButton;
  protected TButton newTrackButton;
  protected JButton trackControlButton, clipSettingsButton;
  protected CalibrationButton calibrationButton;
  protected DrawingButton drawingButton;
  protected JButton axesButton, zoomButton, autotrackerButton;
  protected JButton traceVisButton, pVisButton, vVisButton, aVisButton;
  protected JButton xMassButton, trailButton, labelsButton, stretchButton;
  protected JButton fontSmallerButton, fontBiggerButton;
  protected int trailLength = trailLengths[Tracker.trailLengthIndex];
  protected JPopupMenu newPopup = new JPopupMenu();
  protected JPopupMenu selectPopup = new JPopupMenu();
  protected JMenu vStretchMenu, aStretchMenu;
  protected ButtonGroup vGroup, aGroup;
  protected JMenuItem showTrackControlItem, selectNoneItem, stretchOffItem;
  protected JButton notesButton, refreshButton, desktopButton;
  protected Component toolbarFiller;
  protected int toolbarComponentHeight;
  protected JMenu cloneMenu;
  protected boolean notYetCalibrated = true;
  protected ComponentListener clipSettingsDialogListener;
  protected JPopupMenu zoomPopup = new JPopupMenu();
  protected ArrayList<PageTView.TabData> pageViewTabs = new ArrayList<PageTView.TabData>();
  
  static {
  	newTrackIcon =  new ResizableIcon(Tracker.class.getResource("resources/images/poof.gif")); //$NON-NLS-1$
  	trackControlIcon =  new ResizableIcon(Tracker.class.getResource("resources/images/track_control.gif")); //$NON-NLS-1$
  	trackControlOnIcon =  new ResizableIcon(Tracker.class.getResource("resources/images/track_control_on.gif")); //$NON-NLS-1$
  	trackControlDisabledIcon =  new ResizableIcon(Tracker.class.getResource("resources/images/track_control_disabled.gif")); //$NON-NLS-1$
    zoomIcon = new ResizableIcon(Tracker.class.getResource("resources/images/zoom.gif")); //$NON-NLS-1$
    clipOffIcon = new ResizableIcon(Tracker.class.getResource("resources/images/clip_off.gif")); //$NON-NLS-1$
    clipOnIcon = new ResizableIcon(Tracker.class.getResource("resources/images/clip_on.gif")); //$NON-NLS-1$
    axesOffIcon = new ResizableIcon(Tracker.class.getResource("resources/images/axes.gif")); //$NON-NLS-1$
    axesOnIcon = new ResizableIcon(Tracker.class.getResource("resources/images/axes_on.gif")); //$NON-NLS-1$
    calibrationToolsOffIcon = new ResizableIcon(Tracker.class.getResource("resources/images/calibration_tool.gif")); //$NON-NLS-1$
    calibrationToolsOnIcon = new ResizableIcon(Tracker.class.getResource("resources/images/calibration_tool_on.gif")); //$NON-NLS-1$
    calibrationToolsOffRolloverIcon = new ResizableIcon(Tracker.class.getResource("resources/images/calibration_tool_rollover.gif")); //$NON-NLS-1$
    calibrationToolsOnRolloverIcon = new ResizableIcon(Tracker.class.getResource("resources/images/calibration_tool_on_rollover.gif")); //$NON-NLS-1$
    pointsOffIcon = new ResizableIcon(Tracker.class.getResource("resources/images/positions.gif")); //$NON-NLS-1$
    pointsOnIcon = new ResizableIcon(Tracker.class.getResource("resources/images/positions_on.gif")); //$NON-NLS-1$
    velocOffIcon = new ResizableIcon(Tracker.class.getResource("resources/images/velocities.gif")); //$NON-NLS-1$
    velocOnIcon = new ResizableIcon(Tracker.class.getResource("resources/images/velocities_on.gif")); //$NON-NLS-1$
    accelOffIcon = new ResizableIcon(Tracker.class.getResource("resources/images/accel.gif")); //$NON-NLS-1$
    accelOnIcon = new ResizableIcon(Tracker.class.getResource("resources/images/accel_on.gif")); //$NON-NLS-1$
    traceOffIcon = new ResizableIcon(Tracker.class.getResource("resources/images/trace.gif")); //$NON-NLS-1$
    traceOnIcon = new ResizableIcon(Tracker.class.getResource("resources/images/trace_on.gif")); //$NON-NLS-1$
    labelsOffIcon = new ResizableIcon(Tracker.class.getResource("resources/images/labels.gif")); //$NON-NLS-1$
    labelsOnIcon = new ResizableIcon(Tracker.class.getResource("resources/images/labels_on.gif")); //$NON-NLS-1$
    stretchOffIcon = new ResizableIcon(Tracker.class.getResource("resources/images/stretch.gif")); //$NON-NLS-1$
    stretchOnIcon = new ResizableIcon(Tracker.class.getResource("resources/images/stretch_on.gif")); //$NON-NLS-1$
    xmassOffIcon = new ResizableIcon(Tracker.class.getResource("resources/images/x_mass.gif")); //$NON-NLS-1$
    xmassOnIcon = new ResizableIcon(Tracker.class.getResource("resources/images/x_mass_on.gif")); //$NON-NLS-1$
    fontSmallerIcon = new ResizableIcon(Tracker.class.getResource("resources/images/font_smaller.gif")); //$NON-NLS-1$
    fontBiggerIcon = new ResizableIcon(Tracker.class.getResource("resources/images/font_bigger.gif")); //$NON-NLS-1$
    fontSmallerDisabledIcon = new ResizableIcon(Tracker.class.getResource("resources/images/font_smaller_disabled.gif")); //$NON-NLS-1$
    fontBiggerDisabledIcon = new ResizableIcon(Tracker.class.getResource("resources/images/font_bigger_disabled.gif")); //$NON-NLS-1$
    autotrackerOffIcon = new ResizableIcon(Tracker.class.getResource("resources/images/autotrack_off.gif")); //$NON-NLS-1$
    autotrackerOnIcon = new ResizableIcon(Tracker.class.getResource("resources/images/autotrack_on.gif")); //$NON-NLS-1$
    autotrackerDisabledIcon = new ResizableIcon(Tracker.class.getResource("resources/images/autotrack_disabled.gif")); //$NON-NLS-1$
    infoIcon = new ResizableIcon(Tracker.class.getResource("resources/images/info.gif")); //$NON-NLS-1$
    refreshIcon = new ResizableIcon(Tracker.class.getResource("resources/images/refresh.gif")); //$NON-NLS-1$
    refreshIcon = new ResizableIcon(Tracker.class.getResource("resources/images/refresh.gif")); //$NON-NLS-1$
    htmlIcon = new ResizableIcon(Tracker.class.getResource("resources/images/html.gif")); //$NON-NLS-1$
    htmlDisabledIcon = new ResizableIcon(Tracker.class.getResource("resources/images/html_disabled.gif")); //$NON-NLS-1$
    trailIcons[0] = new ResizableIcon(Tracker.class.getResource("resources/images/trails_off.gif")); //$NON-NLS-1$
    trailIcons[1] = new ResizableIcon(Tracker.class.getResource("resources/images/trails_1.gif")); //$NON-NLS-1$
    trailIcons[2] = new ResizableIcon(Tracker.class.getResource("resources/images/trails_2.gif")); //$NON-NLS-1$
    trailIcons[3] = new ResizableIcon(Tracker.class.getResource("resources/images/trails_on.gif")); //$NON-NLS-1$
    separatorIcon = new ResizableIcon(Tracker.class.getResource("resources/images/separator.gif")); //$NON-NLS-1$
    checkboxOffIcon = new ResizableIcon(Tracker.class.getResource("resources/images/box_unchecked.gif")); //$NON-NLS-1$
    checkboxOnIcon = new ResizableIcon(Tracker.class.getResource("resources/images/box_checked.gif")); //$NON-NLS-1$
    checkboxOnDisabledIcon = new ResizableIcon(Tracker.class.getResource("resources/images/box_checked_disabled.gif")); //$NON-NLS-1$
    pencilOffIcon = new ResizableIcon(Tracker.class.getResource("resources/images/pencil_off.gif")); //$NON-NLS-1$
    pencilOnIcon = new ResizableIcon(Tracker.class.getResource("resources/images/pencil_on.gif")); //$NON-NLS-1$
    pencilOffRolloverIcon = new ResizableIcon(Tracker.class.getResource("resources/images/pencil_off_rollover.gif")); //$NON-NLS-1$
    pencilOnRolloverIcon = new ResizableIcon(Tracker.class.getResource("resources/images/pencil_on_rollover.gif")); //$NON-NLS-1$
    zoomFormat.setMaximumFractionDigits(0);
  }

  /**
   * TToolBar constructor.
   *
   * @param  panel the tracker panel
   */
  private TToolBar(TrackerPanel panel) {
    trackerPanel = panel;
    trackerPanel.addPropertyChangeListener("track", this); //$NON-NLS-1$
    trackerPanel.addPropertyChangeListener("clear", this); //$NON-NLS-1$
    trackerPanel.addPropertyChangeListener("video", this); //$NON-NLS-1$
    trackerPanel.addPropertyChangeListener("magnification", this); //$NON-NLS-1$
    trackerPanel.addPropertyChangeListener("selectedtrack", this); //$NON-NLS-1$
    trackerPanel.addPropertyChangeListener("selectedpoint", this); //$NON-NLS-1$
    createGUI();
    refresh(true);
    validate();
  }

  /**
   *  Creates the GUI.
   */
  protected void createGUI() {
    setFloatable(false);
    // create buttons
    Map<String, AbstractAction> actions = TActions.getActions(trackerPanel);
    // open and save buttons
  	openButton = new TButton(actions.get("open")); //$NON-NLS-1$
  	openBrowserButton = new TButton(actions.get("openBrowser")); //$NON-NLS-1$
    saveButton = new TButton(actions.get("save")); //$NON-NLS-1$
    saveButton.addMouseListener(new MouseAdapter() {
    	public void mouseEntered(MouseEvent e) {
        String fileName = trackerPanel.getTitle();
        String extension = XML.getExtension(fileName);
        if (extension==null || !extension.equals("trk")) //$NON-NLS-1$
        	fileName = XML.stripExtension(fileName)+".trk"; //$NON-NLS-1$
        saveButton.setToolTipText(TrackerRes.getString("TToolBar.Button.Save.Tooltip")); //$NON-NLS-1$
    	}
    });
    saveZipButton = new TButton(actions.get("saveZip")); //$NON-NLS-1$
    saveZipButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
      	final ExportZipDialog zipDialog = ExportZipDialog.getDialog(trackerPanel);
      	final boolean isVis = zipDialog.isVisible();
				Runnable runner = new Runnable() {
					public void run() {
						zipDialog.setVisible(!isVis);
					}
				};
				SwingUtilities.invokeLater(runner);
			} 		
  	});
    // clip settings button
    clipSettingsDialogListener = new ComponentAdapter() {
    	public void componentHidden(ComponentEvent e) {      	
    		refresh(false);
    	}    	 
    };
    clipSettingsButton = new TButton(clipOffIcon,clipOnIcon);
    clipSettingsButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
      	VideoClip clip = trackerPanel.getPlayer().getVideoClip();
      	ClipControl clipControl = trackerPanel.getPlayer().getClipControl();
        TFrame frame = trackerPanel.getTFrame();
        ClipInspector inspector = clip.getClipInspector(clipControl, frame);
        if(inspector.isVisible()) {
          inspector.setVisible(false);
          return;
        }
        FontSizer.setFonts(inspector, FontSizer.getLevel());
        inspector.pack();
        Point p0 = new Frame().getLocation();
        Point loc = inspector.getLocation();
        if((loc.x==p0.x)&&(loc.y==p0.y)) {
          // center inspector on the main view
        	Rectangle rect = trackerPanel.getVisibleRect();
          Point p = frame.getMainView(trackerPanel).scrollPane.getLocationOnScreen();
          int x = p.x+(rect.width-inspector.getBounds().width)/2;
          int y = p.y+(rect.height-inspector.getBounds().height)/2;
          inspector.setLocation(x, y);
        }
        inspector.initialize();
        inspector.removeComponentListener(clipSettingsDialogListener);
        inspector.addComponentListener(clipSettingsDialogListener);
        inspector.setVisible(true);
        refresh(false);
      }

    });
    // axes button
    axesButton = new TButton(axesOffIcon,axesOnIcon);
    axesButton.addActionListener(actions.get("axesVisible")); //$NON-NLS-1$
    
    // calibration button
    calibrationButton = new CalibrationButton();

    // zoom button
    Action zoomAction = new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
      	// set zoom center to center of current viewport
      	Rectangle rect = trackerPanel.scrollPane.getViewport().getViewRect();
      	MainTView mainView = trackerPanel.getTFrame().getMainView(trackerPanel);
      	mainView.setZoomCenter(rect.x+rect.width/2, rect.y+rect.height/2);
        String name = e.getActionCommand();
        if (name.equals("auto")) { //$NON-NLS-1$
          trackerPanel.setMagnification(-1);
        }
        else {
          double mag = Double.parseDouble(name);
          trackerPanel.setMagnification(mag/100);
        }
        refreshZoomButton();
      }
    };         
    JMenuItem item = new JMenuItem(TrackerRes.getString("MainTView.Popup.MenuItem.ToFit")); //$NON-NLS-1$
    item.setActionCommand("auto"); //$NON-NLS-1$
    item.addActionListener(zoomAction);
    zoomPopup.add(item);
    zoomPopup.addSeparator();
    for (int i = 0; i < TrackerPanel.ZOOM_LEVELS.length; i++) {
    	int n = (int)(100*TrackerPanel.ZOOM_LEVELS[i]);
      String m = String.valueOf(n);
      item = new JMenuItem(m+"%"); //$NON-NLS-1$
      item.setActionCommand(m);
      item.addActionListener(zoomAction);
      zoomPopup.add(item);
    }
    zoomButton = new TButton(zoomIcon) {
    	protected JPopupMenu getPopup() {
      	FontSizer.setFonts(zoomPopup, FontSizer.getLevel());
        return zoomPopup;       		
    	}
    };
    zoomButton.addMouseListener(new MouseAdapter() {
    	public void mouseClicked(MouseEvent e) {
    		if (e.getClickCount()==2) {
    			trackerPanel.setMagnification(-1);
    			zoomPopup.setVisible(false);
    			refreshZoomButton();
    		}
    	}
    });

    // new track button
    newTrackButton = new TButton(newTrackIcon) {
      protected JPopupMenu getPopup() {
      	return getNewTracksPopup();
      }   	
    };
    // track control button
    trackControlButton = new TButton(trackControlIcon, trackControlOnIcon);
    trackControlButton.setDisabledIcon(trackControlDisabledIcon);
    trackControlButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        TrackControl tc = TrackControl.getControl(trackerPanel);
      	tc.setVisible(!tc.isVisible());
      }
    });    
    // autotracker button
    autotrackerButton = new TButton(autotrackerOffIcon, autotrackerOnIcon);
    autotrackerButton.setDisabledIcon(autotrackerDisabledIcon);
    autotrackerButton.addMouseListener(new MouseAdapter() {
    	public void mouseEntered(MouseEvent e) {    		
    		requestFocus(); // workaround--shouldn't need this...
    	}
    });
    autotrackerButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
      	autotrackerButton.setSelected(!autotrackerButton.isSelected());
        AutoTracker autoTracker = trackerPanel.getAutoTracker();
        if (autoTracker.getTrack()==null) {
	        TTrack track = trackerPanel.getSelectedTrack();
	        if (track==null) {
	          for (TTrack next: trackerPanel.getTracks()) {
	          	if (!next.isAutoTrackable()) continue;
	          	track = next;
	          	break;
	          }
	        }
        	autoTracker.setTrack(track);
        }
        autoTracker.getWizard().setVisible(autotrackerButton.isSelected());
        trackerPanel.repaint();
      }
    });    
    final Action refreshAction = new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        JButton button = (JButton)e.getSource();
        button.setSelected(!button.isSelected());
        refresh(true);
      }
    };
    // p visible button
    pVisButton = new TButton(pointsOffIcon, pointsOnIcon);
    pVisButton.setSelected(true);
    pVisButton.addActionListener(refreshAction);
    // v visible button
    vVisButton = new TButton(velocOffIcon, velocOnIcon);
    vVisButton.addActionListener(refreshAction);
    // a visible button
    aVisButton = new TButton(accelOffIcon, accelOnIcon);
    aVisButton.addActionListener(refreshAction);
    // trace visible button
    traceVisButton = new TButton(traceOffIcon, traceOnIcon);
    traceVisButton.addActionListener(refreshAction);
    // trail button
    trailButton = new TButton() {
      protected JPopupMenu getPopup() {
      	JPopupMenu popup = new JPopupMenu();
      	ActionListener listener = new ActionListener() {
      		public void actionPerformed(ActionEvent e) {
      			int n = Integer.parseInt(e.getActionCommand());
      			trailLength = trailLengths[n];
          	trailButton.setSelected(trailLength!=1);
            refresh(true);
          	trackerPanel.repaint();
      		}
      	};
      	ButtonGroup group = new ButtonGroup();
      	JMenuItem item = new JRadioButtonMenuItem(TrackerRes.getString("TrackControl.TrailMenu.NoTrail")); //$NON-NLS-1$
      	item.setSelected(trailLength==trailLengths[0]);
      	item.setActionCommand(String.valueOf(0));
      	item.addActionListener(listener);
      	popup.add(item);
      	group.add(item);
      	item = new JRadioButtonMenuItem(TrackerRes.getString("TrackControl.TrailMenu.ShortTrail")); //$NON-NLS-1$
      	item.setSelected(trailLength==trailLengths[1]);
      	item.setActionCommand(String.valueOf(1));
      	item.addActionListener(listener);
      	popup.add(item);
      	group.add(item);
      	item = new JRadioButtonMenuItem(TrackerRes.getString("TrackControl.TrailMenu.LongTrail")); //$NON-NLS-1$
      	item.setSelected(trailLength==trailLengths[2]);
      	item.setActionCommand(String.valueOf(2));
      	item.addActionListener(listener);
      	popup.add(item);
      	group.add(item);
      	item = new JRadioButtonMenuItem(TrackerRes.getString("TrackControl.TrailMenu.FullTrail")); //$NON-NLS-1$
      	item.setSelected(trailLength==trailLengths[3]);
      	item.setActionCommand(String.valueOf(3));
      	item.addActionListener(listener);
      	popup.add(item);
      	group.add(item);
      	FontSizer.setFonts(popup, FontSizer.getLevel());
      	return popup;
      }
    };
    trailButton.setSelected(true);

    // labels visible button
    labelsButton = new TButton(labelsOffIcon, labelsOnIcon);
    labelsButton.setSelected(!Tracker.hideLabels);
    labelsButton.addActionListener(refreshAction);
    // x mass button
    xMassButton = new TButton(xmassOffIcon, xmassOnIcon);
    xMassButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        refreshAction.actionPerformed(e);
      	TTrack track = trackerPanel.getSelectedTrack();
      	if (track instanceof PointMass) {
      		trackerPanel.getTFrame().getTrackBar(trackerPanel).refresh();
      	}
      }
    });
    // stretch button
  	vStretchMenu = new JMenu();
  	aStretchMenu = new JMenu();
  	// velocity stretch menu
  	ActionListener vListener = new ActionListener() {
  		public void actionPerformed(ActionEvent e) {
  			int n = Integer.parseInt(e.getActionCommand());
      	trackerPanel.setSelectedPoint(null);
        trackerPanel.selectedSteps.clear();
        vStretch = n;
        refresh(true);
  		}
  	};
  	vGroup = new ButtonGroup();
  	for (int i=0; i<stretchValues.length; i++) {
  		String s = String.valueOf(stretchValues[i]);
    	item = new JRadioButtonMenuItem("x"+s); //$NON-NLS-1$
    	if (i==0)
    		item.setText(TrackerRes.getString("TrackControl.StretchVectors.None")); //$NON-NLS-1$
    	item.setActionCommand(s);
    	item.setSelected(vStretch==stretchValues[i]);
    	item.addActionListener(vListener);
    	vStretchMenu.add(item);
    	vGroup.add(item);
  	}
  	// acceleration stretch menu
  	ActionListener aListener = new ActionListener() {
  		public void actionPerformed(ActionEvent e) {
  			int n = Integer.parseInt(e.getActionCommand());
      	trackerPanel.setSelectedPoint(null);
        trackerPanel.selectedSteps.clear();
        aStretch = n;
        refresh(true);
  		}
  	};
  	aGroup = new ButtonGroup();
  	for (int i=0; i<stretchValues.length; i++) {
  		String s = String.valueOf(stretchValues[i]);
    	item = new JRadioButtonMenuItem("x"+s); //$NON-NLS-1$
    	if (i==0)
    		item.setText(TrackerRes.getString("TrackControl.StretchVectors.None")); //$NON-NLS-1$
    	item.setActionCommand(s);
    	item.setSelected(aStretch==stretchValues[i]);
    	item.addActionListener(aListener);
    	aStretchMenu.add(item);
    	aGroup.add(item);
  	}
  	stretchOffItem = new JMenuItem();
  	stretchOffItem.addActionListener(new ActionListener() {
  		public void actionPerformed(ActionEvent e) {
        vStretch = 1;
        aStretch = 1;
        refresh(true);
  		}
  	});


    stretchButton = new TButton(stretchOffIcon, stretchOnIcon) {
      protected JPopupMenu getPopup() {
      	JPopupMenu popup = new JPopupMenu();
      	popup.add(vStretchMenu);
      	popup.add(aStretchMenu);
      	popup.addSeparator();
      	popup.add(stretchOffItem);
      	FontSizer.setFonts(popup, FontSizer.getLevel());
      	return popup;
      }
    };
    
    // font buttons
    fontSmallerButton = new TButton(fontSmallerIcon);
    fontSmallerButton.setDisabledIcon(fontSmallerDisabledIcon);
    fontSmallerButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
      	int i = FontSizer.getLevel();
      	FontSizer.setLevel(i-1);
      	fontSmallerButton.setEnabled(FontSizer.getLevel()>0);
      	fontBiggerButton.setEnabled(FontSizer.getLevel()<FontSizer.MAX_LEVEL);
      }
    });
    fontBiggerButton = new TButton(fontBiggerIcon);
    fontBiggerButton.setDisabledIcon(fontBiggerDisabledIcon);
    fontBiggerButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
      	int i = FontSizer.getLevel();
      	FontSizer.setLevel(i+1);
      	fontSmallerButton.setEnabled(FontSizer.getLevel()>0);
      	fontBiggerButton.setEnabled(FontSizer.getLevel()<FontSizer.MAX_LEVEL);
      }
    });

    // horizontal glue for right end of toolbar
    toolbarFiller = Box.createHorizontalGlue();
    // info button
    infoListener = new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        notesButton.setSelected(false);
      }
    };
    drawingButton = new DrawingButton();
    notesButton = new TButton(infoIcon);
    notesButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        TFrame frame = trackerPanel.getTFrame();
        if (frame != null && frame.getTrackerPanel(frame.getSelectedTab()) == trackerPanel) {
          frame.notesDialog.removeWindowListener(infoListener);
          frame.notesDialog.addWindowListener(infoListener);
          // position info dialog if first time shown
        	// or if trackerPanel specifies location
          Point p0 = new Frame().getLocation();
          if (trackerPanel.infoX != Integer.MIN_VALUE ||
          				frame.notesDialog.getLocation().x==p0.x) {
          	int x, y;
            Point p = frame.getLocationOnScreen();
          	if (trackerPanel.infoX != Integer.MIN_VALUE) {
              Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
        			x = Math.max(p.x + trackerPanel.infoX, 0);
        			x = Math.min(x, dim.width-frame.notesDialog.getWidth());
        			y = Math.max(p.y + trackerPanel.infoY, 0);
        			y = Math.min(y, dim.height-frame.notesDialog.getHeight());
          		trackerPanel.infoX = Integer.MIN_VALUE;
          	}
          	else {
              Point pleft = TToolBar.this.getLocationOnScreen();
              Dimension dim = frame.notesDialog.getSize();
              Dimension wdim = TToolBar.this.getSize();
              x = pleft.x + (int)(0.5 * (wdim.width - dim.width));
              y = p.y + 16;
          	}
            frame.notesDialog.setLocation(x, y);
          }
          notesButton.setSelected(!frame.notesDialog.isVisible());
          frame.notesDialog.setVisible(notesButton.isSelected());
          trackerPanel.refreshNotesDialog();
        }
      }
    });
    refreshButton = new TButton(refreshIcon) {
      protected JPopupMenu getPopup() {
      	JPopupMenu popup = new JPopupMenu();
      	JMenuItem item = new JMenuItem(TrackerRes.getString("TToolbar.Button.Refresh.Popup.RefreshNow")); //$NON-NLS-1$
      	item.addActionListener(new ActionListener() {
      		public void actionPerformed(ActionEvent e) {
      	  	// offer to clear RGBRegion data that are valid and visible in a view
       			ArrayList<RGBRegion> regions = trackerPanel.getDrawables(RGBRegion.class);
    				ArrayList<RGBRegion> regionsToClear = new ArrayList<RGBRegion>();
      			if (!regions.isEmpty()) {
      				for (RGBRegion next: regions) {
      					if (trackerPanel.isTrackViewDisplayed(next) && next.dataValid) {
      						regionsToClear.add(next);
      					}
      				}
      			}
      			if (!regionsToClear.isEmpty()) {
      				// get user confirmation
      				String list = " "; //$NON-NLS-1$
      				for (RGBRegion next: regionsToClear) {
      					list += next.getName()+", "; //$NON-NLS-1$
      				}
      				list = list.substring(0, list.length()-2);
      		    int i = JOptionPane.showConfirmDialog(trackerPanel.getTopLevelAncestor(),
                  TrackerRes.getString("TToolBar.Dialog.ClearRGB.Message1") + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
                  TrackerRes.getString("TToolBar.Dialog.ClearRGB.Message2"), //$NON-NLS-1$
                  TrackerRes.getString("TToolBar.Dialog.ClearRGB.Title")+list, //$NON-NLS-1$
                  JOptionPane.YES_NO_OPTION,
                  JOptionPane.QUESTION_MESSAGE);
      		    if (i == JOptionPane.YES_OPTION) {
        				for (RGBRegion next: regionsToClear) {
        					next.clearData();
        				}
	      			}
      		  }
	          trackerPanel.refreshTrackData();
	          trackerPanel.eraseAll();
	          trackerPanel.repaintDirtyRegion();
      		}
      	});
      	popup.add(item);
      	popup.addSeparator();
      	item = new JCheckBoxMenuItem(TrackerRes.getString("TToolbar.Button.Refresh.Popup.AutoRefresh")); //$NON-NLS-1$
      	item.setSelected(trackerPanel.isAutoRefresh);
      	item.addActionListener(new ActionListener() {
      		public void actionPerformed(ActionEvent e) {
      			JMenuItem item = (JMenuItem)e.getSource();
	          trackerPanel.isAutoRefresh = item.isSelected();
	          if (trackerPanel.isAutoRefresh) {
		          trackerPanel.refreshTrackData();
		          trackerPanel.eraseAll();
		          trackerPanel.repaintDirtyRegion();
	          }
      		}
      	});
      	popup.add(item);
      	FontSizer.setFonts(popup, FontSizer.getLevel());
      	return popup;
      }

    };
    desktopButton = new TButton(htmlIcon) {
      protected JPopupMenu getPopup() {
      	JPopupMenu popup = new JPopupMenu();
      	if (!trackerPanel.supplementalFilePaths.isEmpty()) {
	      	JMenu fileMenu = new JMenu(TrackerRes.getString("TToolbar.Button.Desktop.Menu.OpenFile")); //$NON-NLS-1$
	      	popup.add(fileMenu);
	        for (String next: trackerPanel.supplementalFilePaths) {
	        	String title = XML.getName(next);
	        	String path = next;
	        	JMenuItem item = new JMenuItem(title);
	        	item.setActionCommand(path);
	        	item.setToolTipText(path);
	        	item.addActionListener(new ActionListener() {
	        		public void actionPerformed(ActionEvent e) {
	        			String path = e.getActionCommand();
	            	OSPDesktop.displayURL(path);
	        		}
	        	});
	        	fileMenu.add(item);
	        }
      	}
      	if (!pageViewTabs.isEmpty()) {
	      	JMenu pageMenu = new JMenu(TrackerRes.getString("TToolbar.Button.Desktop.Menu.OpenPage")); //$NON-NLS-1$
	      	popup.add(pageMenu);
	        for (PageTView.TabData next: pageViewTabs) {
	        	if (next.url==null) continue;
	        	String title = next.title;
	        	String path = trackerPanel.pageViewFilePaths.get(next.text);
	        	if (path==null) {
	        		path = next.url.toExternalForm();
	        	}
	        	JMenuItem item = new JMenuItem(title);
	        	item.setActionCommand(path);
	        	item.setToolTipText(path);
	        	item.addActionListener(new ActionListener() {
	        		public void actionPerformed(ActionEvent e) {
	        			String path = e.getActionCommand();
	            	OSPDesktop.displayURL(path);
	        		}
	        	});
	        	pageMenu.add(item);
	        }
      	}
      	FontSizer.setFonts(popup, FontSizer.getLevel());
      	return popup;
      }

    };
    desktopButton.setDisabledIcon(htmlDisabledIcon);

    // create menu items
    cloneMenu = new JMenu();
    showTrackControlItem = new JCheckBoxMenuItem();
    showTrackControlItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        TrackControl tc = TrackControl.getControl(trackerPanel);
      	tc.setVisible(showTrackControlItem.isSelected());
      }
    });    
    selectNoneItem = new JMenuItem();
    selectNoneItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
      	trackerPanel.setSelectedTrack(null);
      }
    });
  }
  
  protected void refreshZoomButton() {
  	double zoom = trackerPanel.getMagnification()*100;
    zoomButton.setText(zoomFormat.format(zoom)+"%"); 	 //$NON-NLS-1$
  }

  /**
   * Refreshes the GUI.
   * @param refreshTrackProperties true to refresh the track display properties
   */
  protected void refresh(final boolean refreshTrackProperties) {
    Tracker.logTime(getClass().getSimpleName()+hashCode()+" refresh"); //$NON-NLS-1$
    Runnable runner = new Runnable() {
    	public void run() {
        refreshing = true; // signals listeners that items are being refreshed
        refreshZoomButton();
        calibrationButton.refresh();
        drawingButton.refresh();
        stretchButton.setSelected(vStretch>1 || aStretch>1);
        stretchOffItem.setText(TrackerRes.getString("TToolBar.MenuItem.StretchOff")); //$NON-NLS-1$
        stretchOffItem.setEnabled(vStretch>1 || aStretch>1);
        // refresh stretch items
        Enumeration<AbstractButton> en = vGroup.getElements();
        for (;en.hasMoreElements();) {
        	AbstractButton next = en.nextElement();
        	if (next.getActionCommand().equals(String.valueOf(vStretch))) {
        		next.setSelected(true);
        	}
        }
        en = aGroup.getElements();
        for (;en.hasMoreElements();) {
        	AbstractButton next = en.nextElement();
        	if (next.getActionCommand().equals(String.valueOf(aStretch))) {
        		next.setSelected(true);
        	}
        }
        
        vStretchMenu.setText(TrackerRes.getString("PointMass.MenuItem.Velocity")); //$NON-NLS-1$
        aStretchMenu.setText(TrackerRes.getString("PointMass.MenuItem.Acceleration")); //$NON-NLS-1$
        openButton.setToolTipText(TrackerRes.getString("TToolBar.Button.Open.Tooltip")); //$NON-NLS-1$
        openBrowserButton.setToolTipText(TrackerRes.getString("TToolBar.Button.OpenBrowser.Tooltip")); //$NON-NLS-1$
        saveZipButton.setToolTipText(TrackerRes.getString("TToolBar.Button.SaveZip.Tooltip")); //$NON-NLS-1$
        clipSettingsButton.setToolTipText(MediaRes.getString("VideoPlayer.Button.ClipSettings.ToolTip")); //$NON-NLS-1$
        axesButton.setToolTipText(TrackerRes.getString("TToolbar.Button.AxesVisible.Tooltip")); //$NON-NLS-1$
        zoomButton.setToolTipText(TrackerRes.getString("TToolBar.Button.Zoom.Tooltip")); //$NON-NLS-1$
        notesButton.setToolTipText(TrackerRes.getString("TActions.Action.Description")); //$NON-NLS-1$
        refreshButton.setToolTipText(TrackerRes.getString("TToolbar.Button.Refresh.Tooltip")); //$NON-NLS-1$
        desktopButton.setToolTipText(TrackerRes.getString("TToolbar.Button.Desktop.Tooltip")); //$NON-NLS-1$
        pVisButton.setToolTipText(TrackerRes.getString("TrackControl.Button.Positions.ToolTip")); //$NON-NLS-1$
        vVisButton.setToolTipText(TrackerRes.getString("TrackControl.Button.Velocities.ToolTip")); //$NON-NLS-1$
        aVisButton.setToolTipText(TrackerRes.getString("TrackControl.Button.Accelerations.ToolTip")); //$NON-NLS-1$
        xMassButton.setToolTipText(TrackerRes.getString("TrackControl.Button.Xmass.ToolTip")); //$NON-NLS-1$
        trailButton.setToolTipText(TrackerRes.getString("TrackControl.Button.Trails.ToolTip")); //$NON-NLS-1$
        labelsButton.setToolTipText(TrackerRes.getString("TrackControl.Button.Labels.ToolTip")); //$NON-NLS-1$
        stretchButton.setToolTipText(TrackerRes.getString("TrackControl.Button.StretchVectors.ToolTip")); //$NON-NLS-1$
        traceVisButton.setToolTipText(TrackerRes.getString("TrackControl.Button.Trace.ToolTip")); //$NON-NLS-1$
        newTrackButton.setText(TrackerRes.getString("TrackControl.Button.NewTrack")); //$NON-NLS-1$
        newTrackButton.setToolTipText(TrackerRes.getString("TrackControl.Button.NewTrack.ToolTip")); //$NON-NLS-1$
        trackControlButton.setToolTipText(TrackerRes.getString("TToolBar.Button.TrackControl.Tooltip")); //$NON-NLS-1$
        autotrackerButton.setToolTipText(TrackerRes.getString("TToolBar.Button.AutoTracker.Tooltip")); //$NON-NLS-1$
        fontSmallerButton.setToolTipText(TrackerRes.getString("TrackControl.Button.FontSmaller.ToolTip")); //$NON-NLS-1$
        fontBiggerButton.setToolTipText(TrackerRes.getString("TrackControl.Button.FontBigger.ToolTip")); //$NON-NLS-1$
      	fontSmallerButton.setEnabled(FontSizer.getLevel()>0);
      	fontBiggerButton.setEnabled(FontSizer.getLevel()<FontSizer.MAX_LEVEL);
        if (trackerPanel.getPlayer()!=null) {
	        VideoClip clip = trackerPanel.getPlayer().getVideoClip();
	        ClipInspector inspector = clip.getClipInspector();
	        clipSettingsButton.setSelected(inspector!=null && inspector.isVisible());
      	}
        CoordAxes axes = trackerPanel.getAxes();
        if (axes != null) {
        	axesButton.setSelected(axes.isVisible());
        	axes.removePropertyChangeListener("visible", TToolBar.this); //$NON-NLS-1$
          axes.addPropertyChangeListener("visible", TToolBar.this); //$NON-NLS-1$
        }
        ArrayList<TTrack> tracks = trackerPanel.getUserTracks();
        trackControlButton.setEnabled(!tracks.isEmpty());
        autotrackerButton.setEnabled(trackerPanel.getVideo()!=null);
        // count independent masses
        double totalMass = 0;
        int massCount = 0;
        for (TTrack track: tracks) {
	        if (track instanceof PointMass 
	        		&& !(track instanceof CenterOfMass)
	        		&& !(track instanceof DynamicSystem)) {
	          PointMass p = (PointMass)track;
	          totalMass += p.getMass();
	          massCount++;
	        }
        }
        // refresh all tracks
        if (refreshTrackProperties) {
        	for (TTrack track: trackerPanel.getTracks()) {
	          track.removePropertyChangeListener("locked", TToolBar.this); //$NON-NLS-1$
	          track.addPropertyChangeListener("locked", TToolBar.this); //$NON-NLS-1$
	          // refresh track display properties from current button states
	          track.setTrailLength(trailLength);
	          track.setTrailVisible(trailButton.isSelected());
	          if (track instanceof PointMass) {
	            PointMass p = (PointMass)track;
	            p.setTraceVisible(traceVisButton.isSelected());
	            p.setPositionVisible(trackerPanel, pVisButton.isSelected());
	            p.setVVisible(trackerPanel, vVisButton.isSelected());
	            p.setAVisible(trackerPanel, aVisButton.isSelected());
	            p.setLabelsVisible(trackerPanel, labelsButton.isSelected());
	            Footprint[] footprints = p.getVelocityFootprints();
	            for (int i = 0; i < footprints.length; i++) {
	              if (footprints[i] instanceof ArrowFootprint) {
	                ArrowFootprint arrow = (ArrowFootprint) footprints[i];
	                if (xMassButton.isSelected()) {                	
	                  arrow.setStretch(vStretch * massCount * p.getMass() / totalMass);
	                  arrow.setSolidHead(false);
	                }
	                else {
	                  arrow.setStretch(vStretch);
	                  arrow.setSolidHead(false);
	                }
	              }
	            }
	            footprints = p.getAccelerationFootprints();
	            for (int i = 0; i < footprints.length; i++) {
	              if (footprints[i] instanceof ArrowFootprint) {
	                ArrowFootprint arrow = (ArrowFootprint) footprints[i];
	                if (xMassButton.isSelected()) {
	                  arrow.setStretch(aStretch * massCount * p.getMass() / totalMass);
	                  arrow.setSolidHead(true);
	                }
	                else {
	                  arrow.setStretch(aStretch);
	                  arrow.setSolidHead(true);
	                }
	              }
	            }
	            p.repaint();
	          }
	          else if (track instanceof Vector) {
	          	Vector v = (Vector)track;
	            v.setLabelsVisible(labelsButton.isSelected());
	            Footprint[] footprints = v.getFootprints();
	            for (int i = 0; i < footprints.length; i++) {
	              if (footprints[i] instanceof ArrowFootprint) {
	                ArrowFootprint arrow = (ArrowFootprint) footprints[i];
	                arrow.setStretch(vStretch);
	              }
	            }
	            v.repaint();
	          }
	        }
	    	}
        TPoint pt = trackerPanel.getSelectedPoint();
        if (pt != null) pt.showCoordinates(trackerPanel);

        // set trails icon
        for (int i = 0; i < trailLengths.length; i++) {
          if (trailLength == trailLengths[i]) {
          	trailButton.setIcon(trailIcons[i]);
          	FontSizer.setFonts(trailButton, FontSizer.getLevel());
          }
        }
        
        // refresh pageViewTabs list
        pageViewTabs.clear();
        TFrame frame = trackerPanel.getTFrame();
        if (frame!=null) {
        TView[][] views = frame.getTViews(trackerPanel);
	        for (TView[] next: views) {
	        	if (next==null) continue;
	        	for (TView view: next) {
	        		if (view==null) continue;
	        		if (view instanceof PageTView) {
	        			PageTView page = (PageTView)view;
	        			for (TabView tab: page.tabs) {
	        				if (tab.data.url!=null) {
	        					pageViewTabs.add(tab.data);
	        				}
	        			}
	        		}
	        	}
	        }
	        sortPageViewTabs();
        }

        // assemble buttons
        removeAll();
        if ( org.opensourcephysics.display.OSPRuntime.applet == null) {
	        if (trackerPanel.isEnabled("file.open")) { //$NON-NLS-1$
	        	add(openButton);
	        }
	        if (trackerPanel.isEnabled("file.save")) { //$NON-NLS-1$
	        	add(saveButton);
	        }
	        if (getComponentCount()>0) add(getSeparator());
	        if (trackerPanel.isEnabled("file.library")) { //$NON-NLS-1$
	        	add(openBrowserButton);
		        if (trackerPanel.isEnabled("file.save")) { //$NON-NLS-1$
		        	add(saveZipButton);
		        }
	        }
        }
        if (getComponentCount()>0) add(getSeparator());
        boolean addSeparator = false;
        if (trackerPanel.isEnabled("button.clipSettings")) {//$NON-NLS-1$
        	add(clipSettingsButton);
        	addSeparator = true;
        }
        if (trackerPanel.isEnabled("calibration.stick") //$NON-NLS-1$
        		|| trackerPanel.isEnabled("calibration.tape") //$NON-NLS-1$
        		|| trackerPanel.isEnabled("calibration.points") //$NON-NLS-1$
        		|| trackerPanel.isEnabled("calibration.offsetOrigin")) { //$NON-NLS-1$
        	add(calibrationButton);
        	addSeparator = true;
        }
        if (trackerPanel.isEnabled("button.axes")) {//$NON-NLS-1$
        	add(axesButton);
        	addSeparator = true;
        }
        if (addSeparator)
        	add(getSeparator());
        if (trackerPanel.isCreateTracksEnabled()) {
        	add(newTrackButton);
        }
        add(trackControlButton);
        if (trackerPanel.isEnabled("track.autotrack")) //$NON-NLS-1$
        	add(autotrackerButton);
      	add(getSeparator());
        add(zoomButton);
        add(getSeparator());
        if (trackerPanel.isEnabled("button.trails") //$NON-NLS-1$
        		|| trackerPanel.isEnabled("button.labels")) { //$NON-NLS-1$
	        if (trackerPanel.isEnabled("button.trails")) //$NON-NLS-1$
	        	add(trailButton);
	        if (trackerPanel.isEnabled("button.labels")) //$NON-NLS-1$
	        	add(labelsButton);
	        add(getSeparator());
        }
        if (trackerPanel.isEnabled("button.path") //$NON-NLS-1$
        		|| trackerPanel.isEnabled("button.x") //$NON-NLS-1$
        		|| trackerPanel.isEnabled("button.v") //$NON-NLS-1$
        		|| trackerPanel.isEnabled("button.a") ) {//$NON-NLS-1$
	        if (trackerPanel.isEnabled("button.path")) //$NON-NLS-1$
	        	add(traceVisButton);
	        if (trackerPanel.isEnabled("button.x")) //$NON-NLS-1$
	        	add(pVisButton);
	        if (trackerPanel.isEnabled("button.v")) //$NON-NLS-1$
	        	add(vVisButton);
	        if (trackerPanel.isEnabled("button.a")) //$NON-NLS-1$
	        	add(aVisButton);
	        add(getSeparator());
        }
        if (trackerPanel.isEnabled("button.stretch") //$NON-NLS-1$
        		|| trackerPanel.isEnabled("button.xMass")) { //$NON-NLS-1$
	        if (trackerPanel.isEnabled("button.stretch")) //$NON-NLS-1$
		        add(stretchButton);
	        if (trackerPanel.isEnabled("button.xMass")) //$NON-NLS-1$
		        add(xMassButton);
	        add(getSeparator());
        }
        add(fontSmallerButton);
        add(fontBiggerButton);
        add(getSeparator());
        add(toolbarFiller);
        if (trackerPanel.isEnabled("button.drawing")) //$NON-NLS-1$
        	add(drawingButton);
        add(desktopButton);
        add(notesButton);
        boolean hasPageURLs = !pageViewTabs.isEmpty();
        desktopButton.setEnabled(hasPageURLs || !trackerPanel.supplementalFilePaths.isEmpty());
        add(refreshButton);
        
        FontSizer.setFonts(newTrackButton, FontSizer.getLevel());
        FontSizer.setFonts(zoomButton, FontSizer.getLevel());
        
        validate();
        repaint();
        refreshing = false;
    	}
    };
    if (SwingUtilities.isEventDispatchThread()) runner.run();
    else SwingUtilities.invokeLater(runner); 
  }

  /**
   * Disposes of this toolbar
   */
  public void dispose() {
  	toolbars.remove(trackerPanel);
    removeAll();
    trackerPanel.removePropertyChangeListener("track", this); //$NON-NLS-1$
    trackerPanel.removePropertyChangeListener("clear", this); //$NON-NLS-1$
    trackerPanel.removePropertyChangeListener("video", this); //$NON-NLS-1$
    trackerPanel.removePropertyChangeListener("magnification", this); //$NON-NLS-1$
    trackerPanel.removePropertyChangeListener("selectedtrack", this); //$NON-NLS-1$
    trackerPanel.removePropertyChangeListener("selectedpoint", this); //$NON-NLS-1$
    for (Integer n: TTrack.activeTracks.keySet()) {
    	TTrack track = TTrack.activeTracks.get(n);
      track.removePropertyChangeListener("locked", this); //$NON-NLS-1$
    	track.removePropertyChangeListener("visible", this); //$NON-NLS-1$
    }
    pageViewTabs.clear();
    trackerPanel = null;
  }

  @Override
  public void finalize() {
  	OSPLog.finer(getClass().getSimpleName()+" recycled by garbage collector"); //$NON-NLS-1$
  }

  /**
   * Responds to the following events: "selectedtrack", "selectedpoint",
   * "track" from tracker panel, "locked" from tracks, "visible" from tape
   * and axes.
   *
   * @param e the property change event
   */
  public void propertyChange(PropertyChangeEvent e) {
    String name = e.getPropertyName();
    if (name.equals("video")) {  // video has changed //$NON-NLS-1$
      refresh(false);
    }
    else if (name.equals("selectedtrack")) {  // selected track has changed //$NON-NLS-1$
      // refresh info dialog if visible
      trackerPanel.refreshNotesDialog();
      refresh(false);
    }
    else if (name.equals("magnification")) {  // magnification has changed //$NON-NLS-1$
    	refreshZoomButton();
    }
    else if (name.equals("visible")) { // axes or calibration tool visibility //$NON-NLS-1$
      if (e.getSource()==trackerPanel.getAxes()) {
	      axesButton.setSelected(trackerPanel.getAxes().isVisible());
      }
      else {
      	calibrationButton.refresh();
      }
    }
    else if (name.equals("locked")) {    // track has been locked or unlocked //$NON-NLS-1$
      refresh(false);
    }
    else if (name.equals("selectedpoint")) {  // selected point has changed //$NON-NLS-1$
      refresh(false);
    }
    else if (name.equals("track")) {     // track has been added or removed //$NON-NLS-1$
      if (e.getOldValue()!=null) {      // track has been removed
        TTrack track = (TTrack)e.getOldValue();
  			trackerPanel.calibrationTools.remove(track);       
  	    trackerPanel.visibleTools.remove(track);
      	track.removePropertyChangeListener("visible", this); //$NON-NLS-1$
      	track.removePropertyChangeListener("locked", this); //$NON-NLS-1$
      	if (trackerPanel.visibleTools.isEmpty()) {
      		calibrationButton.setSelected(false);
      	}
      }
      refresh(true);
    }
    else if (name.equals("clear")) {     // tracks have been cleared //$NON-NLS-1$
      for (Integer n: TTrack.activeTracks.keySet()) {
      	TTrack track = TTrack.activeTracks.get(n);
  			trackerPanel.calibrationTools.remove(track);       
  	    trackerPanel.visibleTools.remove(track);
      	track.removePropertyChangeListener("visible", this); //$NON-NLS-1$
      	track.removePropertyChangeListener("locked", this); //$NON-NLS-1$
      }
  		calibrationButton.setSelected(false);
      refresh(true);
    }
  }
  
  /**
   * Refreshes and returns the "new tracks" popup menu.
   *
   * @return the popup
   */
  protected JPopupMenu getNewTracksPopup() {
    newPopup.removeAll();
    TMenuBar menubar = TMenuBar.getMenuBar(trackerPanel);
    menubar.refresh();
  	for (Component c: menubar.newTrackItems) {
      newPopup.add(c);
      if (c==menubar.newDataTrackMenu) {
        // disable newDataTrackPasteItem unless pastable data is on the clipboard
        menubar.newDataTrackPasteItem.setEnabled(false);
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        Transferable data = clipboard.getContents(null);
        if (data != null && data.isDataFlavorSupported(DataFlavor.stringFlavor)) {
          try {
          	String s = (String)data.getTransferData(DataFlavor.stringFlavor);
          	menubar.newDataTrackPasteItem.setEnabled(ParticleDataTrack.getImportableDataName(s)!=null);
          } catch (Exception ex) {}
        }      	
      }
  	}
    if (menubar.cloneMenu.getItemCount()>0 && trackerPanel.isEnabled("new.clone")) { //$NON-NLS-1$
    	newPopup.addSeparator();
      newPopup.add(menubar.cloneMenu);    
    }
    return newPopup;
  }
  
  public static JButton getSeparator() {
  	JButton b = new JButton(separatorIcon);
  	b.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 4));
  	b.setOpaque(false);
  	b.setContentAreaFilled(false);
  	return b;
  }
  
  private void sortPageViewTabs() {
  	Collections.sort(pageViewTabs, new Comparator<PageTView.TabData> () {
      public int compare(PageTView.TabData one, PageTView.TabData two) {
        return (one.title.toLowerCase().compareTo(two.title.toLowerCase()));
      }
  	});    
  }
  
  /**
   * Gets the toolbar for the specified tracker panel.
   *
   * @param panel the tracker panel
   * @return the toolbar
   */
  public static synchronized TToolBar getToolbar(TrackerPanel panel) {
  	TToolBar toolbar = toolbars.get(panel);
    if (toolbar == null) {
    	toolbar = new TToolBar(panel);
      toolbars.put(panel, toolbar);
    }
    return toolbar;
  }

  /**
   * Returns an XML.ObjectLoader to save and load object data.
   *
   * @return the XML.ObjectLoader
   */
  public static XML.ObjectLoader getLoader() {
    return new Loader();
  }

  /**
   * A class to save and load object data.
   */
  static class Loader implements XML.ObjectLoader {

    /**
     * Saves object data.
     *
     * @param control the control to save to
     * @param obj the TrackerPanel object to save
     */
    public void saveObject(XMLControl control, Object obj) {
      TToolBar toolbar  = (TToolBar)obj;
      control.setValue("trace", toolbar.traceVisButton.isSelected()); //$NON-NLS-1$      
      control.setValue("position", toolbar.pVisButton.isSelected()); //$NON-NLS-1$   
      control.setValue("velocity", toolbar.vVisButton.isSelected()); //$NON-NLS-1$   
      control.setValue("acceleration", toolbar.aVisButton.isSelected()); //$NON-NLS-1$   
      control.setValue("labels", toolbar.labelsButton.isSelected()); //$NON-NLS-1$   
      control.setValue("multiply_by_mass", toolbar.xMassButton.isSelected()); //$NON-NLS-1$   
      control.setValue("trail_length", toolbar.trailLength); //$NON-NLS-1$   
      control.setValue("stretch", toolbar.vStretch); //$NON-NLS-1$   
      control.setValue("stretch_acceleration", toolbar.aStretch); //$NON-NLS-1$   
    }

    /**
     * Creates an object.
     *
     * @param control the control
     * @return the newly created object
     */
    public Object createObject(XMLControl control){
      return null;
    }

    /**
     * Loads an object with data from an XMLControl.
     *
     * @param control the control
     * @param obj the object
     * @return the loaded object
     */
    public Object loadObject(XMLControl control, Object obj) {
    	TToolBar toolbar  = (TToolBar)obj;
      toolbar.traceVisButton.setSelected(control.getBoolean("trace")); //$NON-NLS-1$
      toolbar.pVisButton.setSelected(control.getBoolean("position")); //$NON-NLS-1$
      toolbar.vVisButton.setSelected(control.getBoolean("velocity")); //$NON-NLS-1$
      toolbar.aVisButton.setSelected(control.getBoolean("acceleration")); //$NON-NLS-1$
      toolbar.labelsButton.setSelected(control.getBoolean("labels")); //$NON-NLS-1$
      toolbar.xMassButton.setSelected(control.getBoolean("multiply_by_mass")); //$NON-NLS-1$
      toolbar.trailLength = control.getInt("trail_length"); //$NON-NLS-1$   
      toolbar.vStretch = control.getInt("stretch"); //$NON-NLS-1$
      if (control.getPropertyNames().contains("stretch_acceleration")) { //$NON-NLS-1$ 
      	toolbar.aStretch = control.getInt("stretch_acceleration"); //$NON-NLS-1$  
      }
      else toolbar.aStretch = toolbar.vStretch;
      return obj;
    }
  }
  
  /**
   * A button to manage the creation and visibility of calibration tools.
   */
  protected class CalibrationButton extends TButton 
  		implements ActionListener {
  	
  	boolean showPopup;
    JPopupMenu popup = new JPopupMenu();
    
    /**
     * Constructor.
     */
    private CalibrationButton() {
    	setIcons(calibrationToolsOffIcon, calibrationToolsOnIcon);
      setRolloverIcon(calibrationToolsOffRolloverIcon);
      setRolloverSelectedIcon(calibrationToolsOnRolloverIcon);
      // mouse listener to distinguish between popup and tool visibility actions
      addMouseListener(new MouseAdapter() {
        public void mousePressed(MouseEvent e) {
        	int w = calibrationToolsOffRolloverIcon.getIconWidth();
        	int dw = calibrationButton.getWidth()-w;
        	// show popup if right side of button clicked or if no tools selected
        	showPopup = e.getX()>(18 + dw/2) || trackerPanel.visibleTools.isEmpty();
        }
      });
      addActionListener(this);
    }
 	
    /**
     * Overrides TButton method.
     *
     * @return the popup, or null if the right side of this button was clicked
     */
    protected JPopupMenu getPopup() {
    	if (!showPopup)	return null;
      // rebuild popup menu
    	popup.removeAll();    	
      JMenuItem item;
      for (TTrack track: trackerPanel.calibrationTools) {
        item = new JCheckBoxMenuItem(track.getName());
        item.setSelected(trackerPanel.visibleTools.contains(track));
        item.setActionCommand(track.getName());
        item.addActionListener(this);
      	popup.add(item);
      }
      // new tools menu
      JMenu newToolsMenu = getCalibrationToolsMenu();
      if (newToolsMenu.getItemCount()>0) {
      	if (!trackerPanel.calibrationTools.isEmpty())
      		popup.addSeparator();
	      popup.add(newToolsMenu);
      }
      FontSizer.setFonts(popup, FontSizer.getLevel());	          
    	return popup;
    }
    
    protected JMenu getCalibrationToolsMenu() {
      // new tools menu
      JMenu newToolsMenu = new JMenu(TrackerRes.getString("TMenuBar.MenuItem.NewTrack")); //$NON-NLS-1$
      JMenuItem item;
      if (trackerPanel.isEnabled("calibration.stick")) { //$NON-NLS-1$
        item = new JMenuItem(TrackerRes.getString("Stick.Name")); //$NON-NLS-1$
	      item.addActionListener(new ActionListener() {
	        public void actionPerformed(ActionEvent e) {
	    			TapeMeasure track = new TapeMeasure();
	          track.setColor(Color.BLUE);
	        	track.setStickMode(true);
	          // assign a default name
	        	String name = TrackerRes.getString("CalibrationStick.New.Name"); //$NON-NLS-1$
	          int i = trackerPanel.getAlphabetIndex(name, " "); //$NON-NLS-1$
	          String letter = TrackerPanel.alphabet.substring(i, i+1);
	          track.setName(name+" "+letter); //$NON-NLS-1$	          
	  				trackerPanel.addTrack(track);
	          calibrationButton.setSelected(true);
	          
		      	// show all tools in visibleTools list
		      	for (TTrack next: trackerPanel.visibleTools) {
			      	showCalibrationTool(next);
		      	}
		      	
		      	// mark immediately if preferred
		      	if (Tracker.centerCalibrationStick) {
			      	// place at center of viewport
			      	MainTView mainView = trackerPanel.getTFrame().getMainView(trackerPanel);
			        Rectangle rect = mainView.scrollPane.getViewport().getViewRect();
			        int xpix = rect.x+rect.width/2;
			        int ypix = rect.y+rect.height/2;
			        double x = trackerPanel.pixToX(xpix);
			        double y = trackerPanel.pixToY(ypix);
			        track.createStep(0, x-50, y, x+50, y); // length 100 image units
		      	}
		      	
	    			trackerPanel.setSelectedTrack(track);
	        }
	      });
	      newToolsMenu.add(item);
      }
      
      if (trackerPanel.isEnabled("calibration.tape")) { //$NON-NLS-1$
	      item = new JMenuItem(TrackerRes.getString("CalibrationTapeMeasure.Name")); //$NON-NLS-1$
	      item.addActionListener(new ActionListener() {
	        public void actionPerformed(ActionEvent e) {
	    			TapeMeasure track = new TapeMeasure();
	          track.setColor(Color.BLUE);
	    			track.setReadOnly(false);
	          // assign a default name
	        	String name = TrackerRes.getString("CalibrationTapeMeasure.New.Name"); //$NON-NLS-1$
	          int i = trackerPanel.getAlphabetIndex(name, " "); //$NON-NLS-1$
	          String letter = TrackerPanel.alphabet.substring(i, i+1);
	          track.setName(name+" "+letter); //$NON-NLS-1$	
	  				trackerPanel.addTrack(track);
	          calibrationButton.setSelected(true);
	          
		      	// show all tools in visibleTools list
		      	for (TTrack next: trackerPanel.visibleTools) {
			      	showCalibrationTool(next);
		      	}
		      	
		      	// mark immediately if preferred
		      	if (Tracker.centerCalibrationStick) {
			      	// place at center of viewport
			      	MainTView mainView = trackerPanel.getTFrame().getMainView(trackerPanel);
			        Rectangle rect = mainView.scrollPane.getViewport().getViewRect();
			        int xpix = rect.x+rect.width/2;
			        int ypix = rect.y+rect.height/2;
			        double x = trackerPanel.pixToX(xpix);
			        double y = trackerPanel.pixToY(ypix);
			        track.createStep(0, x-50, y, x+50, y); // length 100 image units
		      	}
		      	
	    			trackerPanel.setSelectedTrack(track);
	        }
	      });
	      newToolsMenu.add(item);
      }

      if (trackerPanel.isEnabled("calibration.points")) { //$NON-NLS-1$
	      item = new JMenuItem(TrackerRes.getString("Calibration.Name")); //$NON-NLS-1$
	      item.addActionListener(new ActionListener() {
	        public void actionPerformed(ActionEvent e) {
	    			Calibration track = new Calibration();
	          // assign a default name
	        	String name = TrackerRes.getString("Calibration.New.Name"); //$NON-NLS-1$
	          int i = trackerPanel.getAlphabetIndex(name, " "); //$NON-NLS-1$
	          String letter = TrackerPanel.alphabet.substring(i, i+1);
	          track.setName(name+" "+letter); //$NON-NLS-1$
	
	  				trackerPanel.addTrack(track);
	          calibrationButton.setSelected(true);
		      	// show all tools in visibleTools list
		      	for (TTrack next: trackerPanel.visibleTools) {
			      	showCalibrationTool(next);
		      	}
	    			trackerPanel.setSelectedTrack(track);
	    			trackerPanel.getAxes().setVisible(true);
	        }
	      });
	      newToolsMenu.add(item);
      }

      if (trackerPanel.isEnabled("calibration.offsetOrigin")) { //$NON-NLS-1$
	      item = new JMenuItem(TrackerRes.getString("OffsetOrigin.Name")); //$NON-NLS-1$
	      item.addActionListener(new ActionListener() {
	        public void actionPerformed(ActionEvent e) {
	        	OffsetOrigin track = new OffsetOrigin();
	          // assign a default name
	        	String name = TrackerRes.getString("OffsetOrigin.New.Name"); //$NON-NLS-1$
	          int i = trackerPanel.getAlphabetIndex(name, " "); //$NON-NLS-1$
	          String letter = TrackerPanel.alphabet.substring(i, i+1);
	          track.setName(name+" "+letter); //$NON-NLS-1$
	
	  				trackerPanel.addTrack(track);
	          calibrationButton.setSelected(true);
		      	// show all tools in visibleTools list
		      	for (TTrack next: trackerPanel.visibleTools) {
			      	showCalibrationTool(next);
		      	}
	    			trackerPanel.setSelectedTrack(track);
	    			trackerPanel.getAxes().setVisible(true);
	        }
	      });
	      newToolsMenu.add(item);
      }
      return newToolsMenu;
    }
    
    /**
     * Responds to action events from both this button and the popup items.
     *
     * @param e the action event
     */
    public void actionPerformed(ActionEvent e) {
    	if (e.getSource()==calibrationButton) { // button action: show/hide tools
    		if (showPopup) return;
	      trackerPanel.setSelectedPoint(null);
        trackerPanel.selectedSteps.clear();
	      trackerPanel.hideMouseBox();        
	      if (!calibrationButton.isSelected()) {
	      	calibrationButton.setSelected(true);
	      	// show tools in visibleTools list
	      	for (TTrack track: trackerPanel.visibleTools) {
		      	showCalibrationTool(track);
	      	}
	      }
	      else { 
	      	calibrationButton.setSelected(false);
	      	// hide all tools
	      	for (TTrack track: trackerPanel.calibrationTools) {
		      	hideCalibrationTool(track);
	      	}
	      }
	      trackerPanel.repaint();
    	}
    	else { // menuItem action
    		// see which item changed and show/hide corresponding tool
      	trackerPanel.setSelectedPoint(null);
        trackerPanel.selectedSteps.clear();
        JMenuItem source = (JMenuItem)e.getSource();
        for (TTrack track: trackerPanel.calibrationTools) {
          if (e.getActionCommand().equals(track.getName())) {
        		if (source.isSelected()) {
        			trackerPanel.visibleTools.add(track);
    	      	calibrationButton.setSelected(true);
    	      	// show only tools in visibleTools
    	      	for (TTrack next: trackerPanel.visibleTools) {
    		      	showCalibrationTool(next);
    	      	}        			
        		}
        		else {
        			hideCalibrationTool(track);
        			trackerPanel.visibleTools.remove(track);
        			boolean toolsVisible = false;
    	      	for (TTrack next: trackerPanel.visibleTools) {
    		      	toolsVisible = toolsVisible || next.isVisible();
    	      	}        			
    	      	calibrationButton.setSelected(toolsVisible);
        		}
          }
        }
        refresh();    		
    	}
    }
    
    /**
     * Shows a calibration tool.
     *
     * @param track a calibration tool 
     */
    void showCalibrationTool(TTrack track) {
  		track.erase();
  		track.setVisible(true);
  		if (track instanceof Calibration) {
  			int n = trackerPanel.getFrameNumber();
  			Step step = track.getStep(n);
  			if (step==null || step.getPoints()[1]==null) {
  				trackerPanel.setSelectedTrack(track);
  			}
  		}
  		else if (track instanceof OffsetOrigin) {
  			int n = trackerPanel.getFrameNumber();
  			Step step = track.getStep(n);
  			if (step==null) {
  				trackerPanel.setSelectedTrack(track);
  			}
  		}
    }
    
    /**
     * Hides a calibration tool.
     *
     * @param track a calibration tool 
     */
    void hideCalibrationTool(TTrack track) {
  		track.setVisible(false);
    	if (trackerPanel.getSelectedTrack()==track) {
      	trackerPanel.setSelectedTrack(null);
    	}
    }

    /**
     * Refreshes this button.
     */
    void refresh() {
      setToolTipText(TrackerRes.getString("TToolbar.Button.TapeVisible.Tooltip")); //$NON-NLS-1$        	
      // add "visible" property change listeners to calibration tools
      for (TTrack track: trackerPanel.calibrationTools) {
      	track.removePropertyChangeListener("visible", TToolBar.this); //$NON-NLS-1$
      	track.addPropertyChangeListener("visible", TToolBar.this); //$NON-NLS-1$      	
      }
      // check visibility of tools and state of menu items
      boolean toolsVisible = false;
      for (TTrack track: trackerPanel.calibrationTools) {
		  	toolsVisible = toolsVisible || track.isVisible();
      }
      if (notYetCalibrated && toolsVisible) {
      	notYetCalibrated = false;
	      setSelected(true);
      }
    }
    
  } // end calibration button
  
  /**
   * A button to manage the visibility of the pencil scenes and control dialog
   */
  protected class DrawingButton extends TButton 
  		implements ActionListener {
  	  	
  	boolean showPopup; 	
    JPopupMenu popup;
    JMenuItem drawingVisibleCheckbox;
    
    /**
     * Constructor.
     */
    private DrawingButton() {
    	setIcons(pencilOffIcon, pencilOnIcon);      
      setRolloverIcon(pencilOffRolloverIcon);
      setRolloverSelectedIcon(pencilOnRolloverIcon);
      addActionListener(this);
      
      // mouse listener to distinguish between popup and tool visibility actions
      addMouseListener(new MouseAdapter() {
        public void mousePressed(MouseEvent e) {
        	int w = getIcon().getIconWidth();
        	int dw = getWidth()-w;
        	// show popup if right side of button clicked
        	showPopup = e.getX()>(w*18/28 + dw/2);
        }
      });

      drawingVisibleCheckbox = new JMenuItem();
      drawingVisibleCheckbox.setSelected(true);
      drawingVisibleCheckbox.setDisabledIcon(checkboxOnDisabledIcon);
      drawingVisibleCheckbox.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
        	drawingVisibleCheckbox.setSelected(!drawingVisibleCheckbox.isSelected());
        	trackerPanel.setSelectedPoint(null);
          trackerPanel.selectedSteps.clear();
        	PencilDrawer drawer = PencilDrawer.getDrawer(trackerPanel);
        	drawer.setDrawingsVisible(drawingVisibleCheckbox.isSelected());
        	trackerPanel.repaint();
        }
      }); 
      popup = new JPopupMenu();
    	popup.add(drawingVisibleCheckbox);
    }
 	
    @Override
    protected JPopupMenu getPopup() {
    	if (!showPopup)	return null;
    	refresh();
    	FontSizer.setFonts(popup, FontSizer.getLevel());
    	checkboxOnDisabledIcon.resize(FontSizer.getIntegerFactor());
    	return popup;
    }
    
    @Override
    public void actionPerformed(ActionEvent e) {
  		if (showPopup) return;
      trackerPanel.setSelectedPoint(null);
      trackerPanel.selectedSteps.clear();
      trackerPanel.hideMouseBox();        
      setSelected(!isSelected());
    	PencilDrawer drawer = PencilDrawer.getDrawer(trackerPanel);
      drawer.getDrawingControl().setVisible(isSelected());
      if (isSelected()) {
				if (drawer.scenes.isEmpty()) {
					drawer.addNewScene();
				}
				else {
					PencilScene scene = drawer.getSceneAtFrame(trackerPanel.getFrameNumber());
					drawer.getDrawingControl().setSelectedScene(scene);
				}
	      drawer.setDrawingsVisible(true);        	
      }
    }
    
    /**
     * Refreshes this button.
     */
    void refresh() {
      setToolTipText(TrackerRes.getString("TToolBar.Button.Drawings.Tooltip")); //$NON-NLS-1$
      drawingVisibleCheckbox.setText(TrackerRes.getString("TToolBar.MenuItem.DrawingsVisible.Text")); //$NON-NLS-1$
    	PencilDrawer drawer = PencilDrawer.getDrawer(trackerPanel);
    	drawingVisibleCheckbox.setSelected(drawer.areDrawingsVisible());
      drawingVisibleCheckbox.setIcon(drawer.areDrawingsVisible()? checkboxOnIcon: checkboxOffIcon);
      drawingVisibleCheckbox.setEnabled(!PencilDrawer.isDrawing(trackerPanel));
    }
    
  }


}