/** Copyright 2015 Tim Engler, Rareventure LLC This file is part of Tiny Travel Tracker. Tiny Travel 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. Tiny Travel 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 Tiny Travel Tracker. If not, see <http://www.gnu.org/licenses/>. */ package com.rareventure.android.widget; import java.util.Calendar; import android.content.Context; import android.graphics.Canvas; import android.text.TextPaint; import com.rareventure.android.Util; import com.rareventure.android.AndroidPreferenceSet.AndroidPreferences; import com.rareventure.android.widget.dial.Strip; import com.rareventure.android.widget.dial.TextStrip; //TODO 3: what about other languages? public class TimePosDial extends RangeDial implements Strip.DataGenerator { private static final String[] DAY_OF_WEEK = new String [] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; private String [] MONTHS = new String [] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; /** * The amount of step the years can be on the dial */ private static final int [] YEAR_FACTORS = {1,2,3, 5, 10, 20, 30, 50, 100, 200, 500, 1000}; /** * The amount of step the months can be on the dial up to the years */ private static final int[] MONTH_FACTORS = {1,2,3,6,9}; /** * The amount of step the days can be on the dial up to the months, etc etc etc */ private static final int[] DAY_OF_MONTH_FACTORS = {1,2,3,5,10,15}; private static final int[] DAY_OF_WEEK_FACTORS = {1,2,3,5,10,15}; private static final int[] MINUTE_FACTORS = {1,2,3,5,10,15,20,30,60,90,2*60,3*60,4*60,6*60,9*60,12*60, 18*60}; private TextPaint tp; private Preferences prefs = new Preferences(); private StripGroup[] stripGroups; private TextStrip yearStrip; private TextStrip monthStrip; private TextStrip dayOfMonthStrip; private TextStrip dayOfWeekStrip; private TextStrip minuteStrip; private Calendar calendar = Calendar.getInstance(); public TimePosDial(Context context) { super(context); tp = new TextPaint(); tp.setTextSize(Util.convertSpToPixel(prefs.textSize, context) ); tp.setColor(prefs.textColor ); } public void init(int [] colors, long startTicks, long endTicks, long initialTicks, long initialRange) { super.init(colors); setRangeSizeTicks(initialRange); super.setStartTicks(startTicks); super.setEndTicks(endTicks); setTicks(initialTicks); createStrips(); //create strip groups stripGroups = new StripGroup [1]; int i = 0; stripGroups[i++] = new StripGroup(yearStrip,monthStrip,dayOfMonthStrip,dayOfWeekStrip, minuteStrip); // stripGroups[i++] = new StripGroup(yearStrip,monthStrip,dayOfMonthStrip); // stripGroups[i++] = new StripGroup(yearStrip,monthStrip); // stripGroups[i++] = new StripGroup(yearStrip); } //TODO 3: What to do about year not always displaying when down to the second //strip group? In other words, the year, month, day, hour, and minute strips //may all be blank and the only strip with anything at all on it is the seconds private void createStrips() { int ml = Util.getTextLength(tp, "2000") + prefs.minPadPixels; long ticks = Util.MS_PER_YEAR * 100; ml = Util.getTextLength(tp, "2000") + prefs.minPadPixels; ticks = Util.MS_PER_YEAR - Util.MS_PER_DAY; yearStrip = new TextStrip(); yearStrip.init(tp, this, (double)ticks/ml); ml = Util.getTextLength(tp, "Feb") + prefs.minPadPixels; ticks = Util.MS_PER_DAY * 28; monthStrip = new TextStrip(); monthStrip.init(tp, this, (double)ticks/ml); ml = Util.getTextLength(tp, "30") + prefs.minPadPixels; ticks = Util.MS_PER_DAY - Util.MS_PER_HOUR; dayOfMonthStrip = new TextStrip(); dayOfMonthStrip.init(tp, this, (double)ticks/ml); ml = Util.getTextLength(tp, "Sun") + prefs.minPadPixels; ticks = Util.MS_PER_DAY - Util.MS_PER_HOUR; dayOfWeekStrip = new TextStrip(); dayOfWeekStrip.init(tp, this, (double)ticks/ml); ml = Util.getTextLength(tp, "20:00") + prefs.minPadPixels; ticks = 60*1000; minuteStrip = new TextStrip(); minuteStrip.init(tp, this, (double)ticks/ml); } @Override public void setData(Strip strip, Canvas canvas, long startTicks, long endTicks) { if(strip == yearStrip) { calendar.setTimeInMillis(startTicks); calendar.set(Calendar.MONTH,0); calendar.set(Calendar.DAY_OF_MONTH,1); calendar.set(Calendar.HOUR_OF_DAY,0); calendar.set(Calendar.MINUTE,0); calendar.set(Calendar.SECOND,0); calendar.set(Calendar.MILLISECOND,0); int labelStep = getAppropriateStep(yearStrip.getMinLabelStep(ticksPerPixel), YEAR_FACTORS); drawLabels(canvas, startTicks, endTicks, Calendar.YEAR, yearStrip, labelStep, calendar, new LabelDrawer() { public String createString(int value) { return String.valueOf(value); } }); } else if(strip == monthStrip) { calendar.setTimeInMillis(startTicks); calendar.set(Calendar.DAY_OF_MONTH,1); calendar.set(Calendar.HOUR_OF_DAY,0); calendar.set(Calendar.MINUTE,0); calendar.set(Calendar.SECOND,0); calendar.set(Calendar.MILLISECOND,0); int labelStep = getAppropriateStep(monthStrip.getMinLabelStep(ticksPerPixel), MONTH_FACTORS); drawLabels(canvas, startTicks, endTicks, Calendar.MONTH, monthStrip, labelStep, calendar, new LabelDrawer() { public String createString(int value) { return MONTHS[value - Calendar.JANUARY]; } }); } else if(strip == dayOfMonthStrip) { calendar.setTimeInMillis(startTicks); calendar.set(Calendar.HOUR_OF_DAY,0); calendar.set(Calendar.MINUTE,0); calendar.set(Calendar.SECOND,0); calendar.set(Calendar.MILLISECOND,0); int labelStep = getAppropriateStep(dayOfMonthStrip.getMinLabelStep(ticksPerPixel), DAY_OF_MONTH_FACTORS); drawLabels(canvas, startTicks, endTicks, Calendar.DAY_OF_MONTH, dayOfMonthStrip, labelStep, calendar, new LabelDrawer() { public String createString(int value) { return String.valueOf(value); } }); } else if(strip == dayOfWeekStrip) { calendar.setTimeInMillis(startTicks); calendar.set(Calendar.HOUR_OF_DAY,0); calendar.set(Calendar.MINUTE,0); calendar.set(Calendar.SECOND,0); calendar.set(Calendar.MILLISECOND,0); int labelStep = getAppropriateStep(dayOfWeekStrip.getMinLabelStep(ticksPerPixel), DAY_OF_WEEK_FACTORS); drawLabels(canvas, startTicks, endTicks, Calendar.DAY_OF_WEEK, dayOfWeekStrip, labelStep, calendar, new LabelDrawer() { public String createString(int value) { return DAY_OF_WEEK[value - Calendar.SUNDAY]; } }); } else if(strip == minuteStrip) { calendar.setTimeInMillis(startTicks); calendar.set(Calendar.MINUTE,0); calendar.set(Calendar.SECOND,0); calendar.set(Calendar.MILLISECOND,0); int labelStep = getAppropriateStep(minuteStrip.getMinLabelStep(ticksPerPixel), MINUTE_FACTORS); drawLabels(canvas, startTicks, endTicks, Calendar.MINUTE, minuteStrip, labelStep, calendar, new LabelDrawer() { public String createString(int value) { return String.format("%02d:%02d", value / 60, value % 60); } }); } } private void drawLabels(Canvas canvas, long startTicks, long endTicks, int calendarTimeType, TextStrip strip, int labelStep, Calendar calendar, LabelDrawer labelDrawer) { int timeTypeValue = calendar.get(calendarTimeType); timeTypeValue += timeTypeValue % labelStep; while(true) //PERF: we always draw the first label, even if it's off the screen { if(calendar.getTimeInMillis() > endTicks) //TODO 3: we need to consider pad here, if we are halfway off the screen, we still need to draw break; int value = calendar.get(calendarTimeType); //TODO 4: big hack if(calendarTimeType == Calendar.MINUTE) value += calendar.get(Calendar.HOUR_OF_DAY) * 60; strip.dgDrawText(canvas, calendar.getTimeInMillis(), labelDrawer.createString(value)); calendar.add(calendarTimeType, labelStep); } } private int getAppropriateStep(int minLabelStep, int[] factors) { for(int factor : factors) { if(factor >= minLabelStep) return factor; } return minLabelStep; } private StripGroup currentStripGroup; //TODO 3: Consider a label to the left of each strip that designates the category // ex: // year 2010 // month jan // day 10 // hour 12 // min 05 // sec 01 // private StripGroup createSecondStripGroup() { // Strip seconds = // StripGroup sg = new StripGroup(); // } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); super.setRangeSizePx((int) ((right - left) * prefs.widthOfTimeWindowPerc)); calculateStrips(); } /** * @param periodLength period to set the time frame to */ public void setPeriodLength(double periodLength) { //keep the beginning point the same setTicks(super.ticks + super.ticksFrac - super.rangeSizeTicks/2 + periodLength/2); calculateStrips(); super.setRangeSizeTicks(periodLength); } private void calculateStrips() { StripGroup bestStripGroup = null; // if(ticksPerPixel != 0) // { // for(StripGroup sg : stripGroups) // { // if(sg.maxTicksPerPixel > super.ticksPerPixel && // (bestStripGroup == null || // bestStripGroup.maxTicksPerPixel > sg.maxTicksPerPixel) // ) // bestStripGroup = sg; // } // } // // if(this.currentStripGroup == bestStripGroup) // return; // // currentStripGroup = bestStripGroup; //TODO 3: Fix! We need to choose a strip group currentStripGroup = stripGroups[0]; if(currentStripGroup == null) super.setStrips(new Strip[0]); else super.setStrips(currentStripGroup.strips); } public static class StripGroup { double maxTicksPerPixel = Double.MAX_VALUE; public Strip [] strips; public StripGroup(Strip ... strips) { this.strips = strips; for(Strip s : strips) { if(maxTicksPerPixel > s.maxTicksPerPixel) maxTicksPerPixel = s.maxTicksPerPixel; } } } public void setTicks(long ticks) { super.ticks = ticks; invalidate(); } public interface LabelDrawer { public String createString(int value); } public static class Preferences implements AndroidPreferences { public float textSize = 16; public int textColor = 0xFFFFFFFF; /** * Minimum pad between text labels of dialers in pixels */ public int minPadPixels= 20; /** * The width of the window of time on the screen as a percentage of * the total size of the dial */ public float widthOfTimeWindowPerc = .65f; } public void setColorRange(long startMs, long endMs) { super.setColorRangePx(this.getPixelLoc(startMs), this.getPixelLoc(endMs)); } }