package com.github.pfichtner.jrunalyser.ui.cal.swing;
import static com.google.common.base.Preconditions.checkNotNull;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import com.github.pfichtner.jrunalyser.ui.cal.swing.model.CalendarEntry;
import com.github.pfichtner.jrunalyser.ui.cal.swing.model.CalendarModelEvent;
import com.github.pfichtner.jrunalyser.ui.cal.swing.model.CalendarModelListener;
import com.github.pfichtner.jrunalyser.ui.cal.swing.model.CalenderModel;
public class MyCalendar extends JPanel implements CalendarModelListener {
/**
* indicates a left-click.
*/
public static final String PC_DATE_CHANGED = "dateChanged"; //$NON-NLS-1$
/**
* indicates a right-click.
*/
public static final String PC_CONTEXT_MENU = "contextMenu"; //$NON-NLS-1$
private final SimpleDateFormat yymmdd = new SimpleDateFormat("yyyyMMdd"); //$NON-NLS-1$
private final SimpleDateFormat mm = new SimpleDateFormat("MM"); //$NON-NLS-1$
public enum DayType {
CHOSEN, TODAY, SAME_MONTH;
}
private static final long serialVersionUID = -468418125168005375L;
/**
* The first day-of-the-week.
*/
private int firstDayOfWeek;
/**
* An array of buttons used to display the days-of-the-month.
*/
private DateComponent[] buttons;
/**
* The ordered set of all seven days of a week, beginning with the
* 'firstDayOfWeek'.
*/
private int[] weekDays;
private CalenderModel calenderModel;
private Date chosenDate;
/**
* Constructs a new date chooser panel, using today's date as the initial
* selection.
*/
public MyCalendar() {
this(Calendar.getInstance());
}
/**
* Constructs a new date chooser panel.
*
* @param calendar
* the calendar controlling the date.
* @param controlPanel
* a flag that indicates whether or not the 'today' button should
* appear on the panel.
*/
public MyCalendar(final Calendar calendar) {
super(new BorderLayout());
// the default date is today...
this.firstDayOfWeek = calendar.getFirstDayOfWeek();
this.weekDays = new int[7];
for (int i = 0; i < 7; i++) {
this.weekDays[i] = ((this.firstDayOfWeek + i - 1) % 7) + 1;
}
Chooser chooser = new Chooser();
addPropertyChangeListener(chooser);
chooser.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ("monthChanged".equals(evt.getPropertyName())) { //$NON-NLS-1$
setDate((Date) evt.getNewValue());
}
}
});
add(chooser, BorderLayout.NORTH);
add(getCalendarPanel(), BorderLayout.CENTER);
setDate(calendar.getTime());
}
/**
* Sets the date chosen in the panel.
*
* @param theDate
* the new date.
*/
public void setDate(final Date theDate) {
Date newDate = new Date(theDate.getTime());
if (this.chosenDate == null || !equalDates(this.chosenDate, newDate)) {
Date oldValue = this.chosenDate;
this.chosenDate = newDate;
firePropertyChange(PC_DATE_CHANGED, oldValue, this.chosenDate);
refreshButtons();
}
}
/**
* Returns the date selected in the panel.
*
* @return the selected date.
*/
public Date getDate() {
return new Date(this.chosenDate.getTime());
}
/**
* Returns a panel of buttons, each button representing a day in the month.
* This is a sub-component of the DatePanel.
*
* @return the panel.
*/
private JPanel getCalendarPanel() {
final JPanel panel = new JPanel(new GridLayout(7, 7));
panel.setBorder(BorderFactory
.createMatteBorder(1, 0, 0, 1, Color.BLACK));
final String[] weekDays = DateFormatSymbols.getInstance()
.getShortWeekdays();
for (int i = 0; i < this.weekDays.length; i++) {
JLabel comp = new JLabel(weekDays[this.weekDays[i]],
SwingConstants.CENTER);
comp.setBorder(BorderFactory.createMatteBorder(0, 1, 1, 0,
Color.BLACK));
panel.add(comp);
}
MouseListener listener = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
Date old = getDate();
if (e.getButton() == MouseEvent.BUTTON1) {
Calendar cal = Calendar.getInstance();
cal.setTime(getFirstVisibleDate());
cal.add(Calendar.DATE,
((DateComponent) e.getSource()).getNum());
setDate(cal.getTime());
} else if (e.getButton() == MouseEvent.BUTTON3) {
firePropertyChange(PC_CONTEXT_MENU, old, getDate());
}
}
};
this.buttons = new DateComponent[42];
for (int i = 0; i < this.buttons.length; i++) {
DateComponent dc = new DateComponent(i);
dc.addMouseListener(listener);
panel.add(dc);
this.buttons[i] = dc;
}
return panel;
}
@Override
public synchronized void addMouseListener(MouseListener l) {
for (DateComponent dateComponent : this.buttons) {
dateComponent.addMouseListener(l);
}
}
@Override
public synchronized void removeMouseListener(MouseListener l) {
for (DateComponent dateComponent : this.buttons) {
dateComponent.removeMouseListener(l);
}
}
private void renderButon(final Date date, final DateComponent dc) {
if (this.calenderModel != null) {
Collection<CalendarEntry> entries = this.calenderModel
.getEntries(date);
ImageIcon icon = entries.isEmpty() ? null : new ImageIcon(
getClass().getResource("/run.gif"));
dc.setIcon(icon);
dc.setDisabledIcon(icon);
dc.setSelected(equalDates(date, this.chosenDate));
dc.setSelectedMonth(equalMonths(date, this.chosenDate));
dc.setToday(equalDates(date, new Date()));
}
}
private boolean equalDates(final Date d1, final Date d2) {
return getYyyyMmDd(d1).equals(getYyyyMmDd(d2));
}
private boolean equalMonths(final Date d1, final Date d2) {
return getMm(d1).equals(getMm(d2));
}
private String getYyyyMmDd(final Date date) {
synchronized (this.yymmdd) {
return this.yymmdd.format(date);
}
}
private String getMm(final Date date) {
synchronized (this.mm) {
return this.mm.format(date);
}
}
/**
* Returns the first date that is visible in the grid. This should always be
* in the month preceding the month of the selected date.
*
* @return the date.
*/
private Date getFirstVisibleDate() {
final Calendar c = Calendar.getInstance();
c.setTime(this.chosenDate);
c.set(Calendar.DAY_OF_MONTH, 1);
while (c.get(Calendar.DAY_OF_WEEK) != getFirstDayOfWeek()) {
c.add(Calendar.DATE, -1);
}
return c.getTime();
}
/**
* Returns the first day of the week (controls the labels in the date
* panel).
*
* @return the first day of the week.
*/
private int getFirstDayOfWeek() {
return this.firstDayOfWeek;
}
/**
* Update the button labels and colors to reflect date selection.
*/
private void refreshButtons() {
Calendar c = Calendar.getInstance();
c.setTime(getFirstVisibleDate());
for (int i = 0; i < this.buttons.length; i++) {
final DateComponent dc = this.buttons[i];
dc.setDate(c.getTime().getTime());
renderButon(c.getTime(), dc);
c.add(Calendar.DATE, 1);
}
}
public void setCalenderModel(CalenderModel calenderModel) {
if (this.calenderModel != null) {
this.calenderModel.removeModelListener(this);
}
this.calenderModel = checkNotNull(calenderModel);
this.calenderModel.addModelListener(this);
refreshButtons();
}
@Override
public void modelChanged(CalendarModelEvent e) {
if (isVisible(e.getStartDate()) || isVisible(e.getEndDate())) {
refreshButtons();
}
}
private boolean isVisible(Date date) {
Date c = getFirstVisibleDate();
if (date.before(c)) {
return false;
}
Calendar cal = Calendar.getInstance();
cal.setTime(c);
cal.add(Calendar.DAY_OF_MONTH, this.buttons.length);
return !date.after(cal.getTime());
}
}