/*
* Beanfabrics Framework Copyright (C) by Michael Karneim, beanfabrics.org
* Use is subject to license terms. See license.txt.
*/
package org.beanfabrics.swing.goodies.calendar;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
/**
* CalendarChooser is a date chooser control for a single date.
* <P>
* <B>example:</B><BR>
*
* <pre>
* Locale locale = Locale.US;
* final CalendarChooser cal = new CalendarChooser(new Date(), locale);
* JFrame frame = new JFrame();
* frame.getContentPane().setLayout(new BorderLayout());
* frame.getContentPane().add("Center", cal);
* cal.addActionListener(new ActionListener() {
* public void actionPerformed(ActionEvent evt) {
* System.out.println(cal.getSelectedDate());
* }
* });
* frame.pack();
* frame.setVisible(true);
* </pre>
*
* @author Michael Karneim
* @author Max Gensthaler
* @version 4.0
*/
public class CalendarChooser extends JPanel {
private static final String FORWARD_ACTION = "forward";
private static final String BACK_Action = "back";
private final static ArrowIcon LEFT_ICON = new ArrowIcon(ArrowIcon.LEFT);
private final static ArrowIcon RIGHT_ICON = new ArrowIcon(ArrowIcon.RIGHT);
public static final String SELECTEDDATE_PROPERTYNAME = MonthPanel.SELECTEDDATE_PROPERTYNAME;
// Configuration
private MonthPanel.Configuration config = new MonthPanel.Configuration();
private Dimension rollButtonSize = new Dimension(10, 10);
private int numberOfPreviousVisibleMonths = 0;
private int numberOfSubsequentVisibleMonths = 0;
private Locale locale;
private Date selectedDate = null;
// Implementation
private GridLayout gridLayout = new GridLayout();
private final ButtonGroup group = new ButtonGroup();
private MonthPanel centerMonthPanel;
private MonthPanel[] preMonthPanel = new MonthPanel[] {};
private MonthPanel[] postMonthPanel = new MonthPanel[] {};
private JButton oneMonthBack = new JButton(LEFT_ICON);
private JButton oneMonthForward = new JButton(RIGHT_ICON);
private final ActionListener actionListener = new MyActionListener();
@SuppressWarnings("serial")
private class MyActionListener implements ActionListener, Serializable {
public void actionPerformed(ActionEvent e) {
if (BACK_Action.equals(e.getActionCommand())) {
CalendarChooser.this.rollOneMonthBack();
} else if (FORWARD_ACTION.equals(e.getActionCommand())) {
CalendarChooser.this.rollOneMonthForward();
}
}
};
private final ActionListener monthActionListener = new MonthActionListener();
@SuppressWarnings("serial")
private class MonthActionListener implements ActionListener, Serializable {
public void actionPerformed(ActionEvent e) {
MonthPanel monthPanel = (MonthPanel) e.getSource();
Date selDate = monthPanel.getSelectedDate();
setSelectedDate(selDate, false);
fireActionPerformed(new ActionEvent(CalendarChooser.this, e.getID(), e.getActionCommand(), e.getWhen(),
e.getModifiers()));
}
};
private ArrayList<ActionListener> actionListeners;
/**
* Creates a new instance of CalendarBean displaying the current month using the default locale.
*/
public CalendarChooser() {
this(new Date(), Locale.getDefault());
}
/**
* Creates a new instance of CalendarBean displaying the given month and using the given locale.
*
* @param date
* the date that initially should be selected
* @param locale
* the locale that should be used for the calendar
*/
public CalendarChooser(Date date, Locale locale) {
this.locale = locale;
this.centerMonthPanel = new MonthPanel(date, locale, this.group);
this.centerMonthPanel.setConfiguration(this.config);
this.centerMonthPanel.addActionListener(this.monthActionListener);
this.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
this.gridLayout.setHgap(2);
this.gridLayout.setVgap(2);
this.setLayout(new BorderLayout(1, 1));
oneMonthBack.setActionCommand(BACK_Action);
oneMonthBack.addActionListener(actionListener);
oneMonthForward.setActionCommand(FORWARD_ACTION);
oneMonthForward.addActionListener(actionListener);
this.initGui();
this.setDates(date, date);
}
/**
* Builds or Rebuilds the graphical user interface of the calendar. This method is called by the constructor or
* whenever properties like font colors or font sizes are changed.
*/
protected void initGui() {
// Remove all components from this panel in order to begin creating the
// gui from scratch.
this.removeAll();
// Create the calandar panel which contains all the toggle buttons
// for the days of the currently dispayed month, and the header with
// the names of the days.
JPanel calendarPanel = new JPanel(gridLayout);
calendarPanel.setOpaque(false);
this.add(calendarPanel, BorderLayout.CENTER);
gridLayout.setRows(1);
gridLayout.setColumns(this.numberOfPreviousVisibleMonths + 1 + this.numberOfSubsequentVisibleMonths);
for (int i = 0; i < this.preMonthPanel.length; i++) {
this.preMonthPanel[i].initGui();
calendarPanel.add(this.preMonthPanel[i]);
}
this.centerMonthPanel.initGui();
calendarPanel.add(this.centerMonthPanel);
for (int i = 0; i < this.postMonthPanel.length; i++) {
this.postMonthPanel[i].initGui();
calendarPanel.add(this.postMonthPanel[i]);
}
this.addRollButtons();
// force revalidation of the layout manager
this.validate();
}
private void addRollButtons() {
EmptyBorder emptyBorder = new EmptyBorder(0, 0, 0, 0);
LEFT_ICON.setPreferredSize(this.rollButtonSize);
LEFT_ICON.setColor(config.getHeaderForegroundColor());
RIGHT_ICON.setPreferredSize(this.rollButtonSize);
RIGHT_ICON.setColor(config.getHeaderForegroundColor());
oneMonthBack.setMargin(new Insets(1, 1, 1, 1));
oneMonthBack.setBorder(emptyBorder);
oneMonthBack.setBorderPainted(false);
oneMonthBack.setContentAreaFilled(false);
oneMonthBack.setOpaque(false);
oneMonthForward.setMargin(new Insets(1, 1, 1, 1));
oneMonthForward.setBorder(emptyBorder);
oneMonthForward.setBorderPainted(false);
oneMonthForward.setContentAreaFilled(false);
oneMonthForward.setOpaque(false);
if (this.preMonthPanel.length > 0) {
this.preMonthPanel[0].setLeftUpperCornerComponent(this.oneMonthBack);
} else {
this.centerMonthPanel.setLeftUpperCornerComponent(this.oneMonthBack);
}
if (this.postMonthPanel.length > 0) {
this.postMonthPanel[this.postMonthPanel.length - 1].setRightUpperCornerComponent(this.oneMonthForward);
} else {
this.centerMonthPanel.setRightUpperCornerComponent(this.oneMonthForward);
}
}
/**
* Rolls the calendar one month forward and displays the new month.
*/
public void rollOneMonthForward() {
this.centerMonthPanel.rollOneMonthForward();
this.refresh();
}
/**
* Rolls the calendar one month backwards and displays the new month.
*/
public void rollOneMonthBack() {
this.centerMonthPanel.rollOneMonthBack();
this.refresh();
}
/**
* Sets the displayed month and the selected date.
*
* @param month
* the month to show
* @param selectedDate
* the date to select
*/
public void setDates(Date month, Date selectedDate) {
this.setMonth(month);
this.setSelectedDate(selectedDate, false);
}
/**
* Selects the given date and displays the month containing that date.
*
* @param date
* the date to select
*/
public void setSelectedDate(Date date) {
this.setSelectedDate(date, true);
}
/**
* Selects the given date and displays the month containing that date.
*
* @param date
* the date to select
* @param rollToMonth
* boolean if true the panel scrolls to the respective month
*/
public void setSelectedDate(Date date, boolean rollToMonth) {
Date oldSelectedDate = this.selectedDate == null ? null : (Date) this.selectedDate.clone();
this.centerMonthPanel.setSelectedDate(date, rollToMonth);
this.selectedDate = date;
this.refresh();
this.firePropertyChange(SELECTEDDATE_PROPERTYNAME, oldSelectedDate, selectedDate);
}
/**
* Refreshes the pre- and post-MonthPanels according to the month of the centerMonthPanel.
*/
private void refresh() {
Date month = this.centerMonthPanel.getMonth();
for (int i = 0; i < this.preMonthPanel.length; i++) {
int months = this.preMonthPanel.length - i;
Date showMonth = CalendarChooser.addMonths(month, -months);
this.preMonthPanel[i].setMonth(showMonth);
this.preMonthPanel[i].setSelectedDate(selectedDate, false);
}
for (int i = 0; i < this.postMonthPanel.length; i++) {
int months = i + 1;
Date showMonth = CalendarChooser.addMonths(month, months);
this.postMonthPanel[i].setMonth(showMonth);
this.postMonthPanel[i].setSelectedDate(selectedDate, false);
}
}
/**
* Returns a date that is the result of the summation of the given date and the given number of months.
*
* @param date
* Date
* @param months
* int
* @return Date
*/
private static Date addMonths(Date date, int months) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.MONTH, months);
return cal.getTime();
}
/**
* Returns the selected date as an Date instance.
*
* @return the selected date
*/
public Date getSelectedDate() {
return this.selectedDate;
}
/**
* Returns the visible month.
*
* @return Date
*/
public Date getMonth() {
return this.centerMonthPanel.getMonth();
}
/**
* Sets the given month to display on the calndar panel.
*
* @param month
* Date
*/
public void setMonth(Date month) {
this.centerMonthPanel.setMonth(month);
this.refresh();
}
/**
* Removes the given action listener from this component.
*/
public synchronized void removeActionListener(ActionListener l) {
if (actionListeners != null && actionListeners.contains(l)) {
ArrayList<ActionListener> als = new ArrayList<ActionListener>(actionListeners);
als.remove(l);
actionListeners = als;
}
}
/**
* Adds the given action listnener to this component. All action listeners will be informed about changes that are
* made to the selected date of this calendar.
*
* @param l
* the listener to add to this component
*/
public synchronized void addActionListener(ActionListener l) {
ArrayList<ActionListener> als = actionListeners == null ? new ArrayList<ActionListener>(2)
: new ArrayList<ActionListener>(actionListeners);
if (!als.contains(l)) {
als.add(l);
actionListeners = als;
}
}
/**
* Fires the given action event to all listeners that are added to this component.
*
* @param e
* the event to fire
*/
protected void fireActionPerformed(ActionEvent e) {
if (actionListeners != null) {
for (ActionListener l : actionListeners) {
l.actionPerformed(e);
}
}
}
/**
* Changes the color of the weekend day font.
*
* @param weekendColor
* the new color for the weekend day font
*/
public void setWeekendColor(Color weekendColor) {
this.config.setWeekendForegroundColor(weekendColor);
this.rebuildGui();
}
/**
* Returns the currently used font color for weekend days.
*
* @return the font color for the weekend days
*/
public Color getWeekendColor() {
return this.config.getWeekendForegroundColor();
}
/**
* Changes the color of the workday font.
*
* @param workdayColor
* the new color for the workday font
*/
public void setWorkdayColor(Color workdayColor) {
this.config.setWorkdayForegroundColor(workdayColor);
this.rebuildGui();
}
/**
* Returns the currently used font color for workday days.
*
* @return the font color for the workday days
*/
public Color getWorkdayColor() {
return this.config.getWorkdayForegroundColor();
}
/**
* Changes the color of the selected day font.
*
* @param selectedColor
* the new color for the selected day font
*/
public void setSelectedColor(Color selectedColor) {
this.config.setSelectedColor(selectedColor);
this.rebuildGui();
}
/**
* Returns the currently used font color for selected day.
*
* @return the font color for the selected day
*/
public Color getSelectedColor() {
return this.config.getSelectedColor();
}
/**
* Changes the color of the selected day background.
*
* @param selectedBackgroundColor
* the new color for the selected day background
*/
public void setSelectedBackgroundColor(Color selectedBackgroundColor) {
this.config.setSelectedBackgroundColor(selectedBackgroundColor);
this.rebuildGui();
}
/**
* Returns the currently used day background color for selected day.
*
* @return the day background color for the selected day
*/
public Color getSelectedBackgroundColor() {
return this.config.getSelectedBackgroundColor();
}
/**
* Changes the color of the background of the days.
*
* @param dayBackground
* the new color for the background ofvthe days
*/
public void setDayBackground(Color dayBackground) {
this.config.setBackgroundColor(dayBackground);
this.rebuildGui();
}
/**
* Returns the currently used color of the background of the days.
*
* @return the currently used color of the background of the days
*/
public Color getDayBackground() {
return this.config.getBackgroundColor();
}
/**
* Changes the font of the header of the days.
*
* @param headerFont
* the font of the header of the days
*/
public void setHeaderFont(Font headerFont) {
this.config.setHeaderFont(headerFont);
this.rebuildGui();
}
/**
* Returns the currently used font of the header of the days.
*
* @return the currently used font of the header of the days
*/
public Font getHeaderFont() {
return this.config.getHeaderFont();
}
/**
* Changes the currently used foreground color of the header.
*
* @param c
* the foreground color of the header
*/
public void setHeaderForegroundColor(Color c) {
this.config.setHeaderForegroundColor(c);
this.rebuildGui();
}
/**
* Returns the currently used foreground color of the header.
*
* @return the currently used foreground color of the header
*/
public Color getHeaderForegroundColor() {
return this.config.getHeaderForegroundColor();
}
/**
* Changes the font of the days displayed on the calendar panel.
*
* @param dayFont
* the font of the days displayed on the calendar panel
*/
public void setDayFont(Font dayFont) {
this.config.setDayFont(dayFont);
this.rebuildGui();
}
/**
* Returns the currently used font of the days displayed on the calendar panel.
*
* @return the currently used font of the days displayed on the calendar panel
*/
public Font getDayFont() {
return this.config.getDayFont();
}
/**
* Changes the font of the title of the calendar panel, which displays the currently displayed month's name and the
* year.
*
* @param dateFont
* the new font for the title
*/
public void setDateFont(Font dateFont) {
this.config.setDateFont(dateFont);
this.rebuildGui();
}
/**
* Returns the currently used font of the title of the calendar panel, which displays the currently displayed
* month's name and the year.
*
* @return the currently used font for the title
*/
public Font getDateFont() {
return this.config.getDateFont();
}
/**
* Changes the margin of each days button.
*
* @param dayMargin
* the margin of each days button
*/
public void setDayMargin(Insets dayMargin) {
this.config.setDayMargin(dayMargin);
this.rebuildGui();
}
/**
* Returns the currently used margin of each days button.
*
* @return the currently used margin of each days button
*/
public Insets getDayMargin() {
return this.config.getDayMargin();
}
/**
* Changes the margin of each days button.
*
* @param dayMargin
* the margin of each days button
*/
public void setRollButtonSize(Dimension newSize) {
this.rollButtonSize = newSize;
this.rebuildGui();
}
/**
* Returns the currently used margin of each days button.
*
* @return the currently used margin of each days button
*/
public Dimension getRollButtonSize() {
return this.rollButtonSize;
}
/**
* Sets the number of months that are visible ahead the current month.
*
* @param number
* number of previous visible months
*/
public void setNumberOfPreviousVisibleMonths(int number) {
if (number < 0) {
throw new IllegalArgumentException("number must be >= 0");
}
this.numberOfPreviousVisibleMonths = number;
MonthPanel[] panels = createMonthPanels(this.numberOfPreviousVisibleMonths);
this.setPreMonthPanels(panels);
}
/**
* Returns the number of months that are visible ahead the current month.
*
* @return number of previous visible months
*/
public int getNumberOfPreviousVisibleMonths() {
return this.numberOfPreviousVisibleMonths;
}
/**
* Sets the number of months that are visible behind the current month.
*
* @param number
* number of subsequent visible months
*/
public void setNumberOfSubsequentVisibleMonths(int number) {
if (number < 0) {
throw new IllegalArgumentException("number must be >= 0");
}
this.numberOfSubsequentVisibleMonths = number;
MonthPanel[] panels = createMonthPanels(this.numberOfSubsequentVisibleMonths);
this.setPostMonthPanels(panels);
}
/**
* Returns the number of months that are visible begind the current month.
*
* @return number of subsequent visible months
*/
public int getNumberOfSubsequentVisibleMonths() {
return this.numberOfSubsequentVisibleMonths;
}
private MonthPanel[] createMonthPanels(int number) {
MonthPanel[] panels = new MonthPanel[number];
for (int i = 0; i < panels.length; i++) {
panels[i] = new MonthPanel(this.centerMonthPanel.getMonth(), this.locale);
}
return panels;
}
private void onAddMonthPanel(MonthPanel panel) {
panel.addActionListener(this.monthActionListener);
// panel.addPropertyChangeListener(SELECTEDDATE_PROPERTYNAME,
// this.monthPropertyChangeListener);
panel.setButtonGroup(this.group);
panel.setConfiguration(this.config);
}
private void onRemoveMonthPanel(MonthPanel panel) {
panel.removeActionListener(this.monthActionListener);
// panel.removePropertyChangeListener(SELECTEDDATE_PROPERTYNAME,
// this.monthPropertyChangeListener);
panel.setButtonGroup(null);
}
private void setPreMonthPanels(MonthPanel[] panels) {
if (this.preMonthPanel != null) {
for (int i = 0; i < this.preMonthPanel.length; i++) {
this.onRemoveMonthPanel(this.preMonthPanel[i]);
}
}
this.preMonthPanel = panels;
if (this.preMonthPanel != null) {
for (int i = 0; i < this.preMonthPanel.length; i++) {
this.onAddMonthPanel(this.preMonthPanel[i]);
}
}
this.rebuildGui();
}
private void setPostMonthPanels(MonthPanel[] panels) {
if (this.postMonthPanel != null) {
for (int i = 0; i < this.postMonthPanel.length; i++) {
this.onRemoveMonthPanel(this.postMonthPanel[i]);
}
}
this.postMonthPanel = panels;
if (this.postMonthPanel != null) {
for (int i = 0; i < this.postMonthPanel.length; i++) {
this.onAddMonthPanel(this.postMonthPanel[i]);
}
}
this.rebuildGui();
}
private void rebuildGui() {
this.initGui();
this.refresh();
}
public void updateUI() {
if (config != null) {
config.updateUI();
rebuildGui();
}
super.updateUI();
}
}