package glug.gui.timelinecursor;
import static java.awt.Color.BLACK;
import glug.gui.TimelineComponent;
import glug.model.time.LogInstant;
import glug.model.time.LogInterval;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.EventListenerList;
/**
* This class is modelled a little after javax.swing.text.Caret, and uses some
* of it's terminology.
*
* The cursor has a position in the timeline referred to as a 'dot'. The dot is
* where the cursor is currently located in the model. There is a second
* position maintained by the cursor that represents the other end of a
* selection called 'mark'. If there is no selection the dot and mark will be
* equal. If a selection exists, the two values will be different.
* <p>
* The dot can be placed by either calling <code>setDot</code> or
* <code>moveDot</code>. Setting the dot has the effect of removing any
* selection that may have previously existed. The dot and mark will be equal.
* Moving the dot has the effect of creating a selection as the mark is left at
* whatever position it previously had.
*
* @see javax.swing.text.Caret
*/
public class TimelineCursor {
protected EventListenerList listenerList = new EventListenerList();
private LogInstant dot, mark;
public void install(TimelineComponent c) {
TimelineCursorMouseInputListener timelineCursorMouseInputListener = new TimelineCursorMouseInputListener(c);
c.addMouseListener(timelineCursorMouseInputListener);
c.addMouseMotionListener(timelineCursorMouseInputListener);
addChangeListener(c);
}
public void deinstall(TimelineComponent c) {
}
State getState() {
return new State(dot,mark);
}
public LogInstant getDot() {
return dot;
}
public LogInstant getMark() {
return mark;
}
/**
* Sets the cursor position and mark to the specified instant. This
* implicitly sets the selection range to zero.
*/
public void setDot(LogInstant newDot) {
State oldState = getState();
dot = mark = newDot;
fireEventIfStateChangedFrom(oldState);
}
/**
* Moves the cursor position to the specified instant.
*/
public void moveDot(LogInstant newDot) {
State oldState = getState();
dot = newDot;
fireEventIfStateChangedFrom(oldState);
}
private void fireEventIfStateChangedFrom(State oldState) {
State newState = getState();
if (newState.differsWith(oldState)) {
// notify listeners that the cursor moved - note they are responsible for invalidating the old areas of the component, etc
CursorPositionChanged cursorPositionChanged = new CursorPositionChanged(oldState, newState);
//System.out.println("Cursor move "+this.dot + " "+ newDot);
fireStateChanged(cursorPositionChanged);
}
}
protected void fireStateChanged(CursorPositionChanged cursorPositionChanged) {
ChangeEvent changeEvent = new ChangeEvent(cursorPositionChanged);
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == ChangeListener.class) {
((ChangeListener) listeners[i + 1]).stateChanged(changeEvent);
}
}
}
public Rectangle getBoundsForCursorAt(LogInstant logInstant, TimelineComponent timelineComponent) {
Rectangle bounds = timelineComponent.getViewFor(logInstant);
bounds.grow(1, 1);
return bounds;
}
public Rectangle getBoundsForHighlightedInterval(
LogInterval logInterval,
TimelineComponent timelineComponent) {
Rectangle bounds = timelineComponent.getViewFor(logInterval);
bounds.grow(1, 1);
return bounds;
}
public void paintHighlightOn(TimelineComponent timelineComponent, Graphics2D g) {
LogInterval selectedInterval = getSelectedInterval();
if (selectedInterval != null) {
g.setColor(UIManager.getColor("TextArea.selectionBackground"));
g.fill(timelineComponent.getViewFor(selectedInterval));
}
}
public void paintCursorOn(TimelineComponent timelineComponent, Graphics2D g) {
if (dot != null) {
g.setColor(BLACK);
g.draw(timelineComponent.getViewFor(dot));
}
}
public void addChangeListener(ChangeListener l) {
listenerList.add(ChangeListener.class, l);
}
public void removeChangeListener(ChangeListener l) {
listenerList.remove(ChangeListener.class, l);
}
public static class CursorPositionChanged {
private final State oldState, newState;
public CursorPositionChanged(State oldState, State newState) {
this.oldState = oldState;
this.newState = newState;
}
public State getOldState() {
return oldState;
}
public State getNewState() {
return newState;
}
}
public void processCursorPositionChangedFor(TimelineComponent timelineComponent, CursorPositionChanged cursorPositionChanged) {
State oldState = cursorPositionChanged.getOldState();
if (oldState.getDot()!=null) {
timelineComponent.paintImmediately(getBoundsForCursorAt(oldState.getDot(), timelineComponent));
}
LogInterval currentlySelectedInterval = getSelectedInterval();
LogInterval oldSelectedInterval = oldState.getSelectedInterval();
System.out.println(" oldSelectedInterval:"+oldState.getSelectedInterval());
System.out.println("currentlySelectedInterval:"+currentlySelectedInterval);
LogInterval intervalContainingDifferences = LogInterval.intervalContainingDeltaFor(oldSelectedInterval, currentlySelectedInterval);
System.out.println("intervalContainingDifferences="+intervalContainingDifferences);
if (intervalContainingDifferences!=null) {
timelineComponent.repaint(getBoundsForHighlightedInterval(intervalContainingDifferences, timelineComponent));
}
timelineComponent.repaint(getBoundsForCursorAt(getDot(), timelineComponent));
}
public LogInterval getSelectedInterval() {
return getState().getSelectedInterval();
}
public void setSelectedInterval(LogInterval logInterval) {
setDot(logInterval.getStart());
moveDot(logInterval.getEnd());
}
public static class State {
private LogInstant dot, mark;
public State(LogInstant dot, LogInstant mark) {
this.dot = dot;
this.mark = mark;
}
public boolean differsWith(State otherState) {
return !this.equals(otherState);
}
public LogInstant getDot() {
return dot;
}
LogInterval getSelectedInterval() {
if (dot==null || mark==null || dot.equals(mark)) {
return null;
}
return dot.isBefore(mark)?new LogInterval(dot,mark):new LogInterval(mark,dot);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((dot == null) ? 0 : dot.hashCode());
result = prime * result + ((mark == null) ? 0 : mark.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
State other = (State) obj;
if (dot == null) {
if (other.dot != null)
return false;
} else if (!dot.equals(other.dot))
return false;
if (mark == null) {
if (other.mark != null)
return false;
} else if (!mark.equals(other.mark))
return false;
return true;
}
}
}