package glug.gui.timebar;
import static glug.gui.timebar.Tick.tick;
import static java.awt.Color.WHITE;
import static java.awt.Font.PLAIN;
import static java.awt.Font.SANS_SERIF;
import static java.awt.RenderingHints.KEY_ANTIALIASING;
import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON;
import static java.lang.Math.exp;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.Math.pow;
import static java.lang.Math.round;
import static org.joda.time.DateTimeFieldType.dayOfMonth;
import static org.joda.time.DateTimeFieldType.hourOfDay;
import static org.joda.time.DateTimeFieldType.millisOfSecond;
import static org.joda.time.DateTimeFieldType.minuteOfHour;
import static org.joda.time.DateTimeFieldType.monthOfYear;
import static org.joda.time.DateTimeFieldType.secondOfMinute;
import static org.joda.time.DateTimeFieldType.yearOfCentury;
import static org.joda.time.format.DateTimeFormat.forPattern;
import glug.gui.UITimeScale;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.font.TextLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Map.Entry;
import javax.swing.JComponent;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Duration;
import org.joda.time.Interval;
public class TimelineDateTimeComponent extends JComponent {
int minTickPixelSpacing = 3;
int maxTickPixelSpacing = 160;
private TickSet availableTicks = new TickSet(
tick(1,yearOfCentury(),forPattern("YYYY")),
tick(1,monthOfYear(),forPattern("YYYY-MM")),
tick(1,dayOfMonth(),forPattern("YYYY-MM-dd")),
tick(4,hourOfDay(),forPattern("YYYY-MM-dd HH:mm")), tick(1,hourOfDay(),forPattern("HH:mm")),
tick(10,minuteOfHour(),forPattern("HH:mm")), tick(5,minuteOfHour(),forPattern("HH:mm")), tick(1,minuteOfHour(),forPattern("HH:mm")),
tick(10,secondOfMinute(),forPattern("HH:mm:ss")), tick(5,secondOfMinute(),forPattern("HH:mm:ss")), tick(1,secondOfMinute(),forPattern("HH:mm:ss")),
tick(100,millisOfSecond(),forPattern("HH:mm:ss.S")),tick(10,millisOfSecond(),forPattern("HH:mm:ss.SS")),tick(1,millisOfSecond(),forPattern("HH:mm:ss.SSS")))
;
private static final long serialVersionUID = 1L;
private final UITimeScale timeScale;
public TimelineDateTimeComponent(UITimeScale timeScale) {
this.timeScale = timeScale;
setSize(getPreferredSize());
setDoubleBuffered(true);
setBackground(WHITE);
setOpaque(true);
timeScale.addChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("millisecondsPerPixel")) {
repaint();
}
}
});
}
public void setTimeZone(DateTimeZone dateTimeZone) {
availableTicks = availableTicks.with(dateTimeZone);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(Integer.MAX_VALUE>>>2, 32);
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D graphics2D = (Graphics2D) g;
Rectangle clipBounds = graphics2D.getClipBounds();
fillBackgroundOf(clipBounds, graphics2D);
Interval visibleInterval = timeScale.viewToModel(clipBounds);
paintTicksFor(visibleInterval, graphics2D);
}
private void fillBackgroundOf(Rectangle clipBounds, Graphics2D graphics2D) {
graphics2D.setColor(getBackground());
graphics2D.fill(clipBounds);
}
private void paintTicksFor(Interval visibleInterval, Graphics2D graphics2D) {
NavigableMap<Duration, Tick> availableTicks = tickIntervalsAtCurrentScale();
Map<DateTime, Tick> tickMap = new HashMap<DateTime, Tick>();
Map<Tick, Float> tickWeight = new HashMap<Tick, Float>();
for (Tick tick : availableTicks.values()) {
int pixelsForTickDuration = timeScale.modelDurationToViewPixels(tick.getInterval().getDuration());
float neif =(pixelsForTickDuration - minTickPixelSpacing)/(maxTickPixelSpacing*1.2f - minTickPixelSpacing);
float proportionOfRange = max(0,min(1,neif));
if (tick.getInterval().getValue()==1) {
proportionOfRange=(float) pow(proportionOfRange, 0.7);
}
tickWeight.put(tick, proportionOfRange);
Iterator<DateTime> tickIterator = tick.getInterval().ticksFor(visibleInterval);
while (tickIterator.hasNext()) {
DateTime tickDateTime = tickIterator.next();
tickMap.put(tickDateTime, tick);
}
}
paintTicksFor(tickMap, tickWeight, graphics2D);
}
private void paintTicksFor(Map<DateTime, Tick> tickMap,
Map<Tick, Float> tickWeight, Graphics2D graphics2D) {
int tickHeight;
int bottom = getHeight()-1;
graphics2D.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
Font baseFont = new Font(SANS_SERIF, PLAIN,16);
for (Entry<DateTime, Tick> entry : tickMap.entrySet()) {
Tick tick = entry.getValue();
float proportionOfRange = tickWeight.get(tick);
int col=round(255*(1f-proportionOfRange));
graphics2D.setColor(new Color(col,col,col));
float punchSize = (float) (16f*exp(proportionOfRange-1));
tickHeight = (int) round(punchSize);
DateTime tickDateTime = entry.getKey();
int graphicsX = timeScale.modelToView(tickDateTime.toInstant());
graphics2D.drawLine(graphicsX, bottom, graphicsX, bottom-tickHeight);
if (proportionOfRange>0.3) {
Font tickFont = baseFont.deriveFont(punchSize-1);
graphics2D.setFont(tickFont);
TextLayout textLayout = new TextLayout(tick.format(tickDateTime),tickFont,graphics2D.getFontRenderContext());
textLayout.draw(graphics2D, (float)(-(textLayout.getBounds().getWidth()/2)+graphicsX),(float) bottom-tickHeight-1);
}
}
}
private NavigableMap<Duration, Tick> tickIntervalsAtCurrentScale() {
Duration approxGoodMinorTickDuration = timeScale.viewPixelsToModelDuration(minTickPixelSpacing);
Duration approxGoodMajorTickDuration = timeScale.viewPixelsToModelDuration(maxTickPixelSpacing);
return availableTicks.forRange(approxGoodMinorTickDuration,approxGoodMajorTickDuration);
}
}