/*
 * 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.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collections;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.ListCellRenderer;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
import javax.swing.undo.UndoableEditSupport;

import org.opensourcephysics.display.ColorIcon;
import org.opensourcephysics.display.DrawingPanel;
import org.opensourcephysics.display.ResizableIcon;
import org.opensourcephysics.tools.FontSizer;

/**
 * A Dialog to control PencilScenes for a PencilDrawer.
 *
 * @author Douglas Brown
 */
public class PencilControl extends JDialog {
	
	static PencilScene dummyScene = new PencilScene();
  static Icon undoIcon, redoIcon, undoDisabledIcon, redoDisabledIcon;
  private static Color lightgrey = new Color(230, 230, 230);

  static {
  	undoIcon =  new ResizableIcon(Tracker.class.getResource("resources/images/undo.gif")); //$NON-NLS-1$
  	redoIcon =  new ResizableIcon(Tracker.class.getResource("resources/images/redo.gif")); //$NON-NLS-1$
  	undoDisabledIcon =  new ResizableIcon(Tracker.class.getResource("resources/images/undo_disabled.gif")); //$NON-NLS-1$
  	redoDisabledIcon =  new ResizableIcon(Tracker.class.getResource("resources/images/redo_disabled.gif")); //$NON-NLS-1$
  }
  	
	private PencilDrawer drawer;
	private TrackerPanel trackerPanel;
	private PencilScene selectedScene;
  private JComboBox sceneDropdown;
  private DrawingPanel canvas;
  private JLabel drawingLabel, captionLabel, framesLabel, toLabel;
  private JSpinner startFrameSpinner, endFrameSpinner;
  private JTextField captionField;
  private JButton newSceneButton, deleteSceneButton;
  private JButton undoButton, redoButton, clearAllButton, closeButton, helpButton;
  private ColorButton[][] colorButtons;
  private Dimension canvasSize = new Dimension(120, 90);
  private boolean refreshing;
  private PropertyChangeListener stepListener, tabListener, clipListener;
  private int buttonWidth = 14;
  private boolean isVisible;
  private UndoableEditSupport undoSupport;
  private UndoManager undoManager;
  private AbstractAction postCaptionEditAction;
  private String prevCaptionText;
  JSpinner fontSizeSpinner;
  JCheckBox heavyCheckbox;
	
  /**
   * Constructs a PencilControl for a specified PencilDrawer.
   * 
   * @param pencilDrawer the PencilDrawer
   */
	protected PencilControl(PencilDrawer pencilDrawer) {
		super(JOptionPane.getFrameForComponent(pencilDrawer.trackerPanel), false);
		drawer = pencilDrawer;
		trackerPanel = drawer.trackerPanel;
		// set up the undo system
    undoManager = new UndoManager();
    undoSupport = new UndoableEditSupport();
    undoSupport.addUndoableEditListener(undoManager);
		createGUI();  		 		
		refreshGUI();
		pack();
		// center on screen
    Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
    int x = (dim.width - getBounds().width) / 2;
    int y = (dim.height - getBounds().height) / 2;
    setLocation(x, y);
    // create PropertyChangeListeners
		stepListener = new PropertyChangeListener() {
			@Override
			public void propertyChange(PropertyChangeEvent e) {
				// if selected scene is visible at current frame, do nothing
				if (selectedScene!=null && selectedScene.includesFrame(trackerPanel.getFrameNumber())) return;
				if (isVisible()) {
					setSelectedScene(drawer.getSceneAtFrame(trackerPanel.getFrameNumber()));
					refreshGUI();
				}
			}			
		};
		trackerPanel.addPropertyChangeListener("stepnumber", stepListener); //$NON-NLS-1$
		tabListener = new PropertyChangeListener() {
			@Override
			public void propertyChange(PropertyChangeEvent e) {
	      if (e.getNewValue() == trackerPanel) {
	        setVisible(isVisible);
	      }
	      else {
	        boolean vis = isVisible;
	        setVisible(false);
	        isVisible = vis;
	      }
			}			
		};
    TFrame frame = trackerPanel.getTFrame();
    if (frame != null) {
      frame.addPropertyChangeListener("tab", tabListener); //$NON-NLS-1$
    }
		clipListener = new PropertyChangeListener() {
			@Override
			public void propertyChange(PropertyChangeEvent e) {
				// stepcount has changed
				refreshGUI();
			}			
		};
		trackerPanel.addPropertyChangeListener("stepcount", clipListener); //$NON-NLS-1$
	}
	
