package smovielib;
import java.awt.Dimension;
import java.awt.ScrollPane;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import javax.swing.JFrame;
import acm.graphics.GCanvas;
import acm.graphics.GCompound;
import acm.graphics.GImage;
import acm.graphics.GObject;
import acm.graphics.GPoint;
/**
* This class represents the traditional movie editing "timeline" view.
*
*
* NOTE: Although the code is provided, we don't expect you to modify it,
* nor will we test you on it. It's there so that you can extend it
* if you're looking to do extensions, or examine it if you're idly
* wondering what's going on behind the scenes. It probably won't
* give you any insight if you're having trouble with the base
* assignment.
*
*/
public class Timeline implements MouseListener, MouseMotionListener {
private GCanvas canvas;
// Don't worry about the TimelineProgram - it's just an implementation detail
private TimelineProgram tp;
// Used to go from models to views efficiently
private HashMap <Clip, GTimelineElement> modelToViewMap
= new HashMap<Clip, GTimelineElement>();
// Keeps track of the currently selected element
private GTimelineElement selectedElement;
// Keeps track of the state for mouse dragging events
private GCompound selectedHandle;
private boolean isLeftHandle;
private double startingMouseX;
private long delta;
// This constructor is intentionally not visible from your program.
// A Timeline is set up for you in TimelineProgram, and is
// accessible through getTimeline()
Timeline(GCanvas canvas, TimelineProgram tp) {
this.canvas = canvas;
this.tp = tp;
// Equivalent to addMouseListeners() in professional Java-land
canvas.addMouseListener(this);
canvas.addMouseMotionListener(this);
}
/**
* Adds the given clip to the timeline.
*
* @param clip The clip to add
*/
public void addClip(Clip clip) {
modelToViewMap.put(clip, new GTimelineElement(clip));
canvas.add(modelToViewMap.get(clip));
tp.repaint();
}
/**
* Removes the given clip from the timeline.
*
* @param clip The clip to remove
*/
public void removeClip(Clip clip) {
canvas.remove(modelToViewMap.get(clip));
tp.repaint();
}
/**
* Signals to the timeline that the clip has been modified.
* This method should be called each time the clip is modified
* in order to ensure that the view is synchronized.
*
* @param clip The modified clip
*/
public void clipModified(Clip clip) {
modelToViewMap.get(clip).modelChanged();
tp.repaint();
}
/**
* Returns the currently selected clip.
* If no clip is currently selected, then this method returns null.
*
* @return The currently selected clip or null.
*/
public Clip getSelectedClip() {
if (selectedElement == null) {
return null;
}
return selectedElement.getClip();
}
/**
* Updates a selection internally.
*
* @param elem the selected parameter.
*/
private void select(GTimelineElement elem) {
if (selectedElement != null) {
selectedElement.setSelected(false);
}
selectedElement = elem;
selectedElement.setSelected(true);
}
public void mouseReleased(MouseEvent me) {
delta = convertXToFrame(me.getX()) - convertXToFrame(startingMouseX);
if (selectedHandle == null) {
return;
}
for (TimelineListener listener : listeners) {
if (isLeftHandle) {
listener.clipMoved(selectedElement.getClip(), delta);
} else {
listener.clipResized(selectedElement.getClip(), delta);
}
}
selectedHandle = null;
tp.repaint();
}
// Never used, but we have to declare them because we're implementing
// MouseListener
public void mouseClicked(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mouseDragged(MouseEvent me) {}
// Used to keep track of classes that are interested in receiving timeline
// events.
// Look up the observer pattern if you're interested in learning more.
private HashSet<TimelineListener> listeners
= new HashSet<TimelineListener>();
/**
* Adds a TimelineListener to be notified of events from this timeline.
*
* @param tsl The listener to add
*/
public void addListener(TimelineListener tsl) {
listeners.add(tsl);
}
/**
* Removes a TimelineListener from the list notified by this timeline.
*
* @param tsl The listener to remove
*/
public void removeListener(TimelineListener tsl) {
if (listeners.contains(tsl)) listeners.remove(tsl);
}
/**
* Converts from screen coordinates to timeline coordinates.
*/
private int convertXToFrame(double x) {
return (int) (x * GTimelineElement.FRAMES_PER_PIXEL);
}
/**
* Converts from screen coordinates to timeline coordinates.
*/
private int convertYToOffset(double y) {
return (int) (y / GTimelineElement.ELEMENT_HEIGHT);
}
/**
* Displays the given PixelMatrix in a pop-up window.
*
* @param pm The image to display
* @param title Title for the window
*/
public void showPreview(PixelMatrix pm, String title) {
GImage previewImage = new GImage(pm.getImage());
GCanvas previewCanvas = new GCanvas();
previewCanvas.add(previewImage);
// Creates a new window for the previewImage. You
// aren't expected to know this for this course, but it
// might be useful later on.
JFrame previewFrame = new JFrame(title);
previewFrame.setContentPane(previewCanvas);
previewFrame.getContentPane().setPreferredSize(
new Dimension((int)previewImage.getWidth(),
(int)previewImage.getHeight()));
// pack causes it to lay itself out
previewFrame.pack();
previewFrame.setVisible(true);
}
public void mousePressed(MouseEvent e) {
int frame = convertXToFrame(e.getX());
int offset = convertYToOffset(e.getY());
for (TimelineListener listener : listeners) {
listener.pointSelected(frame, offset);
}
GObject elem = canvas.getElementAt(e.getX(), e.getY());
// If we've clicked on a timeline element...
if (elem instanceof GTimelineElement) {
GTimelineElement timelineElem = (GTimelineElement) elem;
GPoint location = timelineElem.getLocalPoint(e.getX(), e.getY());
GObject interiorElement = timelineElem.getElementAt(location);
// If it's a handle...
if (interiorElement == timelineElem.getLeftHandle()) {
selectedHandle = timelineElem.getLeftHandle();
isLeftHandle = true;
} else if (interiorElement == timelineElem.getRightHandle()){
selectedHandle = timelineElem.getRightHandle();
isLeftHandle = false;
} else {
selectedHandle = null;
}
startingMouseX = e.getX();
select(timelineElem);
}
tp.repaint();
}
public void mouseMoved(MouseEvent e) {}
}