/*
* 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.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Vector;
import javax.swing.ButtonGroup;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
/**
* @author Michael Karneim
* @author Max Gensthaler
* @version 3.0
*/
@SuppressWarnings("serial")
public class MonthPanel extends JPanel {
private final static int ROWS = 7;
private final static int COLS = 7;
public static final String SELECTEDDATE_PROPERTYNAME = "selectedDate";
private Configuration config = new Configuration();
public static class Configuration extends MonthPanelUISettings {
public Configuration() {
updateUI();
}
public void updateUI() {
setDelegate(new MonthPanelUISettingsDefaults());
}
}
// State
private Locale locale = null;
private Calendar month;
private Calendar selectedDate = null;
private DateFormat format = null;
private DateFormatSymbols dateSymbols = null;
private JComponent leftUpperCornerComponent = null;
private JComponent rightUpperCornerComponent = null;
// Implementation
private GridLayout gridLayout1 = new GridLayout();
private String[] dayName = null;
private JLabel[] headerLabel = new JLabel[7];
private JToggleButton[][] button = new JToggleButton[ROWS][COLS];
private JPanel controlPanel;
private ButtonGroup group;
private JLabel dateLabel = new JLabel("September 2000");
private final ItemListener itemListener = new MyItemListener();
private class MyItemListener implements ItemListener, Serializable {
public void itemStateChanged(ItemEvent e) {
if (pendingShowMonth) {
return;
} else {
if (e.getStateChange() == ItemEvent.SELECTED) {
String day = ((JToggleButton) e.getSource()).getText();
try {
int d = Integer.parseInt(day);
MonthPanel.this.setSelectedDay(d);
} catch (NumberFormatException ex) {
// ignore
}
} else {
setSelectedDate((Date) null, false);
}
}
}
};
private final ActionListener dayActionListener = new MyActionListener();
private class MyActionListener implements ActionListener, Serializable {
public void actionPerformed(ActionEvent evt) {
fireActionPerformed(new ActionEvent(MonthPanel.this, ActionEvent.ACTION_PERFORMED, null));
}
};
private Vector<ActionListener> actionListeners;
/**
* Creates a new instance of MonthPanel displaying the current month using the default locale.
*/
public MonthPanel() {
this(new Date(), Locale.getDefault(), new ButtonGroup());
}
/**
* Creates a new instance of MonthPanel 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 MonthPanel(Date date, Locale locale) {
this(date, locale, new ButtonGroup());
}
/**
* Creates a new instance of MonthPanel displaying the current month using the default locale.
*/
public MonthPanel(ButtonGroup buttonGroup) {
this(new Date(), Locale.getDefault(), buttonGroup);
}
/**
* Creates a new instance of MonthPanel 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
* @param buttonGroup
* ButtonGroup
*/
public MonthPanel(Date date, Locale locale, ButtonGroup buttonGroup) {
this.locale = locale;
this.group = buttonGroup;
Calendar month = Calendar.getInstance(locale);
month.setTime(date);
this.format = new SimpleDateFormat("MMMM yyyy", this.locale);
this.dateSymbols = new DateFormatSymbols(this.locale);
this.setLayout(new BorderLayout(1, 1));
this.setOpaque(false);
this.initGui();
this.setMonth(month);
}
/**
* Returns the button group.
*
* @return ButtonGroup
*/
public ButtonGroup getButtonGroup() {
return this.group;
}
/**
* Adds all 'day' buttons to the given button group.
*
* @param group
* ButtonGroup
*/
public void setButtonGroup(ButtonGroup group) {
if (this.group != null) {
for (int y = 1; y < ROWS; y++) {
for (int x = 0; x < COLS; x++) {
if (button[y][x] != null) {
this.group.remove(button[y][x]);
}
}
}
}
this.group = group;
if (this.group != null) {
for (int y = 1; y < ROWS; y++) {
for (int x = 0; x < COLS; x++) {
if (button[y][x] != null) {
group.add(button[y][x]);
}
}
}
}
this.rebuildGui();
}
/**
* Sets the graphical configuration of this panel
*
* @param config
* Configuration
*/
public void setConfiguration(Configuration config) {
this.config = config;
this.rebuildGui();
}
/**
* Sets the component to be displayed in the left upper corner of this panel
*
* @param comp
* JComponent
*/
public void setLeftUpperCornerComponent(JComponent comp) {
if (this.controlPanel != null && this.leftUpperCornerComponent != null) {
this.controlPanel.remove(this.leftUpperCornerComponent);
}
this.leftUpperCornerComponent = comp;
if (this.controlPanel != null && this.leftUpperCornerComponent != null) {
this.controlPanel.add("West", this.leftUpperCornerComponent);
}
}
/**
* Sets the component to be displayed in the right upper corner of this panel
*
* @param comp
* JComponent
*/
public void setRightUpperCornerComponent(JComponent comp) {
if (this.controlPanel != null && this.rightUpperCornerComponent != null) {
this.controlPanel.remove(this.rightUpperCornerComponent);
}
this.rightUpperCornerComponent = comp;
if (this.controlPanel != null && this.rightUpperCornerComponent != null) {
this.controlPanel.add("East", this.rightUpperCornerComponent);
}
}
/**
* 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.
*/
public void initGui() {
// Remove all components from this panel in order to begin creating the
// gui
// from scratch.
this.removeAll();
// Create all toggle buttons
for (int y = 1; y < ROWS; y++) {
for (int x = 0; x < COLS; x++) {
if (button[y][x] == null) {
button[y][x] = new JToggleButton(" ") {
@Override
public void paintComponent(Graphics g) {
// paint background
if (this.isSelected()) {
g.setColor(config.getSelectedBackgroundColor());
} else {
g.setColor(this.getBackground());
}
g.fillRect(0, 0, this.getWidth(), this.getHeight());
super.paintComponent(g);
}
};
button[y][x].setContentAreaFilled(false);
button[y][x].setBorderPainted(false);
button[y][x].setBorder(null);
button[y][x].setFocusPainted(false);
button[y][x].addItemListener(itemListener);
button[y][x].addActionListener(dayActionListener);
if (group != null) {
group.add(button[y][x]);
}
}
}
}
// Create the control panel on the top of the calendar
// containing the roll forward and roll backward buttons
// and the title label for the calendar, which displays the current
// visible
// month name and the year.
EmptyBorder emptyBorder = new EmptyBorder(0, 0, 0, 0);
if (this.controlPanel == null) {
this.controlPanel = new JPanel(new BorderLayout());
this.controlPanel.setOpaque(false);
this.controlPanel.add("Center", dateLabel);
if (this.leftUpperCornerComponent != null) {
this.controlPanel.add("West", this.leftUpperCornerComponent);
}
if (this.rightUpperCornerComponent != null) {
this.controlPanel.add("East", this.rightUpperCornerComponent);
}
}
this.add("North", controlPanel);
dateLabel.setHorizontalAlignment(SwingConstants.CENTER);
dateLabel.setFont(this.config.getDateFont());
dateLabel.setForeground(this.config.getHeaderForegroundColor());
dateLabel.setOpaque(false);
// 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(gridLayout1);
calendarPanel.setOpaque(false);
this.add("Center", calendarPanel);
gridLayout1.setColumns(COLS);
gridLayout1.setRows(ROWS);
// Add the headers
for (int x = 0; x < COLS; x++) {
// headerLabel[x] = new JLabel( getDayNames()[ x]);
if (headerLabel[x] == null) {
headerLabel[x] = new JLabel("XX");
headerLabel[x].setOpaque(false);
}
headerLabel[x].setHorizontalAlignment(SwingConstants.CENTER);
headerLabel[x].setFont(this.config.getHeaderFont());
headerLabel[x].setForeground(this.config.getHeaderForegroundColor());
calendarPanel.add(headerLabel[x]);
}
// Add the toggle buttons for the days
for (int y = 1; y < ROWS; y++) {
for (int x = 0; x < COLS; x++) {
button[y][x].setFont(this.config.getDayFont());
button[y][x].setMargin(this.config.getDayMargin());
button[y][x].setBorder(emptyBorder);
button[y][x].setBackground(this.config.getBackgroundColor());
calendarPanel.add(button[y][x]);
}
}
// force revalidation of the layout manager
this.validate();
}
/**
* Rolls the calendar one month forward and displays the new month.
*/
public void rollOneMonthForward() {
this.month.add(Calendar.MONTH, 1);
this.refresh();
}
/**
* Rolls the calendar one month backwards and displays the new month.
*/
public void rollOneMonthBack() {
this.month.add(Calendar.MONTH, -1);
this.refresh();
}
/**
* Sets the selected day of the visible month.
*
* @param dayInMonth
* int
*/
public void setSelectedDay(int dayInMonth) {
Calendar cal = (Calendar) this.month.clone();
cal.set(Calendar.DATE, dayInMonth);
this.setSelectedDate(cal.getTime());
}
/**
* 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);
}
/**
* 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
* @param rollToMonth
* boolean if true the panel scrolls to the respective month
*/
public void setSelectedDate(Date date, boolean rollToMonth) {
if (date == null) {
this.selectedDate = null;
this.refresh();
if (rollToMonth) {
setMonth(new Date());
}
} else {
if (this.selectedDate == null) {
this.selectedDate = (Calendar) this.month.clone();
}
this.selectedDate.setTime(date);
if (rollToMonth) {
this.setMonth(date);
} else {
this.refresh();
}
}
}
/**
* Returns the selected date as an Date instance.
*
* @return the selected date
*/
public Date getSelectedDate() {
if (this.selectedDate == null)
return null;
return this.selectedDate.getTime();
}
/**
* Returns the visible month's date.
*
* @return Date
*/
public Date getMonth() {
return this.month.getTime();
}
/**
* Sets the given month to display on the panel.
*
* @param month
* Date
*/
public void setMonth(Date month) {
if (month == null)
throw new NullPointerException("Illeagal null argument for month.");
this.month.setTime(month);
this.refresh();
}
/**
* Sets the given month to display on the panel.
*
* @param month
* the month to display
*/
public void setMonth(Calendar month) {
if (month == null)
throw new NullPointerException("Illeagal null argument for month.");
this.month = (Calendar) month.clone();
this.format.setCalendar((Calendar) this.month.clone());
this.refresh();
}
boolean pendingShowMonth = false;
/**
* Displays the currently set month on the calendar panel.
*/
protected void refresh() {
if (this.pendingShowMonth) {
return;
}
this.pendingShowMonth = true;
try {
int selectedDay = -1; // NONE selected
if (selectedDate != null && (this.selectedDate.get(Calendar.YEAR) == this.month.get(Calendar.YEAR))
&& (this.selectedDate.get(Calendar.MONTH) == this.month.get(Calendar.MONTH))) {
selectedDay = this.selectedDate.get(Calendar.DATE);
}
if (group != null) {
group.setSelected(null, true);
}
int todayDate = -1;
Calendar today = Calendar.getInstance();
if ((today.get(Calendar.YEAR) == this.month.get(Calendar.YEAR))
&& (today.get(Calendar.MONTH) == this.month.get(Calendar.MONTH))) {
todayDate = today.get(Calendar.DATE);
}
for (int x = 0; x < COLS; x++) {
this.headerLabel[x].setText(this.getDayNames()[x]);
}
int[][] dates = getDatesForButtons(month);
for (int y = 0; y < 6; y++) {
for (int x = 0; x < 7; x++) {
int dayOfMonth = dates[y][x];
button[y + 1][x].setSelected(false);
if (dayOfMonth != 0) {
button[y + 1][x].setText("" + dayOfMonth);
button[y + 1][x].setEnabled(true);
configure(button[y + 1][x], dayOfMonth, selectedDay, todayDate);
if (dayOfMonth == selectedDay) {
button[y + 1][x].setSelected(true);
} else {
this.deselectButton(button[y + 1][x]);
}
} else {
button[y + 1][x].setText("");
button[y + 1][x].setEnabled(false);
this.deselectButton(button[y + 1][x]);
}
}
}
this.dateLabel.setText(this.formatDate(this.month.getTime()));
} finally {
this.pendingShowMonth = false;
}
}
private void deselectButton(JToggleButton btn) {
if (group != null) {
group.remove(btn);
}
btn.setSelected(false);
if (group != null) {
group.add(btn);
}
}
/**
* Configures the given button.
*
* @param btn
* JToggleButton
* @param dayOfMonth
* int
* @param selectedDay
* int
* @param todayDate
* int
*/
private void configure(JToggleButton btn, int dayOfMonth, int selectedDay, int todayDate) {
if (dayOfMonth == selectedDay) {
btn.setForeground(this.config.getSelectedColor());
if (this.config.getSelectedBackgroundColor() != null) {
btn.setBackground(this.config.getSelectedBackgroundColor());
}
} else {
if (dayOfMonth == todayDate) {
btn.setForeground(this.config.getTodayForegroundColor());
} else if (this.isWeekend(dayOfMonth)) {
btn.setForeground(this.config.getWeekendForegroundColor());
} else {
btn.setForeground(this.config.getWorkdayForegroundColor());
}
btn.setBackground(this.config.getBackgroundColor());
}
}
/**
* Returns the human readable shortages for the week days beginning with the weeks first day at index 0.
*
* @return an array of String containing the short names of the days of the week
*/
protected String[] getDayNames() {
// return this.dateSymbols.getShortWeekdays();
if (this.dayName == null) {
String[] shortWeekdays = this.dateSymbols.getShortWeekdays();
int firstDayOfWeek = this.month.getFirstDayOfWeek();
int delta;
switch (firstDayOfWeek) {
case Calendar.SUNDAY:
delta = 0;
break;
case Calendar.MONDAY:
delta = 1;
break;
case Calendar.TUESDAY:
delta = 2;
break;
case Calendar.WEDNESDAY:
delta = 3;
break;
case Calendar.THURSDAY:
delta = 4;
break;
case Calendar.FRIDAY:
delta = 5;
break;
case Calendar.SATURDAY:
delta = 6;
break;
default:
throw new Error("Unexpected Error. First day of month is non of Sun, Mon, Tue, Wed, Thu, Fri, Sat");
}
this.dayName = new String[7];
for (int i = 0; i < 7; i++) {
this.dayName[i] = shortWeekdays[((i + delta) % 7) + 1];
}
}
return this.dayName;
}
protected boolean isWeekend(int dayOfMonth) {
Calendar temp = (Calendar) this.month.clone();
temp.set(Calendar.DAY_OF_MONTH, dayOfMonth);
int dayName = temp.get(Calendar.DAY_OF_WEEK);
if (dayName == Calendar.SATURDAY || dayName == Calendar.SUNDAY)
return true;
else
return false;
}
/**
* Rebuilds the GUI.
*/
public void rebuildGui() {
this.initGui();
this.refresh();
}
// These members are for optimization purpose only
private Date tempDate = new Date();
private Calendar tempCal = null;
/**
* Returns an two dimensional array of int, each containing the date of the given months day as they will appear on
* the toggle buttons on the calendar panel.
*
* @param month
* the month the array should be calculated for
* @return an array of the dates of the days of the given month
*/
protected/* static */int[][] getDatesForButtons(Calendar month) {
int[][] result = new int[6][7];
this.tempDate.setTime(month.getTime().getTime());
if (this.tempCal == null)
this.tempCal = (Calendar) month.clone();
this.tempCal.setTime(tempDate);
Calendar day = tempCal;
// Calendar day = (Calendar) month.clone();
day.set(Calendar.DAY_OF_MONTH, 1);
// int firstWeekDayOfMonth = day.get(Calendar.DAY_OF_WEEK);
// find the first day of the month in the first week
int x = 0;
while (day.get(Calendar.DAY_OF_WEEK) != day.getFirstDayOfWeek()) {
day.roll(Calendar.DAY_OF_WEEK, false);
x = x + 1;
}
day = (Calendar) month.clone();
day.set(Calendar.DAY_OF_MONTH, 1);
for (int y = 0; y < 6; y++) {
for (; x < 7; x++) {
result[y][x] = day.get(Calendar.DATE);
day.roll(Calendar.DATE, true);
if (day.get(Calendar.DATE) == 1)
return result;
}
x = 0;
}
throw new Error("Unexpected Error. Month has more than 31 days!");
}
/**
* Returns the formatted string of the given date, using the internal DateFormat as it was passed to the constructor
* of this class. This method is called by the routine that displays the title of the currently displayed month.
*
* @param date
* the dat to format
* @return the fromatted string
*/
protected String formatDate(Date date) {
return this.format.format(date);
}
/**
* Removes the given action listener from this component.
*/
public synchronized void removeActionListener(ActionListener l) {
if (actionListeners != null && actionListeners.contains(l)) {
Vector<ActionListener> v = (Vector<ActionListener>) actionListeners.clone();
v.removeElement(l);
actionListeners = v;
}
}
/**
* 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) {
Vector<ActionListener> v = (actionListeners == null ? new Vector<ActionListener>(2)
: (Vector<ActionListener>) actionListeners.clone());
if (!v.contains(l)) {
v.addElement(l);
actionListeners = v;
}
}
/**
* 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) {
Vector<ActionListener> listeners = actionListeners;
int count = listeners.size();
for (int i = 0; i < count; i++) {
listeners.elementAt(i).actionPerformed(e);
}
}
}
}