  /**
   * Creates and assembles the GUI components
   */
	private void createGUI() {
		setResizable(false);
		// create labels
		drawingLabel = new JLabel();
		drawingLabel.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 4));
		captionLabel = new JLabel();
		captionLabel.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 4));
		framesLabel = new JLabel();
		framesLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 4));
		toLabel = new JLabel();
		toLabel.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 4));
		
		// create buttons
		newSceneButton = new JButton() {
  		@Override
      public Dimension getMaximumSize() {
  			Dimension dim = super.getMaximumSize();
  	    dim.height = captionField.getPreferredSize().height+4;
        return dim;
      }    	
		};
		newSceneButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				drawer.addNewScene();
				refreshGUI();
			} 			
		});

		deleteSceneButton = new JButton() {
  		@Override
      public Dimension getMaximumSize() {
  			Dimension dim = super.getMaximumSize();
  	    dim.height = captionField.getPreferredSize().height+4;
        return dim;
      }    	

		};
		deleteSceneButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				boolean isEmpty = selectedScene.getDrawings().isEmpty()
						&& "".equals(selectedScene.getCaption().getText()); //$NON-NLS-1$
				drawer.removeScene(selectedScene);
				if (!isEmpty) {
					postDeletionEdit(selectedScene);
				}
				setSelectedScene(drawer.getSceneAtFrame(trackerPanel.getFrameNumber()));
			} 			
		});
		
		undoButton = new JButton(undoIcon) {
  		@Override
      public Dimension getPreferredSize() {
				Dimension dim = super.getPreferredSize();
		    dim.width = (int)(2*buttonWidth*FontSizer.getFactor());
	      return dim;
      }    	
		};
		undoButton.setDisabledIcon(undoDisabledIcon);
		undoButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
      	undoManager.undo();
    		refreshGUI();
      }
    });  		
		
		redoButton = new JButton(redoIcon) {
  		@Override
      public Dimension getPreferredSize() {
				Dimension dim = super.getPreferredSize();
		    dim.width = (int)(2*buttonWidth*FontSizer.getFactor());
	      return dim;
      }    	
		};
		redoButton.setDisabledIcon(redoDisabledIcon);
		redoButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
      	undoManager.redo();
    		refreshGUI();
      }
    });  		
		
		clearAllButton = new JButton();
		clearAllButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
      	if (PencilDrawer.hasDrawings(trackerPanel)) {
      		postClearEdit(drawer.scenes);
      	}
      	drawer.clearScenes();
      	setSelectedScene(null);
    		refreshGUI();
      }
    });
		
		closeButton = new JButton();
		closeButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				setVisible(false);
			} 			
		});
		
		helpButton = new JButton();
		helpButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
        trackerPanel.getTFrame().showHelp("drawings", 0); //$NON-NLS-1$
			} 			
		});
		
		// create pencilButtons AFTER undoButton
		Box colorBox = Box.createVerticalBox();
	  colorButtons = new ColorButton[PencilDrawer.colors.length][PencilDrawer.colors[0].length];
		for (int i=0; i<PencilDrawer.colors[0].length; i++) {
			Box b = Box.createHorizontalBox();
			for (int j=0; j<colorButtons.length; j++) {
				colorButtons[j][i] = new ColorButton(PencilDrawer.colors[j][i]);
				b.add(colorButtons[j][i]);
			}
			colorBox.add(b);
		}
		
		// create checkbox
		heavyCheckbox = new JCheckBox();
		heavyCheckbox.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
      	if (refreshing || selectedScene==null) return;
      	selectedScene.setHeavy(heavyCheckbox.isSelected());
      	trackerPanel.repaint();
      	repaintCanvas();
			} 			
		});
				
		// create spinners
		startFrameSpinner = new JSpinner();
		startFrameSpinner.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
      	if (selectedScene==null) return;
      	selectedScene.setStartFrame((Integer)startFrameSpinner.getValue());
      	Collections.sort(drawer.scenes);
      	trackerPanel.repaint();
      	refreshGUI();
      }
    }); 
		
		endFrameSpinner = new JSpinner();
		endFrameSpinner.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
      	if (selectedScene==null) return;
      	selectedScene.setEndFrame((Integer)endFrameSpinner.getValue());
      	Collections.sort(drawer.scenes);
      	trackerPanel.repaint();
      	refreshGUI();
      }
    });
		
		int defaultFontSize = PencilCaption.baseFont.getSize();
		fontSizeSpinner = new JSpinner(new SpinnerNumberModel(defaultFontSize, 12, 98, 2));
		((JSpinner.DefaultEditor)fontSizeSpinner.getEditor()).getTextField().setEditable(false);
		fontSizeSpinner.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
      	if (selectedScene.getCaption()==null) return;
      	if (refreshing) return;
      	Font font = selectedScene.getCaption().getFont();
      	float size = (Integer)fontSizeSpinner.getValue();
      	font = font.deriveFont(size);
      	selectedScene.getCaption().setFont(font);
      	trackerPanel.repaint();
      	repaintCanvas();
      }
    });
		
		// create dropdown
		sceneDropdown = new JComboBox() {
  		@Override
      public Dimension getMaximumSize() {
  			Dimension dim = super.getMaximumSize();
  	    dim.height = deleteSceneButton.getMaximumSize().height;
        return dim;
      }    	
		};
		sceneDropdown.setRenderer(new SceneDropdownRenderer());
		sceneDropdown.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
      	if (refreshing) return;
      	PencilScene scene = (PencilScene)sceneDropdown.getSelectedItem();
      	if (scene==dummyScene) return;
      	setSelectedScene(scene);
      	if (selectedScene!=null) {
      		goToScene(selectedScene);
      	}
      	captionField.requestFocusInWindow();
      }
    });
		
		// create caption field
		captionField = new JTextField(16);
		captionField.addKeyListener(new KeyAdapter() {
      public void keyReleased(KeyEvent e) {
      	if (e.getKeyCode()==KeyEvent.VK_ENTER) return;
      	String text = captionField.getText();
      	if (!selectedScene.isCaptionPositioned && !"".equals(text.trim())) { //$NON-NLS-1$
      		// place caption 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);
        	PencilCaption caption = selectedScene.getCaption();
        	caption.setXY(x, y);
        	caption.setText(text);       	
        	caption.color = drawer.color;
        	selectedScene.isCaptionPositioned = true;
        	trackerPanel.changed = true;
  			}
      	else {
      		selectedScene.getCaption().setText(text);
        	trackerPanel.changed = true;
      	}
      	captionField.setBackground(Color.YELLOW);
      	refreshGUI();
      	trackerPanel.repaint();
      }
    });
		postCaptionEditAction = new AbstractAction() {
			public void actionPerformed(ActionEvent e) {
      	String text = selectedScene.getCaption().getText().trim();
    		selectedScene.getCaption().setText(text);
      	postCaptionEdit(selectedScene, prevCaptionText, text);
      	captionField.setBackground(Color.WHITE);
      	prevCaptionText = text;
      	refreshGUI();
      }
		};
		captionField.addActionListener(postCaptionEditAction);
		captionField.addFocusListener(new FocusAdapter() {
			@Override
			public void focusLost(FocusEvent e) {
				if (captionField.getBackground().equals(Color.YELLOW)) {
					postCaptionEditAction.actionPerformed(null);
				}
			}
		});
		
		// create canvas
		canvas = new DrawingPanel();
		canvas.setAutoscaleX(false);
		canvas.setAutoscaleY(false);
		canvas.setSquareAspect(true);
		canvas.setShowCoordinates(false);
		canvas.setBackground(Color.WHITE);
		canvas.setPreferredGutters(6, 6, 6, 6);
		canvas.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
		canvas.setPreferredSize(canvasSize);
		canvas.setEnabled(false);
		
		// assemble GUI
		JPanel contentPane = new JPanel(new BorderLayout());
		contentPane.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
		setContentPane(contentPane);

		// north panel
		JPanel northPanel = new JPanel(new BorderLayout());
		contentPane.add(northPanel, BorderLayout.NORTH);
		
		// add canvas to north panel
		northPanel.add(canvas, BorderLayout.CENTER);
		
		// add drawing bar to north panel
		JToolBar drawingBar = new JToolBar();
		northPanel.add(drawingBar, BorderLayout.NORTH);
		drawingBar.setBorder(BorderFactory.createEmptyBorder(2, 4, 2, 4));
		drawingBar.setFloatable(false);
		drawingBar.setOpaque(false);
		drawingBar.add(drawingLabel);
		drawingBar.add(sceneDropdown);
		drawingBar.add(Box.createHorizontalStrut(2));
		drawingBar.add(newSceneButton);
		drawingBar.add(Box.createHorizontalStrut(2));
		drawingBar.add(deleteSceneButton);
		
		// add caption bar to north panel
		JToolBar captionBar = new JToolBar();
		captionBar.setBorder(BorderFactory.createEmptyBorder(8, 4, 4, 8));
		northPanel.add(captionBar, BorderLayout.SOUTH);
		captionBar.setFloatable(false);
		captionBar.setOpaque(false);
		captionBar.add(captionLabel);
		captionBar.add(captionField);
		captionBar.add(fontSizeSpinner);
		
		// add color box to north panel
		northPanel.add(colorBox, BorderLayout.WEST);
		
		// add undo/redo buttons to north panel
		Box undoBox = Box.createVerticalBox();
		northPanel.add(undoBox, BorderLayout.EAST);
		undoBox.add(undoButton);
		undoBox.add(redoButton);
		undoBox.add(Box.createVerticalGlue());
		
		// center panel
		JPanel centerPanel = new JPanel();
		contentPane.add(centerPanel, BorderLayout.CENTER);
		
		// add frame spinners to center panel
		JToolBar controlBar = new JToolBar();
		centerPanel.add(controlBar);
		controlBar.setFloatable(false);
		controlBar.setOpaque(false);
		controlBar.setBorderPainted(false);
		controlBar.add(framesLabel);
		controlBar.add(startFrameSpinner);
		controlBar.add(toLabel);
		controlBar.add(endFrameSpinner);
		
		// add heavyCheckbox to center panel
		centerPanel.add(Box.createHorizontalStrut(15));
		centerPanel.add(heavyCheckbox);

		// south panel
		JPanel southPanel = new JPanel();
		southPanel.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
		contentPane.add(southPanel, BorderLayout.SOUTH);
		
		// add clear and close buttons to south panel
		southPanel.add(helpButton);  		
		southPanel.add(clearAllButton);  		
		southPanel.add(closeButton);  
		
	}
	
  /**
   * Sets the font level
   * 
   * @param level the font level
   */
  protected void setFontLevel(int level) {
		FontSizer.setFonts(this, level);
		pack();
  }
  
  /**
   * Posts a drawing edit for a specified scene.
   * 
   * @param drawing the drawing
   * @param scene the scene
   */
  protected void postDrawingEdit(PencilDrawing drawing, PencilScene scene) {
  	DrawingEdit edit = new DrawingEdit(drawing, scene);
		undoSupport.postEdit(edit);
  }
	
  /**
   * Posts a caption edit for a specified scene.
   * 
   * @param scene the scene
   * @param oldText the previous caption text
   * @param newText the new caption text
   */
  protected void postCaptionEdit(PencilScene scene, String oldText, String newText) {
  	CaptionEdit edit = new CaptionEdit(scene, oldText, newText);
		undoSupport.postEdit(edit);
  }
	
  /**
   * Posts a deletion edit for a specified scene.
   * 
   * @param scene the scene
   */
  protected void postDeletionEdit(PencilScene scene) {
  	DeletionEdit edit = new DeletionEdit(scene);
		undoSupport.postEdit(edit);
  }
	
  /**
   * Posts a clear edit.
   * 
   * @param scenes the cleared scenes
   */
  protected void postClearEdit(ArrayList<PencilScene> scenes) {
  	ClearEdit edit = new ClearEdit(scenes);
		undoSupport.postEdit(edit);
  }
	
  /**
   * Refreshes the GUI.
   */
	protected void refreshGUI() {
		refreshing = true;
		setTitle(TrackerRes.getString("PencilControlDialog.Title")); //$NON-NLS-1$
		// set label and button text
		drawingLabel.setText(TrackerRes.getString("PencilControlDialog.Label.Drawing.Text")+":"); //$NON-NLS-1$ //$NON-NLS-2$
		toLabel.setText(TrackerRes.getString("PencilControlDialog.Label.To.Text")); //$NON-NLS-1$
		captionLabel.setText(TrackerRes.getString("PencilControlDialog.Label.Caption.Text")+":"); //$NON-NLS-1$ //$NON-NLS-2$
		framesLabel.setText(TrackerRes.getString("PencilControlDialog.Label.Frames.Text")); //$NON-NLS-1$
		newSceneButton.setText(TrackerRes.getString("PencilControlDialog.Button.NewScene.Text")); //$NON-NLS-1$
		deleteSceneButton.setText(TrackerRes.getString("PencilControlDialog.Button.DeleteScene.Text")); //$NON-NLS-1$
		clearAllButton.setText(TrackerRes.getString("PencilControlDialog.Button.ClearAll.Text")); //$NON-NLS-1$
		closeButton.setText(TrackerRes.getString("Dialog.Button.Close")); //$NON-NLS-1$
		helpButton.setText(TrackerRes.getString("Dialog.Button.Help")); //$NON-NLS-1$
		heavyCheckbox.setText(TrackerRes.getString("PencilControlDialog.Checkbox.Heavy.Text")); //$NON-NLS-1$
		// set label and button tooltips
		newSceneButton.setToolTipText(TrackerRes.getString("PencilControlDialog.Button.NewScene.Tooltip")); //$NON-NLS-1$
		deleteSceneButton.setToolTipText(TrackerRes.getString("PencilControlDialog.Button.DeleteScene.Tooltip")); //$NON-NLS-1$
		clearAllButton.setToolTipText(TrackerRes.getString("PencilControlDialog.Button.ClearAll.Tooltip")); //$NON-NLS-1$
		heavyCheckbox.setToolTipText(TrackerRes.getString("PencilControlDialog.Checkbox.Heavy.Tooltip")); //$NON-NLS-1$
		captionField.setToolTipText(TrackerRes.getString("PencilControlDialog.Field.Caption.Tooltip")); //$NON-NLS-1$		
		sceneDropdown.setToolTipText(TrackerRes.getString("PencilControlDialog.Dropdown.Drawing.Tooltip")); //$NON-NLS-1$		
		fontSizeSpinner.setToolTipText(TrackerRes.getString("PencilControlDialog.Spinner.FontSize.Tooltip")); //$NON-NLS-1$		
		startFrameSpinner.setToolTipText(TrackerRes.getString("PencilControlDialog.Spinner.FrameRange.Tooltip")); //$NON-NLS-1$		
		endFrameSpinner.setToolTipText(TrackerRes.getString("PencilControlDialog.Spinner.FrameRange.Tooltip")); //$NON-NLS-1$		
		undoButton.setToolTipText(undoManager.getUndoPresentationName());
		redoButton.setToolTipText(undoManager.getRedoPresentationName());
		// enable/disable components
		boolean enabled = selectedScene!=null;
		drawingLabel.setEnabled(enabled);
		toLabel.setEnabled(enabled);
		captionLabel.setEnabled(enabled);
		framesLabel.setEnabled(enabled);
		deleteSceneButton.setEnabled(enabled);
		heavyCheckbox.setEnabled(enabled);
		fontSizeSpinner.setEnabled(enabled);
		startFrameSpinner.setEnabled(enabled);
		endFrameSpinner.setEnabled(enabled);
		captionField.setEnabled(enabled);
		undoButton.setEnabled(undoManager.canUndo());
		redoButton.setEnabled(undoManager.canRedo());
		clearAllButton.setEnabled(!drawer.scenes.isEmpty());
		sceneDropdown.setEnabled(!drawer.scenes.isEmpty());
		if (enabled) {
			repaintCanvas();
  	  int first = trackerPanel.getPlayer().getVideoClip().getFirstFrameNumber();
  	  int last = trackerPanel.getPlayer().getVideoClip().getLastFrameNumber();
  	  // scene start frame can't be less than clip start frame
  	  selectedScene.startframe = Math.max(first, selectedScene.startframe);
  		SpinnerNumberModel model = new SpinnerNumberModel(selectedScene.startframe, first, last, 1); // init, min, max, step
  		startFrameSpinner.setModel(model);
  		int end = selectedScene.endframe==Integer.MAX_VALUE? last: selectedScene.endframe;
  		model = new SpinnerNumberModel(end, selectedScene.startframe, last, 1); // init, min, max, step
  		endFrameSpinner.setModel(model);
  		heavyCheckbox.setSelected(selectedScene.isHeavy());
  		drawer.color = selectedScene.getCaption().color;
			fontSizeSpinner.setValue(selectedScene.getCaption().getFont().getSize());
		}
		else {
    	heavyCheckbox.setSelected(false);
		}
		// refresh color buttons
		for (int i=0; i<colorButtons.length; i++) {
	    for (ColorButton b: colorButtons[i]) {
	    	b.setToolTipText(TrackerRes.getString("PencilControlDialog.Button.Color.Tooltip")); //$NON-NLS-1$
	    	b.setBorder(b.color==drawer.color && enabled? BorderFactory.createLineBorder(Color.GRAY, 2):
	    		BorderFactory.createLineBorder(lightgrey, 2));
	    	b.icon.setColor(enabled? b.color: Color.LIGHT_GRAY);
	    	b.setEnabled(enabled);
	    }
		}
		// refresh scene dropdown
    sceneDropdown.removeAllItems();
    for (PencilScene scene: drawer.scenes) {
    	sceneDropdown.addItem(scene);
    }
    if (selectedScene!=null) {
      refreshing = false;
    	sceneDropdown.setSelectedItem(selectedScene);
    }
    else if (!drawer.scenes.isEmpty()) {
     	sceneDropdown.addItem(dummyScene);
    	sceneDropdown.setSelectedItem(dummyScene);
    }
		refreshing = false;
		pack();
    repaint();
	}
	
  /**
   * Repaints the canvas.
   */
	protected void repaintCanvas() {
		selectedScene.measure();
		canvas.setPreferredMinMaxX(selectedScene.getXMin(), selectedScene.getXMax());
		// y min/max are purposely reversed in following line
		canvas.setPreferredMinMaxY(selectedScene.getYMax(), selectedScene.getYMin());
		canvas.repaint();
	}
	
	@Override
	public void setVisible(boolean vis) {
		super.setVisible(vis);
		TToolBar toolbar = TToolBar.getToolbar(trackerPanel);
		toolbar.drawingButton.setSelected(vis);
    if (Tracker.showHints) {
  		trackerPanel.setMessage(vis? TrackerRes.getString("PencilDrawer.Hint"): null); //$NON-NLS-1$
    }
    isVisible = vis;
	}
	
  /**
   * Gets the selected scene. May return null.
   * 
   * @return the selected scene
   */
	protected PencilScene getSelectedScene() {
		return selectedScene;
	}
	
  /**
   * Sets the selected scene.
   * 
   * @param scene the scene to select. May be null.
   */
	protected void setSelectedScene(PencilScene scene) {
		if (selectedScene==scene) return;
		if (selectedScene!=null) canvas.removeDrawable(selectedScene);
		selectedScene = scene;
		if (selectedScene!=null) {
			canvas.addDrawable(selectedScene);
			prevCaptionText = selectedScene.getCaption().getText();
			captionField.setText(selectedScene.getCaption().getText());
		}
		else {
			captionField.setText(null);			
		}
		refreshGUI();
	}
	
  /**
   * Goes to a specified scene.
   * 
   * @param scene the scene
   */
	private void goToScene(PencilScene scene) {
		if (scene==null) return;
		if (!scene.includesFrame(trackerPanel.getFrameNumber())) {
  		// set step number to show scene
			int stepNum = trackerPanel.getPlayer().getVideoClip().frameToStep(scene.startframe);
			trackerPanel.getPlayer().setStepNumber(stepNum);
		}
	}
	
	@Override
  public void dispose() {
		trackerPanel.removePropertyChangeListener("stepnumber", stepListener); //$NON-NLS-1$
		trackerPanel.removePropertyChangeListener("stepcount", clipListener); //$NON-NLS-1$
    TFrame frame = trackerPanel.getTFrame();
    if (frame != null) {
      frame.removePropertyChangeListener("tab", tabListener); //$NON-NLS-1$
    }
		setSelectedScene(null);
  }
	
  /**
   * A button inner class to manage pencil colors.
   */
  private class ColorButton extends JButton implements ActionListener {
  	
  	Color color;
  	ColorIcon icon;
  	
	  /**
	   * Constructor
	   * 
	   * @param c the color
	   */
  	private ColorButton(Color c) {
  		color = c;
  		setBackground(color);
  		icon = new ColorIcon(color, buttonWidth-4, 16);
  		setIcon(new ResizableIcon(icon));
  		addActionListener(this);
      addMouseListener(new MouseAdapter() {
      	@Override
      	public void mouseEntered(MouseEvent e) {
      		if (isEnabled()) setBorderPainted(false);
      	}

      	@Override
      	public void mouseExited(MouseEvent e) {
      		setBorderPainted(true);
      	}
      });
  	}
  	
		@Override
		public void actionPerformed(ActionEvent e) {
			if (drawer.color.equals(color)) return;
			drawer.color = color;
			if (drawer.getSelectedScene()!=null) {
				drawer.getSelectedScene().setColor(color);
				trackerPanel.repaint();
			}
			refreshGUI();
      trackerPanel.setMouseCursor(drawer.getPencilCursor());
  		setBorderPainted(true);
		}
  	
  }

  /**
   * Custom renderer for scene dropdown. List items are PencilScenes.
   */
  private class SceneDropdownRenderer extends JLabel implements ListCellRenderer {

	  /**
	   * Private constructor
	   */
  	private SceneDropdownRenderer() {
      setOpaque(true);
      setHorizontalAlignment(LEFT);
      setVerticalAlignment(CENTER);
      setBorder(BorderFactory.createEmptyBorder(1, 4, 1, 0));
    }
  	
  	@Override
    public Component getListCellRendererComponent(JList list,
                                                  Object value,
                                                  int index,
                                                  boolean isSelected,
                                                  boolean cellHasFocus) {
      if (isSelected) {
        setBackground(list.getSelectionBackground());
        setForeground(list.getSelectionForeground());
      } else {
        setBackground(list.getBackground());
        setForeground(list.getForeground());
      }
      if (value!=null) {
        PencilScene scene = (PencilScene)value;
      	setText(scene.getDescription(trackerPanel));
      }
      else {
        setText(""); //$NON-NLS-1$
      }
      return this;
    }
  }
  
  /**
   * A class to undo/redo a pencil drawing for a PencilScene.
   */
  private class DrawingEdit extends AbstractUndoableEdit {
  	PencilDrawing drawing;
  	PencilScene scene;

    /**
     * Constructor.
     * 
     * @param drawing the drawing
     * @param scene the scene
     */
    public DrawingEdit(PencilDrawing drawing, PencilScene scene) {
      this.drawing = drawing;
      this.scene = scene;
    }

    @Override
    public void undo() throws CannotUndoException {
      super.undo();
      scene.getDrawings().remove(drawing);
      setSelectedScene(scene);
      goToScene(scene);
      trackerPanel.repaint();
    }

    @Override
    public void redo() throws CannotUndoException {
      super.redo();
      scene.getDrawings().add(drawing);
      setSelectedScene(scene);
      goToScene(scene);
      trackerPanel.repaint();
    }

    @Override
    public String getUndoPresentationName() {
    	return TrackerRes.getString("PencilControlDialog.DrawingEdit.Undo.Text"); //$NON-NLS-1$
    }

    @Override
    public String getRedoPresentationName() {
    	return TrackerRes.getString("PencilControlDialog.DrawingEdit.Redo.Text"); //$NON-NLS-1$
    }

  }

  /**
   * A class to undo/redo a PencilScene deletion.
   */
  private class DeletionEdit extends AbstractUndoableEdit {
  	PencilScene scene;

    /**
     * Constructor.
     * 
     * @param scene the deleted scene
     */
    public DeletionEdit(PencilScene scene) {
      this.scene = scene;
    }

    @Override
    public void undo() throws CannotUndoException {
      super.undo();
      drawer.addScene(scene);
      goToScene(scene);
      setSelectedScene(scene);
    }

    @Override
    public void redo() throws CannotUndoException {
      super.redo();
      drawer.removeScene(scene);
			setSelectedScene(drawer.getSceneAtFrame(trackerPanel.getFrameNumber()));
    }
    
    @Override
    public String getUndoPresentationName() {
    	return TrackerRes.getString("PencilControlDialog.DeletionEdit.Undo.Text"); //$NON-NLS-1$
    }

    @Override
    public String getRedoPresentationName() {
    	return TrackerRes.getString("PencilControlDialog.DeletionEdit.Redo.Text"); //$NON-NLS-1$
    }

  }

  /**
   * A class to undo/redo clearing all PencilScenes.
   */
  private class ClearEdit extends AbstractUndoableEdit {
  	ArrayList<PencilScene> scenes = new ArrayList<PencilScene>();

    /**
     * Constructor.
     * 
     * @param scenes the cleared scenes
     */
    public ClearEdit(ArrayList<PencilScene> scenes) {
      this.scenes.addAll(scenes);
    }

    @Override
    public void undo() throws CannotUndoException {
      super.undo();
      drawer.setScenes(scenes);
      PencilScene scene = drawer.getSceneAtFrame(trackerPanel.getFrameNumber());
      if (scene!=null) {
      	setSelectedScene(scene);
      }
      else {
      	goToScene(scenes.get(0));
      }
  		trackerPanel.repaint();
    }

    @Override
    public void redo() throws CannotUndoException {
      super.redo();
      drawer.clearScenes();
    	setSelectedScene(null);
  		refreshGUI();
    }

    @Override
    public String getUndoPresentationName() {
    	return TrackerRes.getString("PencilControlDialog.ClearEdit.Undo.Text"); //$NON-NLS-1$
    }

    @Override
    public String getRedoPresentationName() {
    	return TrackerRes.getString("PencilControlDialog.ClearEdit.Redo.Text"); //$NON-NLS-1$
    }

  }
  
  /**
   * A class to undo/redo caption text changes.
   */
  private class CaptionEdit extends AbstractUndoableEdit {
  	String undoText, redoText;
  	PencilScene scene;

    /**
     * Constructor.
     * 
     * @param scene the scene with the original caption
     * @param newText the new text
     */
    public CaptionEdit(PencilScene scene, String oldText, String newText) {
    	this.scene = scene;
    	undoText = oldText;
    	redoText = newText;
    }

    @Override
    public void undo() throws CannotUndoException {
      super.undo();
      scene.getCaption().setText(undoText);
      setSelectedScene(scene);
      goToScene(scene);
      captionField.setText(undoText);
  		trackerPanel.repaint();
  		repaintCanvas();
    }

    @Override
    public void redo() throws CannotUndoException {
      super.redo();     
      scene.getCaption().setText(redoText);
      setSelectedScene(scene);
      goToScene(scene);
      captionField.setText(redoText);
  		trackerPanel.repaint();
  		repaintCanvas();
    }

    @Override
    public String getUndoPresentationName() {
    	return TrackerRes.getString("PencilControlDialog.CaptionEdit.Undo.Text"); //$NON-NLS-1$
    }

    @Override
    public String getRedoPresentationName() {
    	return TrackerRes.getString("PencilControlDialog.CaptionEdit.Redo.Text"); //$NON-NLS-1$
    }

  }

}