/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db.gui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Vector; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFormattedTextField; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.KeyStroke; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.text.DefaultFormatter; import javax.swing.text.DefaultFormatterFactory; import javax.swing.text.MaskFormatter; import com.servoy.j2db.Messages; import com.servoy.j2db.smart.cmd.MnemonicCheckAction; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.gui.JEscapeDialog; /** * JDateChooser is a simple Date choosing component with similar functionality to JFileChooser and JColorChooser. It can be used as a component, to be inserted * into a client layout, or can display it's own Dialog through use of the {@link #showDialog(Component, String) showDialog} method. * <p> * JDateChooser can be initialized to the current date using the no argument constructor, or initialized to a predefined date by passing an instance of Calendar * to the constructor. * <p> * Using the JDateChooser dialog works in a similar manner to JFileChooser or JColorChooser. The {@link #showDialog(Component, String) showDialog} method * returns an int that equates to the public variables ACCEPT_OPTION, CANCEL_OPTION or ERROR_OPTION. * <p> * <tt> * JDateChooser chooser = new JDateChooser();<br> * if (chooser.showDialog(this, "Select a date...") == JDateChooser.ACCEPT_OPTION) {<br> *   Calendar selectedDate = chooser.getSelectedDate();<br> *   // process date here...<br> * }<p> * To use JDateChooser as a component within a GUI, users should subclass * JDateChooser and override the {@link #acceptSelection() acceptSelection} and * {@link #cancelSelection() cancelSelection} methods to process the * corresponding user selection.<p> * The current date can be retrieved by calling {@link #getSelectedDate() getSelectedDate} * method. */ public class JDateChooser extends JEscapeDialog implements ActionListener, DaySelectionListener { /** * Value returned by {@link #showDialog(Component, String) showDialog} upon an error. */ public static final int ERROR_OPTION = 0; /** * Value returned by {@link #showDialog(Component, String) showDialog} upon pressing the "okay" button. */ public static final int ACCEPT_OPTION = 2; /** * Value returned by {@link #showDialog(Component, String) showDialog} upon pressing the "cancel" button. */ public static final int CANCEL_OPTION = 4; private int currentDay; private int currentMonth; private JLabel dateText; private Calendar calendar; private Calendar todayCalender; private JButton previousYear; private JButton previousMonth; private JButton nextMonth; private JButton nextYear; private JButton okay; private JButton cancel; private int returnValue; private JPanel days; private DayButton[] array; private Color background; private SimpleDateFormat sdf; private String defaultPattern; private JFormattedTextField timeField; /** * This constructor creates a new instance of JDateChooser initialized to the current date. */ public JDateChooser(JFrame parent, String title, String defaultPattern) { this(parent, title, defaultPattern, Calendar.getInstance()); } /** * Creates a new instance of JDateChooser initialized to the given Calendar. */ public JDateChooser(JFrame parent, String title, String defaultPattern, Calendar c) { super(parent, title, true); init(defaultPattern, c); } /** * This constructor creates a new instance of JDateChooser initialized to the current date. */ public JDateChooser(JDialog parent, String title, String defaultPattern) { this(parent, title, defaultPattern, Calendar.getInstance()); } /** * Creates a new instance of JDateChooser initialized to the given Calendar. */ public JDateChooser(JDialog parent, String title, String defaultPattern, Calendar c) { super(parent, title, true); init(defaultPattern, c); } private void init(String defaultPattern, Calendar c) { if (defaultPattern == null) throw new NullPointerException("DefaultPattern can't be null"); //$NON-NLS-1$ this.calendar = c; this.defaultPattern = defaultPattern; this.todayCalender = (Calendar)c.clone(); this.calendar.setLenient(true); sdf = new SimpleDateFormat(); array = new DayButton[31]; setup(); loadBounds("JDateChooser"); //$NON-NLS-1$ } private void setup() { MouseListener ml = new ButtonMouseListener(); for (int i = 0; i < 31; i++) { array[i] = new DayButton(i + 1); array[i].addDaySelectionListener(this); array[i].addMouseListener(ml); } background = array[0].getBackground(); GridBagLayout g = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); JPanel header = new JPanel(g); c.gridx = 0; c.gridy = 0; c.insets = new Insets(2, 0, 2, 0); previousYear = (JButton)header.add(new JButton("<<")); //$NON-NLS-1$ previousYear.addActionListener(this); previousYear.setToolTipText(Messages.getString("servoy.datechooser.previousyear")); //$NON-NLS-1$ g.setConstraints(previousYear, c); previousMonth = (JButton)header.add(new JButton("<")); //$NON-NLS-1$ previousMonth.addActionListener(this); previousMonth.setToolTipText(Messages.getString("servoy.datechooser.previousmonth")); //$NON-NLS-1$ c.gridx++; g.setConstraints(previousMonth, c); JPanel labelTime = new JPanel(new BorderLayout(5, 5)); labelTime.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); dateText = new JLabel("", SwingConstants.CENTER); //$NON-NLS-1$ // dateText.setBorder(BorderFactory.createEmptyBorder(0, 12,0, 12)); timeField = new JFormattedTextField(); labelTime.add(dateText, BorderLayout.CENTER); labelTime.add(timeField, BorderLayout.EAST); header.add(labelTime); // dateText.setBorder(new EtchedBorder(EtchedBorder.LOWERED)); c.gridx++; c.weightx = 1.0; c.fill = GridBagConstraints.BOTH; g.setConstraints(dateText, c); nextMonth = (JButton)header.add(new JButton(">")); //$NON-NLS-1$ nextMonth.addActionListener(this); nextMonth.setToolTipText(Messages.getString("servoy.datechooser.nextmonth")); //$NON-NLS-1$ c.gridx++; c.weightx = 0.0; c.fill = GridBagConstraints.NONE; g.setConstraints(nextMonth, c); nextYear = (JButton)header.add(new JButton(">>")); //$NON-NLS-1$ nextYear.addActionListener(this); nextYear.setToolTipText(Messages.getString("servoy.datechooser.nextyear")); //$NON-NLS-1$ c.gridx++; g.setConstraints(nextYear, c); JButton today = new JButton(Messages.getString("servoy.datechooser.today")); //$NON-NLS-1$ today.setActionCommand("today"); //$NON-NLS-1$ today.addActionListener(this); okay = new JButton(Messages.getString("servoy.button.ok")); //$NON-NLS-1$ okay.addActionListener(this); cancel = new JButton(Messages.getString("servoy.button.cancel")); //$NON-NLS-1$ cancel.addActionListener(this); JPanel buttonPane = new JPanel(); buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS)); buttonPane.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0)); buttonPane.add(today); buttonPane.add(Box.createHorizontalGlue()); buttonPane.add(okay); buttonPane.add(Box.createRigidArea(new Dimension(5, 0))); buttonPane.add(cancel); panel = new JPanel(); panel.setLayout(new BorderLayout()); updateCalendar(calendar); panel.add("North", header); //$NON-NLS-1$ panel.add("Center", days); //$NON-NLS-1$ panel.add("South", buttonPane); //$NON-NLS-1$ panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); getContentPane().setLayout(new BorderLayout()); getContentPane().add(panel, BorderLayout.CENTER); getRootPane().setDefaultButton(okay); InputMap im = getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); ActionMap am = getRootPane().getActionMap(); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "down"); //$NON-NLS-1$ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "up"); //$NON-NLS-1$ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "left"); //$NON-NLS-1$ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "right"); //$NON-NLS-1$ am.put("left", new ArrowKeyAction(-1)); //$NON-NLS-1$ am.put("right", new ArrowKeyAction(1)); //$NON-NLS-1$ am.put("down", new ArrowKeyAction(7)); //$NON-NLS-1$ am.put("up", new ArrowKeyAction(-7)); //$NON-NLS-1$ } private JPanel panel; private String timePattern; public void updateCalendar(Calendar c) { if (days != null) { days.removeAll(); } else { days = new JPanel(new GridLayout(7, 8)); panel.add(days, BorderLayout.CENTER); days.setBorder(BorderFactory.createTitledBorder(Messages.getString("servoy.datechooser.label.monthoverview"))); //$NON-NLS-1$ } calendar = c; currentDay = calendar.get(Calendar.DAY_OF_MONTH); currentMonth = calendar.get(Calendar.MONTH); Calendar setup = (Calendar)calendar.clone(); setup.set(Calendar.DAY_OF_WEEK, setup.getFirstDayOfWeek()); days.add(new JLabel(Messages.getString("servoy.datechooser.label.week"), SwingConstants.CENTER)); int lastLayoutPosition = 0; for (int i = 0; i < 7; i++) { int dayInt = setup.get(Calendar.DAY_OF_WEEK); if (dayInt == Calendar.MONDAY) days.add(new JLabel(Messages.getString("servoy.datechooser.label.monday"), SwingConstants.CENTER)); //$NON-NLS-1$ if (dayInt == Calendar.TUESDAY) days.add(new JLabel(Messages.getString("servoy.datechooser.label.tuesday"), SwingConstants.CENTER)); //$NON-NLS-1$ if (dayInt == Calendar.WEDNESDAY) days.add(new JLabel(Messages.getString("servoy.datechooser.label.wednesday"), SwingConstants.CENTER)); //$NON-NLS-1$ if (dayInt == Calendar.THURSDAY) days.add(new JLabel(Messages.getString("servoy.datechooser.label.thursday"), SwingConstants.CENTER)); //$NON-NLS-1$ if (dayInt == Calendar.FRIDAY) days.add(new JLabel(Messages.getString("servoy.datechooser.label.friday"), SwingConstants.CENTER)); //$NON-NLS-1$ if (dayInt == Calendar.SATURDAY) days.add(new JLabel(Messages.getString("servoy.datechooser.label.saturday"), SwingConstants.CENTER)); //$NON-NLS-1$ if (dayInt == Calendar.SUNDAY) days.add(new JLabel(Messages.getString("servoy.datechooser.label.sunday"), SwingConstants.CENTER)); //$NON-NLS-1$ setup.roll(Calendar.DAY_OF_WEEK, true); lastLayoutPosition++; } setup = (Calendar)calendar.clone(); setup.set(Calendar.DAY_OF_MONTH, 1); days.add(new JLabel(String.valueOf(setup.get(Calendar.WEEK_OF_YEAR)), SwingConstants.CENTER)); int first = setup.get(Calendar.DAY_OF_WEEK) + Calendar.SUNDAY - setup.getFirstDayOfWeek(); if (first <= 0) first += 7; for (int i = 0; i < (first - 1); i++) { days.add(new JLabel("")); //$NON-NLS-1$ lastLayoutPosition++; } setup.set(Calendar.DAY_OF_MONTH, 1); for (int i = 0; i < setup.getMaximum(Calendar.DAY_OF_MONTH); i++) /* Actual */ { if (todayCalender.get(Calendar.MONTH) == setup.get(Calendar.MONTH) && todayCalender.get(Calendar.YEAR) == setup.get(Calendar.YEAR) && todayCalender.get(Calendar.DATE) == setup.get(Calendar.DAY_OF_MONTH)) { array[i].setBackground(UIManager.getColor("Table.selectionBackground")); //$NON-NLS-1$ array[i].setForeground(UIManager.getColor("Table.selectionForeground")); //$NON-NLS-1$ array[i].setToolTipText(Messages.getString("servoy.datechooser.today")); //$NON-NLS-1$ } else { array[i].setBackground(background); array[i].setToolTipText(null); } days.add(array[i]); setup.roll(Calendar.DAY_OF_MONTH, true); lastLayoutPosition++; if (setup.get(Calendar.DAY_OF_MONTH) == 1) { break; } if ((first + i) % 7 == 0) days.add(new JLabel(String.valueOf(setup.get(Calendar.WEEK_OF_YEAR)), SwingConstants.CENTER)); } for (int i = lastLayoutPosition; i < 49; i++) days.add(new JLabel("")); //$NON-NLS-1$ days.invalidate(); validate(); days.repaint(); setup = null; updateLabel(); SwingUtilities.invokeLater(new Runnable() { public void run() { array[currentDay - 1].requestFocus(); } }); } private void updateLabel() { String text = sdf.format(calendar.getTime()); dateText.setText(text); setTitle(Messages.getString("servoy.datechooser.title", new Object[] { text })); //$NON-NLS-1$ } /** * Returns the currently selected Date in the form of a java.util.Calendar object. Typically called adter receipt of an {@link #ACCEPT_OPTION ACCEPT_OPTION} * (using the {@link #showDialog(Component, String) showDialog} method) or within the {@link #acceptSelection() acceptSelection} method (using the * JDateChooser as a component.) * <p> * * @return java.util.Calendar The selected date in the form of a Calendar object. */ public Calendar getSelectedDate() { return calendar; } /** * Pops up a Date chooser dialog with the supplied <i>title</i>, centered about the component <i>parent</i>. * * @return int An integer that equates to the static variables <i>ERROR_OPTION</i>, <i>ACCEPT_OPTION</i> or <i>CANCEL_OPTION</i>. */ // public int showDialog(JFrame parent, String title) { // returnValue = ERROR_OPTION; //// Frame frame = parent instanceof Frame ? (Frame) parent : (Frame)SwingUtilities.getAncestorOfClass(Frame.class, parent); // // dialog = new JEscapeDialog(parent, title, true); // dialog.getContentPane().add("Center", this); // dialog.pack(); // dialog.setLocationRelativeTo(parent); // dialog.getRootPane().setDefaultButton(okay); // dialog.show(); // return returnValue; // } public Date format(Date date, String pattern) { sdf.applyPattern(getDisplayPattern(pattern)); try { return sdf.parse(sdf.format(date)); } catch (ParseException e) { } return date; } public int showDialog(String pattern) { String pat = getDisplayPattern(pattern); int tmp = pat.toLowerCase().indexOf("hh:mm"); //$NON-NLS-1$ if (tmp == -1) { tmp = pat.toLowerCase().indexOf("hhmm"); //$NON-NLS-1$ } if (tmp != -1) { int space = pat.indexOf(" ", tmp); //$NON-NLS-1$ if (space == -1) space = pat.length(); timePattern = pat.substring(tmp, space); pat = pat.substring(0, tmp) + pat.substring(space); sdf.applyPattern(timePattern); StringBuilder sb = new StringBuilder(timePattern.length()); for (int i = 0; i < timePattern.length(); i++) { char ch = timePattern.charAt(i); if (Character.isLetter(ch)) { sb.append('#'); } else { sb.append(ch); } } DefaultFormatter defaultFormatter = new DefaultFormatter(); DefaultFormatter maskFormatter; try { maskFormatter = new MaskFormatter(sb.toString()); maskFormatter.setOverwriteMode(true); } catch (ParseException e) { Debug.error(e); maskFormatter = defaultFormatter; } timeField.setFormatterFactory(new DefaultFormatterFactory(defaultFormatter, defaultFormatter, maskFormatter)); timeField.setVisible(true); timeField.setValue(sdf.format(calendar.getTime())); } else { timeField.setVisible(false); timePattern = null; } sdf.applyPattern(pat.trim()); updateLabel(); returnValue = ERROR_OPTION; setVisible(true); return returnValue; } private String getDisplayPattern(String pattern) { int index; if (pattern == null) { return defaultPattern; } else if ((index = pattern.indexOf("|")) != -1) //$NON-NLS-1$ { return pattern.substring(0, index); } return pattern; } /** * This method is called when the user presses the "okay" button. Users must subclass JDateChooser and override this method to use JDateChooser as a * Component and receive accept selections by the user. */ public void acceptSelection() { if (timePattern != null) { String pattern = sdf.toPattern(); sdf.applyPattern(timePattern); try { Date d = sdf.parse(timeField.getText()); Calendar cal = Calendar.getInstance(); cal.setTime(d); calendar.set(Calendar.HOUR_OF_DAY, cal.get(Calendar.HOUR_OF_DAY)); calendar.set(Calendar.MINUTE, cal.get(Calendar.MINUTE)); calendar.set(Calendar.SECOND, cal.get(Calendar.SECOND)); calendar.set(Calendar.MILLISECOND, cal.get(Calendar.MILLISECOND)); } catch (ParseException e) { JOptionPane.showMessageDialog(this, Messages.getString("servoy.datechooser.message.timeinvalid")); //$NON-NLS-1$ return; } finally { sdf.applyPattern(pattern); } } setVisible(false); } /** * This method is called when the user presses the "cancel" button. Users must subclass JDateChooser and override this method to use JDateChooser as a * Component and receive cancel selections by the user. */ @Override public void cancel() { setVisible(false); } /** * Used to process events from the previous month, previous year, next month, next year, okay and cancel buttons. Users should call * super.actionPerformed(ActionEvent) if overriding this method. */ public void actionPerformed(ActionEvent e) { if ("today".equals(e.getActionCommand())) //$NON-NLS-1$ { Calendar today = Calendar.getInstance(); today.setTime(new Date()); calendar.set(Calendar.YEAR, today.get(Calendar.YEAR)); calendar.set(Calendar.MONTH, today.get(Calendar.MONTH)); calendar.set(Calendar.DAY_OF_MONTH, today.get(Calendar.DAY_OF_MONTH)); updateCalendar(calendar); } else if (e.getSource() == okay) { returnValue = ACCEPT_OPTION; acceptSelection(); } else if (e.getSource() == cancel) { returnValue = CANCEL_OPTION; cancel(); } else if (e.getSource() == previousYear) { calendar.roll(Calendar.YEAR, false);//-1 updateCalendar(calendar); } else if (e.getSource() == previousMonth) { int month = calendar.get(Calendar.MONTH); if (month == 0) { int year = calendar.get(Calendar.YEAR); calendar.roll(Calendar.MONTH, false);//-1 calendar.set(Calendar.YEAR, year - 1); } else { calendar.roll(Calendar.MONTH, false);//-1 } updateCalendar(calendar); } else if (e.getSource() == nextMonth) { int month = calendar.get(Calendar.MONTH); if (month == 11) { int year = calendar.get(Calendar.YEAR); calendar.roll(Calendar.MONTH, true);//1 calendar.set(Calendar.YEAR, year + 1); } else { calendar.roll(Calendar.MONTH, true);//1 } updateCalendar(calendar); } else if (e.getSource() == nextYear) { calendar.roll(Calendar.YEAR, true);//1 updateCalendar(calendar); } } /** * Used to process day selection events from the user. This method resets resets the Calendar object to the selected day. Subclasses should make a call to * super.daySelected() if overriding this method. */ public void daySelected(int d) { calendar.set(Calendar.DAY_OF_MONTH, d); updateLabel(); currentDay = d; returnValue = ACCEPT_OPTION; // acceptSelection(); } class ArrowKeyAction extends MnemonicCheckAction { int ndays; ArrowKeyAction(int ndays) { this.ndays = ndays; } public void actionPerformed(ActionEvent e) { final int oldDay = currentDay; calendar.roll(Calendar.DAY_OF_YEAR, ndays);//-1 currentDay = calendar.get(Calendar.DAY_OF_MONTH); int month = calendar.get(Calendar.MONTH); if (month != currentMonth) { updateCalendar(calendar); SwingUtilities.invokeLater(new Runnable() { /** * @see java.lang.Runnable#run() */ public void run() { JDateChooser.this.days.invalidate(); JDateChooser.this.days.getParent().validate(); array[oldDay - 1].repaint(); } }); } else { array[currentDay - 1].requestFocus(); updateLabel(); } } } public class ButtonMouseListener extends MouseAdapter { /** * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent) */ @Override public void mouseReleased(MouseEvent e) { if (e.getClickCount() == 2) { acceptSelection(); } } } } interface DaySelectionListener { public void daySelected(int day); } class DayButton extends JButton implements ActionListener { private final int day; private Vector listeners; public DayButton(int d) { super((new Integer(d)).toString()); this.day = d; setMargin(new Insets(0, 0, 0, 0)); setPreferredSize(new Dimension(40, 22)); addActionListener(this); } public void actionPerformed(ActionEvent e) { if (listeners != null) { for (int i = 0; i < listeners.size(); i++) { ((DaySelectionListener)listeners.elementAt(i)).daySelected(day); } } } public void addDaySelectionListener(DaySelectionListener l) { if (listeners == null) listeners = new Vector(1, 1); listeners.addElement(l); } public void removeDaySelectionListener(DaySelectionListener l) { if (listeners != null) listeners.removeElement(l); } public void removeAllListeners() { listeners = new Vector(1, 1); } }