package org.matveev.pomodoro4nb.timer;
import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import org.matveev.pomodoro4nb.controls.RolloverButton;
import org.matveev.pomodoro4nb.utils.Resources;
/**
*
* @author Alexey Matveev
*/
public class PomodoroTimer extends JPanel {
public enum State {
IDLE, WORK, BREAK
}
private final List<PomodoroTimerListener> timerListeners = new ArrayList<PomodoroTimerListener>();
private JLabel timeLabel;
private JButton controlButton;
private final Action startAction = new StartTimerAction();
private final Action stopAction = new StopTimerAction();
private Timer countTime;
private Timer progressTimer;
private PomodoroTimerData data;
private State state;
private double progress;
private long startTime;
private long endTime;
private boolean isForcedStateChange;
private int pomodoros;
public PomodoroTimer(PomodoroTimerData data) {
this.data = data;
createComponents();
}
private void createComponents() {
setLayout(new BorderLayout());
controlButton = new RolloverButton(startAction);
add(controlButton, BorderLayout.WEST);
timeLabel = new JLabel("00:00");
timeLabel.setFont(Resources.createFont("digital.ttf", 20, null));
timeLabel.setHorizontalAlignment(SwingConstants.CENTER);
timeLabel.setOpaque(false);
add(timeLabel);
}
/*package*/ void setTimerData(PomodoroTimerData data) {
this.data = data;
if (state == State.IDLE) {
updateGUI();
}
}
public void forcedStop() {
isForcedStateChange = true;
setState(State.IDLE);
}
public void setNewTimerData(PomodoroTimerData data) {
if (data != null) {
this.data = data;
}
}
public void setState(State newState) {
this.state = newState;
fireStateChanged();
stop();
if (!State.IDLE.equals(state)) {
if (State.BREAK.equals(state)) {
pomodoros++;
}
start();
}
}
protected void fireStateChanged() {
for (PomodoroTimerListener l : timerListeners) {
l.stateChanged(state, isForcedStateChange);
}
isForcedStateChange = false;
}
private void cancelTimers() {
if (countTime != null) {
countTime.cancel();
}
if (progressTimer != null) {
progressTimer.cancel();
}
}
/*package*/ void addPomodoroTimerListener(PomodoroTimerListener listener) {
timerListeners.add(listener);
}
/*package*/ void removePomodoroTimerListener(PomodoroTimerListener listener) {
timerListeners.remove(listener);
}
private void cancelAndRecreateTimers() {
countTime = new Timer("P4NB-CountTimer");
countTime.schedule(new TimerTask() {
@Override
public void run() {
long delta = endTime - System.currentTimeMillis();
if (delta > 0) {
timeLabel.setText(TimeFormater.format(delta));
} else {
nextState();
}
}
}, 0, 1000);
progressTimer = new Timer("P4NB-ProgressTimer");
progressTimer.schedule(new TimerTask() {
@Override
public void run() {
switch (state) {
case WORK:
progress = (double) (System.currentTimeMillis() - startTime)
/ (double) (endTime - startTime);
break;
case BREAK:
progress = (double) (endTime - System.currentTimeMillis())
/ (double) (endTime - startTime);
break;
}
repaint();
}
}, 0, (endTime - startTime) / getWidth());
}
private void updateTimeValuesAccordingToState() {
progress = 0;
startTime = System.currentTimeMillis();
if (State.WORK.equals(state)) {
endTime = startTime + data.getPomodoroLengthInMillis();
} else if (State.BREAK.equals(state)) {
if (pomodoros == data.getLongBreakInterval()) {
endTime = startTime + data.getLongBreakLengthInMillis();
pomodoros = 0;
} else {
endTime = startTime + data.getShortBreakLengthInMillis();
}
}
}
public void start() {
updateTimeValuesAccordingToState();
cancelAndRecreateTimers();
updateGUI();
repaint();
}
private void updateGUI() {
controlButton.setAction(State.IDLE.equals(state) ? startAction : stopAction);
timeLabel.setText(TimeFormater.format(data.getPomodoroLengthInMillis()));
}
private void nextState() {
State newState = State.IDLE;
if (state == State.WORK) {
newState = State.BREAK;
}
setState(newState);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
final Graphics2D g2d = (Graphics2D) g;
g2d.setColor(State.WORK.equals(state)
? data.getWorkProgressBarColor()
: data.getBreakProgressBarColor());
Rectangle2D.Double shape = new Rectangle2D.Double(0, 0, getWidth() * progress, getHeight());
g2d.fill(shape);
}
public void forcedStart() {
setState(State.WORK);
}
public void stop() {
cancelTimers();
updateTimeValuesAccordingToState();
updateGUI();
repaint();
}
private class StartTimerAction extends AbstractAction {
public StartTimerAction() {
super("", Resources.createIcon("control_play.png"));
}
@Override
public void actionPerformed(ActionEvent e) {
isForcedStateChange = true;
setState(State.WORK);
}
}
private class StopTimerAction extends AbstractAction {
public StopTimerAction() {
super("", Resources.createIcon("control_stop.png"));
}
@Override
public void actionPerformed(ActionEvent e) {
isForcedStateChange = true;
setState(State.IDLE);
}
}
private static final class TimeFormater {
private static final String DEFAULT_TIME_FORMAT_PATTERN = "%02d:%02d";
public static String format(String format, long timeInMillis) {
return String.format(format, timeInMillis / (1000 * 60), timeInMillis / 1000 % 60);
}
public static String format(long time) {
return format(DEFAULT_TIME_FORMAT_PATTERN, time);
}
}
}