/* * SleepArchiver - cross-platform data manager for Sleeptracker-series watches. * Copyright (C) 2009-2011 Pavel Fatin <http://pavelfatin.com> * * This program 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. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package com.pavelfatin.sleeparchiver.gui.main.render; import com.pavelfatin.sleeparchiver.lang.Utilities; import com.pavelfatin.sleeparchiver.model.*; import org.jdesktop.application.ResourceMap; import java.awt.*; import java.awt.font.FontRenderContext; import java.awt.font.TextLayout; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.List; public class Renderer extends AbstractRenderer<Night> { private static final BasicStroke STROKE_WINDOW = new BasicStroke( 1.0F, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0F, new float[]{2.0F}, 0); private static final int H_GAP = 11; private static final Font FONT_BOLD = new Font("Arial", Font.BOLD, 12); private static final Font FONT_PLAIN = new Font("Arial", Font.PLAIN, 12); private static final Color COLOR_BLUE = new Color(0, 0, 148); private static final Color COLOR_GREEN = new Color(0, 110, 0); private static final Color COLOR_FRAME = new Color(204, 204, 204); private static final Color COLOR_SELECTED_BORDER = new Color(153, 153, 153); private static final Color COLOR_SELECTED_BACKGROUND = new Color(255, 255, 233); private static final Color COLOR_HOLIDAY = new Color(180, 0, 0); private static final int MIN_MOMENTS_SPACE = 3; private ResourceMap _map; private Transform _transform; public Renderer(ResourceMap map) { _map = map; } public void setTransform(Transform transform) { _transform = transform; } public void setNights(List<Night> nights) { _transform.setNights(completeOf(nights)); } private static List<Night> completeOf(List<Night> nights) { ArrayList<Night> complete = new ArrayList<Night>(); for (Night night : nights) { if (night.isComplete()) { complete.add(night); } } return complete; } public void setResolution(double resolution) { _transform.setResolution(resolution); } public int getPrefferedHeight() { return 2 + 96 + 2; } public int getPrefferedWidth(List<Night> nights) { return H_GAP + _transform.getPrefferedWidth(nights) + H_GAP; } private int toX(Instant instant) { return H_GAP + _transform.toX(instant); } protected void render(Graphics2D g, Night night, boolean selected, boolean focused) { drawFrame(g, selected, focused); Date date = night.getDate(); if (date != null) { drawDate(g, 13, 22, date); } drawConditionsAndComments(g, 313, 22, night); drawObservations(g, 125, 13, night); if (night.isAlarmWorked()) { drawAlarm(g, 190, 16); } if (night.isComplete()) { Metrics metrics = night.getMetrics(); drawEquation(g, 208, 302, 22, metrics); _transform.setNight(night); List<Span> spans = night.getMetrics().getSpans(); drawBars(g, spans); drawGaps(g, spans, selected); if (night.hasWindow()) { drawWindow(g, night); } drawMoments(g, spans); drawLengths(g, spans); } } private void drawDate(Graphics2D g, int x, int y, Date date) { g.setFont(FONT_BOLD); g.setColor(date.isHoliday() ? COLOR_HOLIDAY : Color.BLACK); g.drawString(date.format() + " " + date.getDayOfWeek(), x, y); } private void drawConditionsAndComments(Graphics2D g, int x, int y, Night night) { g.setFont(FONT_BOLD); g.setColor(COLOR_GREEN); String conditions = Utilities.join(" | ", night.getConditions()); g.drawString(conditions, x, y); int xx = conditions.isEmpty() ? 0 : widthOf(g, conditions) + 6; g.setFont(FONT_PLAIN); g.setColor(COLOR_BLUE); String comments = night.getComments().replaceAll("\\n", "; "); g.drawString(comments.isEmpty() ? "" : String.format("(%s)", comments), x + xx, y); } private void drawEquation(Graphics2D g, int x1, int x2, int y, Metrics metrics) { g.setFont(FONT_BOLD); g.setColor(COLOR_BLUE); String duration = new Instant(metrics.getDuration()).getTime().formatShort(); String equation = String.format("%s / %d = %d", duration, metrics.getSpansCount(), metrics.getAverage()); int gaps = x2 - x1 - widthOf(g, equation); int x = x1 + Math.round((float) gaps / 2.0F); g.drawString(equation, x, y); } private void drawAlarm(Graphics2D g, int x, int y) { g.setColor(Color.BLACK); g.drawLine(x, y, x + 2, y + 5); g.drawLine(x + 3, y + 3, x + 5, y - 3); } private void drawFrame(Graphics2D g, boolean selected, boolean focused) { if (selected) { g.setColor(backgroundOf(selected)); g.fillRoundRect(2, 2, getWidth() - 5, getHeight() - 5, 15, 15); } g.setColor(COLOR_FRAME); g.drawLine(3, 29, getWidth() - 4, 29); g.drawLine(114, 6, 114, 6 + 22); g.drawLine(175, 6, 175, 6 + 22); g.drawLine(208, 6, 208, 6 + 22); g.drawLine(302, 6, 302, 6 + 22); g.setColor(focused ? Color.BLACK : COLOR_SELECTED_BORDER); g.drawRoundRect(2, 2, getWidth() - 5, getHeight() - 5, 15, 15); } private Color backgroundOf(boolean selected) { return selected ? COLOR_SELECTED_BACKGROUND : Color.WHITE; } private void drawObservations(Graphics2D g, int x, int y, Night night) { Ease asleep = night.getEaseOfFallingAsleep(); if (asleep.isKnown()) { g.setColor(asleep.color(_map)); g.fillPolygon(new int[]{x, x + 9, x + 9}, new int[]{y, y, y + 9}, 3); } Quality quality = night.getQualityOfSleep(); if (quality.isKnown()) { g.setColor(quality.color(_map)); g.fillRect(x + 12, y, 16, 9); } Ease waking = night.getEaseOfWakingUp(); if (waking.isKnown()) { g.setColor(waking.color(_map)); g.fillPolygon(new int[]{x + 31, x + 31 + 9, x + 31}, new int[]{y, y, y + 9}, 3); } } private void drawMoments(Graphics2D g, List<Span> spans) { g.setFont(FONT_PLAIN); g.setColor(Color.BLACK); for (Span span : spans) { String moment = span.getBegin().getTime().formatShort(); Rectangle r = rectangleOf(span); if (r.width > (widthOf(g, moment) + MIN_MOMENTS_SPACE)) { g.drawString(moment, r.x, r.y - 4); } } } private void drawLengths(Graphics2D g, List<Span> spans) { g.setFont(FONT_PLAIN); g.setColor(Color.BLACK); for (Span span : spans) { String length = String.format("%d", span.toMinutes()); Rectangle2D bounds = boundsOf(g, length); Rectangle r = rectangleOf(span); if (r.width > bounds.getWidth()) { g.drawString(length, r.x + Math.round((r.width - bounds.getWidth()) / 2), r.y + r.height + (int) bounds.getHeight() + 4); } } } private void drawBars(Graphics2D g, List<Span> spans) { for (Span span : spans) { Rectangle r = rectangleOf(span); g.setColor(colorOf(span)); g.fillRect(r.x, r.y, r.width, r.height); } } private void drawGaps(Graphics2D g, List<Span> spans, boolean selected) { g.setColor(backgroundOf(selected)); for (Span span : spans) { Rectangle r = rectangleOf(span); g.fillRect(r.x - 1, r.y, 3, r.height); } } private void drawWindow(Graphics2D g, Night night) { g.setColor(Color.RED); g.setStroke(STROKE_WINDOW); Rectangle alarm = alarmRectangleOf(night); g.drawRect(alarm.x, alarm.y, alarm.width - 1, alarm.height - 1); } private Rectangle alarmRectangleOf(Night night) { Rectangle result = rectangleOf(night.getMetrics().getTotalSpan()); int width = _transform.toWidth(night.getWindow()); result.x = result.x + result.width - width; result.width = width; return result; } private Rectangle rectangleOf(Span span) { int x1 = toX(span.getBegin()); int x2 = toX(span.getEnd()); int y1 = 53; int y2 = y1 + 22; return new Rectangle(x1, y1, x2 - x1, y2 - y1); } private static Color colorOf(Span span) { int c = Math.min(span.toMinutes() / 3, 127); return new Color(127 - c, 212 - c, 255 - c); } private static Rectangle2D boundsOf(Graphics2D g, String string) { FontRenderContext frc = g.getFontRenderContext(); TextLayout layout = new TextLayout(string, g.getFont(), frc); return layout.getBounds(); } private static int widthOf(Graphics2D g, String string) { return (int) Math.round(boundsOf(g, string).getWidth()); } private static int heightOf(Graphics2D g, String string) { return (int) Math.round(boundsOf(g, string).getHeight()); } private static Color mix(Color c1, Color c2, float k) { return new Color(mix(c1.getRed(), c2.getRed(), k), mix(c1.getGreen(), c2.getGreen(), k), mix(c1.getBlue(), c2.getBlue(), k)); } private static int mix(float i1, float i2, float k) { return Math.round(i1 + (i2 - i1) * k); } }