/* * @(#)DatePicker.java * * Copyright 2011 Marcos Vasconcelos * * Towel is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Towel 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.towel.swing.calendar; import java.awt.BorderLayout; import java.awt.Color; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import javax.swing.BorderFactory; import javax.swing.JPanel; import javax.swing.SwingConstants; import javax.swing.JLabel; import javax.swing.JButton; import javax.swing.Timer; import javax.swing.UIManager; import com.towel.cfg.TowelConfig; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.IOException; import java.io.InputStream; import java.util.Calendar; import java.util.Locale; import java.util.Properties; import java.text.DateFormat; import java.text.DateFormatSymbols; import java.text.SimpleDateFormat; /** * <code>DatePicker<code> is a component which allows you to select a date * * @author Fabio Rener * @author Marcos Vasconcelos * @modified Eric Yuzo */ public class DatePicker extends JPanel { private static final String TODAY_TXT_ATTR = "today_txt"; private CalendarView calendar; private Calendar selectedDate; private JPanel monthPanel; private JPanel daysPanel; // Month navigation labels private JLabel previousMonthLabel; private JLabel nextMonthLabel; private JLabel monthLabel; // Year navigation labels private JLabel previousYearLabel; private JLabel nextYearLabel; private JLabel yearLabel; private JLabel[] weekDayLabels; private JLabel[] dayLabels; private JButton todayButton; private Locale locale; private DateFormat dateFormat; private String[] monthNames; private String[] weekDayNames; // Background colors private Color headerBackground; private Color weekDaysBackground; private Color dayPickerBackground; private Color selectedDayBackground; // Foreground colors private Color headerForeground; private Color weekDaysForeground; private Color dayPickerForeground; private Color selectedDayForeground; /** * Constructs a new <code>DatePicker</code> associated to default locale. */ public DatePicker() { this(null, null); } /** * Creates a new <code>DatePicker</code> associated to default locale * and using specified date format pattern to format selected date. * * @param pattern * the pattern describing the date format */ public DatePicker(String pattern) { this(null, new SimpleDateFormat(pattern)); } /** * Constructs a new <code>DatePicker</code> associated to the given locale * and using given <code>dateFormat</code>. * * @param locale * the locale associated to this <code>DatePicker</code> * @param dateFormat * the <code>DateFormat</code> used to format selected date */ public DatePicker(Locale locale, DateFormat dateFormat) { if (locale == null) { locale = Locale.getDefault(); } this.locale = locale; if (dateFormat == null) { dateFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale); } dateFormat.setLenient(false); this.dateFormat = dateFormat; selectedDate = getToday(); init(); refresh(); } /** * Constructs a new <code>DatePicker</code>. * * @param cal * this <code>DatePicker</code>'s owner * @param day * selected day. This value must be greater than zero to be set * @param month * selected month. This value is only set if day is greater than * zero * @param year * selected year. This value is only set if day is greater than * zero */ public DatePicker(CalendarView cal, int day, int month, int year) { calendar = cal; locale = TowelConfig.getInstance().getDefaultLocale(); selectedDate = getToday(); if (day > 0) { selectedDate.set(Calendar.DAY_OF_MONTH, day); selectedDate.set(Calendar.MONTH, month - 1); selectedDate.set(Calendar.YEAR, year); } init(); refresh(); } private void updateButtonTxt(Locale locale) { InputStream is = getClass().getResourceAsStream( "/res/strings_" + locale.toString() + ".properties"); Properties props = new Properties(); try { props.load(is); is.close(); todayButton.setText(props.getProperty(TODAY_TXT_ATTR)); } catch (IOException e) { e.printStackTrace(); } } private void init() { headerBackground = Color.LIGHT_GRAY; weekDaysBackground = new Color(63, 124, 124); dayPickerBackground = UIManager.getColor("Label.background"); selectedDayBackground = dayPickerBackground.darker(); headerForeground = Color.BLACK; weekDaysForeground = Color.WHITE; dayPickerForeground = UIManager.getColor("Label.foreground"); selectedDayForeground = UIManager.getColor("Label.foreground"); setLayout(new BorderLayout()); add(getMonthPanel(), BorderLayout.NORTH); add(getDaysPanel(), BorderLayout.CENTER); add(getTodayButton(), BorderLayout.SOUTH); updateWeekDays(locale); updateButtonTxt(locale); } private JPanel getMonthPanel() { if (monthPanel == null) { monthPanel = new JPanel(new GridBagLayout()); previousMonthLabel = createLabelWithBorder("<"); previousMonthLabel.setBackground(headerBackground); previousMonthLabel.setForeground(headerForeground); previousMonthLabel.addMouseListener(new NavigationListener() { @Override public void execute() { int oldMonth = selectedDate.get(Calendar.MONTH); selectedDate.add(Calendar.MONTH, -1); firePropertyChange("month", oldMonth, oldMonth - 1); if (oldMonth == 0) { int year = selectedDate.get(Calendar.YEAR); firePropertyChange("year", year + 1, year); } refresh(); } }); monthPanel.add(previousMonthLabel); GridBagConstraints gbc = new GridBagConstraints(); gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0; monthLabel = createLabelWithBorder(""); monthLabel.setBackground(headerBackground); monthLabel.setForeground(headerForeground); monthPanel.add(monthLabel, gbc); nextMonthLabel = createLabelWithBorder(">"); nextMonthLabel.setBackground(headerBackground); nextMonthLabel.setForeground(headerForeground); nextMonthLabel.addMouseListener(new NavigationListener() { @Override public void execute() { int oldMonth = selectedDate.get(Calendar.MONTH); selectedDate.add(Calendar.MONTH, 1); firePropertyChange("month", oldMonth, oldMonth + 1); if (oldMonth == 11) { int year = selectedDate.get(Calendar.YEAR); firePropertyChange("year", year - 1, year); } refresh(); } }); monthPanel.add(nextMonthLabel); previousYearLabel = createLabelWithBorder("<"); previousYearLabel.setBackground(headerBackground); previousYearLabel.setForeground(headerForeground); previousYearLabel.addMouseListener(new NavigationListener() { @Override public void execute() { int oldYear = selectedDate.get(Calendar.YEAR); selectedDate.add(Calendar.YEAR, -1); firePropertyChange("year", oldYear, oldYear - 1); refresh(); } }); monthPanel.add(previousYearLabel); yearLabel = createLabelWithBorder(""); yearLabel.setBackground(headerBackground); yearLabel.setForeground(headerForeground); monthPanel.add(yearLabel); nextYearLabel = createLabelWithBorder(">"); nextYearLabel.setBackground(headerBackground); nextYearLabel.setForeground(headerForeground); nextYearLabel.addMouseListener(new NavigationListener() { @Override public void execute() { int oldYear = selectedDate.get(Calendar.YEAR); selectedDate.add(Calendar.YEAR, 1); firePropertyChange("year", oldYear, oldYear + 1); refresh(); } }); monthPanel.add(nextYearLabel); } return monthPanel; } private JPanel getDaysPanel() { if (daysPanel == null) { daysPanel = new JPanel(new GridLayout(7, 7)); weekDayLabels = new JLabel[7]; for (int i = 0; i < 7; i++) { weekDayLabels[i] = new JLabel(); weekDayLabels[i].setHorizontalAlignment(SwingConstants.CENTER); weekDayLabels[i].setBackground(weekDaysBackground); weekDayLabels[i].setForeground(weekDaysForeground); weekDayLabels[i].setOpaque(true); daysPanel.add(weekDayLabels[i]); } dayLabels = new JLabel[42]; for (int i = 0; i < 42; i++) { dayLabels[i] = createLabelWithBorder(""); dayLabels[i].setBackground(dayPickerBackground); dayLabels[i].setForeground(dayPickerForeground); dayLabels[i].addMouseListener(new DaySelectionListener()); daysPanel.add(dayLabels[i]); } } return daysPanel; } private JButton getTodayButton() { if (todayButton == null) { todayButton = new JButton("Today"); todayButton.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { selectedDate = getToday(); setSelectedDay(selectedDate.get(Calendar.DAY_OF_MONTH)); firePropertyChange("day", 0, selectedDate.get(Calendar.DAY_OF_MONTH)); if (calendar != null) { calendar.dateSelected(getDate()); } } }); } return todayButton; } private void updateWeekDays(Locale locale) { DateFormatSymbols symbols = new DateFormatSymbols(locale); weekDayNames = symbols.getShortWeekdays(); monthNames = symbols.getMonths(); for (int i = 0; i < 7; i++) { weekDayLabels[i].setText(weekDayNames[i + 1]); } } private String getMonthName(int month) { return monthNames[month]; } private void refresh() { String monthName = getMonthName(selectedDate.get(Calendar.MONTH)); monthLabel.setText(monthName); String currentYear = String.valueOf(selectedDate.get(Calendar.YEAR)); yearLabel.setText(currentYear); populateCells(); setSelectedDay(selectedDate.get(Calendar.DAY_OF_MONTH)); } private void populateCells() { Calendar cal = getSelectedDate(); cal.set(Calendar.DAY_OF_MONTH, 1); int weekDay = cal.get(Calendar.DAY_OF_WEEK); int monthDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH); int day = 1; for (int i = 0; i < 42; i++) { if ((i < weekDay - 1) || (i > (monthDay + weekDay - 2))) { dayLabels[i].setText(""); } else { dayLabels[i].setText(String.valueOf(day)); day++; } dayLabels[i].setBackground(dayPickerBackground); dayLabels[i].setForeground(dayPickerForeground); } } private Calendar getToday() { Calendar cal = Calendar.getInstance(); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); return cal; } /** * Returns a string representing the selected date. * * @return a string representing the selected date */ public String getDate() { return dateFormat.format(getSelectedDate().getTime()); } /** * Returns a copy of selected <code>Calendar</code>. * * @return a copy of selected <code>Calendar</code> */ public Calendar getSelectedDate() { return (Calendar) selectedDate.clone(); } /** * Sets the selected <code>Calendar</code>. * * @param calendar * the new selected <code>Calendar</code> */ public void setSelectedDate(Calendar calendar) { if (calendar != null) { Calendar oldDate = getSelectedDate(); this.selectedDate = getToday(); this.selectedDate.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)); firePropertyChange("date", oldDate, getSelectedDate()); refresh(); } } /** * Changes the selected day. * <p> * <strong>Warning:</strong> It's not recommended to use this method. Prefer * to use {@link #setSelectedDate(Calendar)}. * * @param newDay * the day to select */ public void setSelectedDay(int newDay) { String day; for (int i = 0; i < 42; i++) { day = dayLabels[i].getText(); if (day.equals(Integer.toString(newDay))) { selectedDate.set(Calendar.DAY_OF_MONTH, newDay); dayLabels[i].setBackground(selectedDayBackground); dayLabels[i].setForeground(selectedDayForeground); break; } } } /** * Returns the locale associated to this <code>DatePicker</code>. The locale * is used to get appropriate week day names and month names. * * @return the locale associated to this <code>DatePicker</code> */ public Locale getLocale() { return locale; } /** * Changes the locale associated to this <code>DatePicker</code>. The locale * is used to get appropriate week day names and month names. * * @param locale * the new locale */ public void setLocale(Locale locale) { if (locale != null) { this.locale = locale; updateWeekDays(locale); } } /** * Returns the <code>dateFormat</code> used to format selected date. * * @return the <code>dateFormat</code> used to format selected date */ public DateFormat getDateFormat() { return dateFormat; } /** * Changes the <code>dateFormat</code> used to format selected date. * * @param locale * the new <code>dateFormat</code> */ public void setDateFormat(DateFormat dateFormat) { if (dateFormat != null) { this.dateFormat = dateFormat; } } /** * Changes the date format's pattern. * * @param pattern * the pattern describing the date format */ public void setPattern(String pattern) { setDateFormat(new SimpleDateFormat(pattern)); } /** * Returns the background color for header labels used to select month and * year. * * @return the background color for header labels */ public Color getHeaderBackground() { return headerBackground; } /** * Sets the background color for header labels used to select month and * year. * * @param headerBg * the background color for header labels */ public void setHeaderBackground(Color headerBg) { if (headerBg != null) { this.headerBackground = headerBg; previousMonthLabel.setBackground(headerBg); nextMonthLabel.setBackground(headerBg); monthLabel.setBackground(headerBg); previousYearLabel.setBackground(headerBg); nextYearLabel.setBackground(headerBg); yearLabel.setBackground(headerBg); } } /** * Returns the background color for week day labels. * * @return the background color for week day labels */ public Color getWeekDaysBackground() { return weekDaysBackground; } /** * Sets the background color for week day labels. * * @param weekDaysBg * the background color for week day labels */ public void setWeekDaysBackground(Color weekDaysBg) { if (weekDaysBg != null) { this.weekDaysBackground = weekDaysBg; for (int i = 0; i < 7; i++) { weekDayLabels[i].setBackground(weekDaysBg); } } } /** * Returns the background color for day picker labels. * * @return the background color for day picker labels */ public Color getDayPickerBackground() { return dayPickerBackground; } /** * Sets the background color for day picker labels. * * @param dayPickerBg * the background color for day picker labels */ public void setDayPickerBackground(Color dayPickerBg) { if (dayPickerBg != null) { this.dayPickerBackground = dayPickerBg; refresh(); } } /** * Returns the background color for selected day label. * * @return the background color for selected day label */ public Color getSelectedDayBackground() { return selectedDayBackground; } /** * Sets the background color for selected day label. * * @param selectedDayBg * the background color for selected day label */ public void setSelectedDayBackground(Color selectedDayBg) { if (selectedDayBg != null) { this.selectedDayBackground = selectedDayBg; refresh(); } } /** * Returns the foreground color for header labels used to select month and * year. * * @return the foreground color for header labels */ public Color getHeaderForeground() { return headerForeground; } /** * Sets the foreground color for header labels used to select month and * year. * * @param headerFg * the foreground color for header labels */ public void setHeaderForeground(Color headerFg) { if (headerFg != null) { this.headerForeground = headerFg; previousMonthLabel.setForeground(headerFg); nextMonthLabel.setForeground(headerFg); monthLabel.setForeground(headerFg); previousYearLabel.setForeground(headerFg); nextYearLabel.setForeground(headerFg); yearLabel.setForeground(headerFg); } } /** * Returns the foreground color for week day labels. * * @return the foreground color for week day labels */ public Color getWeekDaysForeground() { return weekDaysForeground; } /** * Sets the foreground color for week day labels. * * @param weekDaysFg * the foreground color for week day labels */ public void setWeekDaysForeground(Color weekDaysFg) { if (weekDaysFg != null) { this.weekDaysForeground = weekDaysFg; for (int i = 0; i < 7; i++) { weekDayLabels[i].setForeground(weekDaysFg); } } } /** * Returns the foreground color for day picker labels. * * @return the foreground color for day picker labels */ public Color getDayPickerForeground() { return dayPickerForeground; } /** * Sets the foreground color for day picker labels. * * @param dayPickerFg * the foreground color for day picker labels */ public void setDayPickerForeground(Color dayPickerFg) { if (dayPickerFg != null) { this.dayPickerForeground = dayPickerFg; refresh(); } } /** * Returns the foreground color for selected day label. * * @return the foreground color for selected day label */ public Color getSelectedDayForeground() { return selectedDayForeground; } /** * Sets the foreground color for selected day label. * * @param selectedDayFg * the foreground color for selected day label */ public void setSelectedDayForeground(Color selectedDayFg) { if (selectedDayFg != null) { this.selectedDayForeground = selectedDayFg; refresh(); } } /** * Sets <code>todayButton</code>'s text. * * @param todayString * the text to be displayed in <code>todayButton</code>. */ public void setTodayString(String todayString) { getTodayButton().setText(todayString); } /** * Makes <code>todayButton</code> visible or invisible. * * @param visible * true to make <code>todayButton</code> visible; false, * otherwise */ public void setTodayButtonVisible(boolean visible) { getTodayButton().setVisible(visible); } private JLabel createLabelWithBorder(String text) { JLabel label = new JLabel(text); label.setBorder(BorderFactory.createEtchedBorder()); label.setHorizontalAlignment(SwingConstants.CENTER); label.setOpaque(true); return label; } // Listener for day selection labels private class DaySelectionListener extends MouseAdapter { @Override public void mouseClicked(MouseEvent e) { String day = ((JLabel) e.getSource()).getText(); if (day.length() > 0) { setSelectedDay(Integer.parseInt(day)); firePropertyChange("day", 0, selectedDate.get(Calendar.DAY_OF_MONTH)); if (calendar != null) { calendar.dateSelected(getDate()); } } refresh(); } } // Listener for navigation labels. private class NavigationListener extends MouseAdapter { // Timer used to auto repeat execute() method whenever // the user holds one of the navigation labels. Timer timer; public NavigationListener() { timer = new Timer(100, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { execute(); } }); timer.setInitialDelay(500); } public void execute() { // This method must be implemented by subclasses } @Override public void mousePressed(MouseEvent e) { execute(); timer.start(); } @Override public void mouseReleased(MouseEvent e) { timer.stop(); } } }