package org.yamcs.ui.archivebrowser;
import org.yamcs.utils.TaiUtcConverter.DateTimeComponents;
import org.yamcs.utils.TimeEncoding;
import javax.swing.*;
import javax.swing.plaf.basic.BasicSliderUI;
import java.awt.*;
import java.text.SimpleDateFormat;
import java.util.*;
public class TMScale extends JSlider {
private static final long serialVersionUID = 1L;
final SimpleDateFormat f_yyyy_MMM = sdfFactory("yyyy\nMMM");
final SimpleDateFormat f_MMM = sdfFactory("MMM");
final SimpleDateFormat f_yyyy_MM_dd = sdfFactory("yyyy.MM\ndd");
final SimpleDateFormat f_dd = sdfFactory("dd");
final SimpleDateFormat f_DDD = sdfFactory("DDD");
final SimpleDateFormat f_yyyy_DDD = sdfFactory("yyyy\nDDD");
final SimpleDateFormat f_yyyy_DDD_HH = sdfFactory("yyyy/DDD\nHH");
final SimpleDateFormat f_yyyy_MM_dd_HH = sdfFactory("yyyy.MM.dd/DDD\nHH");
final SimpleDateFormat f_yyyy_DDD_HH_mm = sdfFactory("yyyy/DDD\nHH:mm");
final SimpleDateFormat f_yyyy_MM_dd_HH_mm = sdfFactory("yyyy.MM.dd/DDD\nHH:mm");
final SimpleDateFormat f_HH = sdfFactory("HH");
final SimpleDateFormat f_HH_mm = sdfFactory("HH:mm");
final SimpleDateFormat f_mm = sdfFactory("mm");
static SimpleDateFormat sdfFactory(String format) {
SimpleDateFormat sdf=new SimpleDateFormat(format);
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
return sdf;
}
Hashtable<Integer, JComponent> labels = new Hashtable<Integer, JComponent>();
ZoomSpec zoom;
long div;
public TMScale() {
super(JSlider.HORIZONTAL, 0, 100, 100);
setUI(new TMScaleUI(this));
setBackground(Color.LIGHT_GRAY);
setPaintTicks(true);
setPaintLabels(true);
setPaintTrack(false);
setFocusable(false);
double wantedHeight = (new JLabel("x").getPreferredSize().getHeight()*2);
setPreferredSize(new Dimension(getPreferredSize().width, (int) wantedHeight));
setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.BLACK));
/*final TMScale me = this;
addComponentListener(new java.awt.event.ComponentAdapter() {
public void componentResized(ComponentEvent e) {
debugLogComponent("scale resized", me);
}
});*/
}
private void addLabel(long instant, String labelString) {
JComponent lab= new TwoLineLabel(this, labelString);
labels.put(Integer.valueOf((int)((instant-zoom.startInstant) / div)), lab);
}
void setToZoom(ZoomSpec zoom ) {
this.zoom=zoom;
if((zoom.stopInstant - zoom.startInstant)>1000l*3600*24*365*100) {
throw new RuntimeException("Cannot show more than 100 years");
}
if(zoom.stopInstant-zoom.startInstant>(0x0FFFFFFFL)) {
div=1000;
} else {
div=1;
}
labels.clear();
SimpleDateFormat f;
Calendar cal = getTruncatedCal(zoom.stopInstant);
long instant;
long measure = (long)(500 * zoom.pixelRatio / 1000); // number of seconds covered by 500 pixels
//debugLog("setToZoom: measure " + measure + " ratio " + zoom.pixelRatio + " viewTimeWindow " + zoom.viewTimeWindow);
if ( measure > 1 * 365 * 86400 ) {
// the view is showing more than 2 years
// align calendar to 1st of month, 00:00:00
cal.set(Calendar.DAY_OF_MONTH, 1);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
for ( ; (instant=TimeEncoding.fromCalendar(cal))>zoom.startInstant ; cal.add(Calendar.MONTH, -1) ) {
if ( cal.get(Calendar.MONTH) == 0 ) {
f = f_yyyy_MMM;
} else if ( cal.get(Calendar.MONTH) % 3 == 0 ) {
f = f_MMM;
} else {
continue;
}
addLabel(instant, f.format(cal.getTime()));
}
} else if ( measure > 180 * 86400 ) {
// the view is showing more than 6 months
// draw 1 month per major tick
// align calendar to 1st of month, 00:00:00
cal.set(Calendar.DAY_OF_MONTH, 1);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
for ( ; (instant=TimeEncoding.fromCalendar(cal))>zoom.startInstant ; cal.add(Calendar.MONTH, -1) ) {
if ( cal.get(Calendar.MONTH) == 0 ) {
f = f_yyyy_MMM;
} else {
f = f_MMM;
}
addLabel(instant, f.format(cal.getTime()));
}
} else if ( measure > 30 * 86400 ) {
// the view is showing more than 30 days
// draw 1 week per major tick, and the year/month for the first week of each month
int d;
// align calendar to Monday, 00:00:00
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
d = cal.get(Calendar.DAY_OF_MONTH);
d = d >= 22 ? 22 : (d >= 15 ? 15 : (d >= 8 ? 8 : 1));
cal.set(Calendar.DAY_OF_MONTH, d);
for ( ; (instant=TimeEncoding.fromCalendar(cal))>zoom.startInstant; ) {
if (d == 1) {
f = f_yyyy_MM_dd;
} else {
f = f_dd;
}
addLabel(instant, f.format(cal.getTime()));
if (d == 1) {
cal.add(Calendar.DAY_OF_YEAR, -1);
d = 22;
} else {
d -= 7;
}
cal.set(Calendar.DAY_OF_MONTH, d);
}
} else if ( measure > 8 * 86400 ) {
// the view is showing more than 8 days
// draw 1 day per major tick, and DoY 001 + every 10th day displays the year
int doy;
// align calendar to 00:00:00
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
for ( ; (instant=TimeEncoding.fromCalendar(cal))>zoom.startInstant ; cal.add(Calendar.DAY_OF_YEAR, -1) ) {
if ( cal.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY ) {
f = f_yyyy_MM_dd;
} else {
f = f_dd;
}
addLabel(instant, f.format(cal.getTime()));
}
} else if ( measure > 86400 ) {
// the view is showing more than 1 day
// draw 1 day per major tick, and every day contains the year
// align calendar to 00:00:00
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
for ( ; (instant=TimeEncoding.fromCalendar(cal))>zoom.startInstant ; cal.add(Calendar.DAY_OF_YEAR, -1) ) {
f = f_yyyy_MM_dd;
addLabel(instant, f.format(cal.getTime()));
}
} else if ( measure > 3 * 3600 ) {
// the view is showing 3-24 hours
// draw 1 hour per major tick, and midnight shows the DoY + year
cal.set(Calendar.MINUTE, 0);
for ( ; (instant=TimeEncoding.fromCalendar(cal))>zoom.startInstant ; cal.add(Calendar.HOUR_OF_DAY, -1) ) {
if ( cal.get(Calendar.HOUR) == 0 ) {
f = f_yyyy_MM_dd_HH;
} else {
f = f_HH;
}
addLabel(instant, f.format(cal.getTime()));
}
} else if ( measure > 600 ) {
// the view is showing >10 minutes
// draw 15 minutes per major tick, and every hour shows the DoY + year
int m = cal.get(Calendar.MINUTE);
cal.set(Calendar.MINUTE, m - m % 15); // rounding down to quarter of an hour
for ( ; (instant=TimeEncoding.fromCalendar(cal))>zoom.startInstant ; cal.add(Calendar.MINUTE, -15) ) {
if ( cal.get(Calendar.MINUTE) == 0 ) {
f = f_yyyy_MM_dd_HH;
} else {
f = f_HH_mm;
}
addLabel( instant, f.format(cal.getTime()));
}
} else {
// (highest resolution)
// the view is showing <10 minutes
// draw 1 minute per major tick, and every 10th shows the DoY + year
for ( ; (instant=TimeEncoding.fromCalendar(cal))>zoom.startInstant ; cal.add(Calendar.MINUTE, -1) ) {
if ( cal.get(Calendar.MINUTE) % 10 == 0 ) {
f = f_yyyy_MM_dd_HH_mm;
} else {
f = f_HH_mm;
}
addLabel(instant, f.format(cal.getTime()));
}
}
setLabelTable(labels);
setMinimum(0);
setMaximum((int)((zoom.stopInstant-zoom.startInstant) / div));
updateFontSize();
}
private Font getLabelFont() {
return getFont().deriveFont(getFont().getSize2D()-2);
}
/**
*
* @param instant
* @return a calendar with the seconds set to 0, suitable for "gross" operations
*/
static Calendar getTruncatedCal(long instant) {
DateTimeComponents dtc = TimeEncoding.toUtc(instant);
Calendar cal=Calendar.getInstance(TimeZone.getTimeZone("UTC"));
cal.set(dtc.year, dtc.month-1,dtc.day, dtc.hour, dtc.minute, 0);
cal.set(Calendar.MILLISECOND, 0);
cal.setFirstDayOfWeek(Calendar.MONDAY);
return cal;
}
static class TwoLineLabel extends JLabel {
private static final long serialVersionUID = 1L;
int lineHeight;
String[] textlines;
TwoLineLabel(TMScale scale, String text ) {
super(text);
//setForeground(UiColors.BORDER_COLOR);
setFont(scale.getLabelFont());
lineHeight = getPreferredSize().height;
textlines = text.toUpperCase().split("\\n");
if ( textlines.length > 1 ) {
setText(textlines[0]);
int w0 = getPreferredSize().width;
setText(textlines[1]);
int w1 = getPreferredSize().width;
int newWidth = Math.max(w0, w1);
setPreferredSize(new Dimension(newWidth, lineHeight * 2));
} else {
setText(textlines[0]);
setPreferredSize(new Dimension(getPreferredSize().width, lineHeight * 2));
}
setVerticalAlignment(SwingConstants.TOP);
setHorizontalAlignment(SwingConstants.CENTER);
}
@Override
public void paint( Graphics g ) {
if ( textlines.length > 1 ) {
setText(textlines[0]);
super.paint(g);
setText(textlines[1]);
g.translate(0, lineHeight);
super.paint(g);
g.translate(0, -lineHeight);
} else {
g.translate(0, lineHeight);
super.paint(g);
g.translate(0, -lineHeight);
}
}
}
/**
* Tricky to change JSlider font size
* See http://nadeausoftware.com/articles/2009/04/mac_java_tip_how_customize_aqua_sliders
*/
public void updateFontSize() {
for(JComponent comp:labels.values()) {
JLabel lbl = (JLabel) comp;
Font smallerFont = getLabelFont();
lbl.setFont(smallerFont);
lbl.setSize(lbl.getPreferredSize());
}
}
static class TMScaleUI extends BasicSliderUI {
// this UI class makes the thumb disappear, and it places the labels above the ticks
TMScaleUI(JSlider slider) {
super(slider);
}
@Override
protected Dimension getThumbSize() {
if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
return new Dimension(0, 0); // completely disable the thumb
}
return super.getThumbSize();
}
@Override
protected void calculateTickRect() {
// invoked before calculateLabelRect() so we cannot use labelRect.height
super.calculateTickRect();
if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
tickRect.y += getHeightOfTallestLabel();
}
}
@Override
protected void calculateLabelRect() {
super.calculateLabelRect();
if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
labelRect.y = trackRect.y + trackRect.height;
}
}
@Override
protected void calculateTrackBuffer() {
trackBuffer = 0;
}
@Override
public void paintTicks(Graphics g){
if ( slider.getOrientation() == JSlider.HORIZONTAL ) {
Dictionary dict = slider.getLabelTable();
if ( dict != null ) {
g.setColor(Color.BLACK);
g.translate(0, tickRect.y);
for ( Enumeration e = dict.keys(); e.hasMoreElements(); ) {
Integer tsint = (Integer)(e.nextElement());
int ts = tsint.intValue();
int labelCenter = xPositionForValue(ts);
paintMajorTickForHorizSlider(g, tickRect, labelCenter);
}
g.translate(0, -tickRect.y);
}
} else {
super.paintTicks(g);
}
}
@Override
protected int getTickLength() {
return 6;
}
@Override
protected int getHeightOfHighValueLabel() {
return super.getHeightOfHighValueLabel() - 3;
}
@Override
protected int getHeightOfLowValueLabel() {
return super.getHeightOfLowValueLabel() - 3;
}
@Override
protected void installListeners(JSlider jslider) {
// disable mouse listeners
super.installListeners(jslider);
jslider.removeMouseListener(trackListener);
jslider.removeMouseMotionListener(trackListener);
}
}
}