/* Copyright 2004-2014 Jim Voris * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.qumasoft.guitools.qwin.dialog; import java.awt.Component; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.text.DateFormatSymbols; import java.util.Calendar; import java.util.Date; import javax.swing.DefaultComboBoxModel; import javax.swing.JLabel; import javax.swing.JSpinner; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.SpinnerDateModel; import javax.swing.UIManager; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumn; /** * A date/time chooser panel. * @author Jim Voris */ public class JDateTimeChooser extends javax.swing.JPanel { private static final long serialVersionUID = 7045062870751237617L; private static final int ROW_COUNT = 7; private static final int COLUMN_COUNT = 7; private static final int PREFERRED_COLUMN_WIDTH = 25; private Date chooserDate; private Calendar chooserCalendar; private int intDayOfMonth = 1; private final DateTableModel dateTableModel; private final DateFormatSymbols dateFormatSymbols; private String[] daysOfWeek; /** * Create a date/time chooser. The date defaults to 'now'. */ JDateTimeChooser() { this(new Date()); } /** * Create a date/time chooser with the given date as the default 'value'. * @param date */ JDateTimeChooser(Date date) { this.dateFormatSymbols = new DateFormatSymbols(); this.dateTableModel = new DateTableModel(); initComponents(); chooserDate = date; populateComponents(); } /** * Get the date. * @return the date. */ Date getDate() { return chooserCalendar.getTime(); } /** * Set the date. * @param date the date. */ void setDate(Date date) { chooserDate = date; chooserCalendar.setTime(date); } private void populateComponents() { daysOfWeek = dateFormatSymbols.getWeekdays(); chooserCalendar = Calendar.getInstance(); chooserCalendar.setTimeInMillis(chooserDate.getTime()); intDayOfMonth = chooserCalendar.get(Calendar.DAY_OF_MONTH); dateTable.setModel(dateTableModel); dateTable.setDefaultRenderer(javax.swing.JLabel.class, new CellRenderer()); dateTable.setTableHeader(null); // Set the column width to 25 pixels wide for (int i = 0; i < COLUMN_COUNT; i++) { TableColumn column = dateTable.getColumnModel().getColumn(i); int width = PREFERRED_COLUMN_WIDTH; column.setPreferredWidth(width); } // Only allow a single cell to be selected. dateTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); dateTable.getColumnModel().getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); initSelectionListener(); monthComboBox.setModel(new MonthComboBoxModel()); monthComboBox.setSelectedIndex(chooserCalendar.get(Calendar.MONTH)); populateTimeSpinner(); populateYearSpinner(); selectDateCell(chooserCalendar.get(Calendar.DAY_OF_MONTH)); } private void populateYearSpinner() { // One year in the future. // <editor-fold> Date maxDate = new Date(chooserDate.getTime() + (1000L * 60L * 60L * 24L * 365L)); // 10 years in the past. Date minDate = new Date(chooserDate.getTime() - (1000L * 60L * 60L * 24L * 365L * 10L)); // </editor-fold> SpinnerDateModel spinnerDateModel = new SpinnerDateModel(chooserCalendar.getTime(), minDate, maxDate, Calendar.YEAR); yearSpinner.setModel(spinnerDateModel); // Define the editor so we'll show only the year. JSpinner.DateEditor dateEditor = new JSpinner.DateEditor(yearSpinner, "yyyy"); yearSpinner.setEditor(dateEditor); dateEditor.getTextField().addKeyListener(new KeyAdapter() { @Override public void keyTyped(KeyEvent e) { int keyCode = e.getKeyCode(); switch (keyCode) { case KeyEvent.VK_UP: case KeyEvent.VK_DOWN: case KeyEvent.VK_LEFT: case KeyEvent.VK_RIGHT: { break; } default: { e.consume(); } } } }); } private void populateTimeSpinner() { SpinnerDateModel spinnerDateModel = new SpinnerDateModel(chooserCalendar.getTime(), null, null, Calendar.MINUTE); timeSpinner.setModel(spinnerDateModel); // Define the editor so we'll show only the time. JSpinner.DateEditor timeEditor = new JSpinner.DateEditor(timeSpinner, "hh:mm:ss aa"); timeSpinner.setEditor(timeEditor); timeEditor.getTextField().addKeyListener(new KeyAdapter() { @Override public void keyTyped(KeyEvent e) { int keyCode = e.getKeyCode(); switch (keyCode) { case KeyEvent.VK_UP: case KeyEvent.VK_DOWN: case KeyEvent.VK_LEFT: case KeyEvent.VK_RIGHT: { break; } default: { e.consume(); } } } }); } private void initSelectionListener() { TableSelectionListener tableSelectionListener = new TableSelectionListener(); // Listen for row changes. dateTable.getSelectionModel().addListSelectionListener(tableSelectionListener); // Listen for column changes. dateTable.getColumnModel().getSelectionModel().addListSelectionListener(tableSelectionListener); } private void selectCell(int rowIndex, int columnIndex) { dateTable.changeSelection(rowIndex, columnIndex, false, false); } private void selectDateCell(int dayOfMonthValue) { // Find the cell for the given day of month // We'll do this brute force, for now boolean continueFlag = true; DateTableModel dataModel = (DateTableModel) dateTable.getModel(); for (int row = 1; continueFlag && (row < ROW_COUNT); row++) { for (int column = 0; column < COLUMN_COUNT; column++) { JLabel dayOfMonth = (JLabel) dataModel.getValueAt(row, column); if (dayOfMonth.getText().length() > 0) { int dom = Integer.valueOf(dayOfMonth.getText()).intValue(); if (dom == dayOfMonthValue) { selectCell(row, column); continueFlag = false; break; } } } } } private String computeDayOfMonthAtRowAndColumn(int rowIndex, int columnIndex) { // Default to an empty string (for beginning and end of month). String returnValue = ""; // Figure out the month index... int monthIndex = columnIndex + ((rowIndex - 1) * COLUMN_COUNT); Calendar calendar = Calendar.getInstance(); calendar.setTime(chooserCalendar.getTime()); calendar.set(Calendar.DAY_OF_MONTH, 1); int dayOfWeekOfFirst = calendar.get(Calendar.DAY_OF_WEEK) - 1; int dayOfMonthIndex = monthIndex - dayOfWeekOfFirst; int dayOfMonth = dayOfMonthIndex + 1; int year = calendar.get(Calendar.YEAR); if (monthIndex >= dayOfWeekOfFirst) { returnValue = Integer.toString(dayOfMonth); } int month = calendar.get(Calendar.MONTH); switch (month) { case Calendar.JANUARY: case Calendar.MARCH: case Calendar.MAY: case Calendar.JULY: case Calendar.AUGUST: case Calendar.OCTOBER: case Calendar.DECEMBER: if (dayOfMonth > 31) { returnValue = ""; } break; case Calendar.APRIL: case Calendar.JUNE: case Calendar.SEPTEMBER: case Calendar.NOVEMBER: if (dayOfMonth > 30) { returnValue = ""; } break; default: case Calendar.FEBRUARY: if ((year % 4) == 0) { if (dayOfMonth > 29) { returnValue = ""; } } else { if (dayOfMonth > 28) { returnValue = ""; } } break; } return returnValue; } class TableSelectionListener implements ListSelectionListener { @Override public void valueChanged(ListSelectionEvent listSelectionEvent) { if (listSelectionEvent.getValueIsAdjusting()) { return; } ListSelectionModel listSelectionModel = (ListSelectionModel) listSelectionEvent.getSource(); DateTableModel dataModel = (DateTableModel) dateTable.getModel(); if (!listSelectionModel.isSelectionEmpty()) { if (listSelectionEvent.getFirstIndex() >= 0) { int selectedRow = dateTable.getSelectedRow(); if (selectedRow > 0) { JLabel dayOfMonth = (JLabel) dataModel.getValueAt(selectedRow, dateTable.getSelectedColumn()); if (dayOfMonth.getText().length() > 0) { intDayOfMonth = Integer.valueOf(dayOfMonth.getText()).intValue(); chooserCalendar.set(Calendar.DAY_OF_MONTH, intDayOfMonth); } else { selectDateCell(intDayOfMonth); } } else { selectDateCell(intDayOfMonth); } } } } } class DateTableModel extends AbstractTableModel { private static final long serialVersionUID = 1L; private static final int SUNDAY_COLUMN_INDEX = 0; private static final int MONDAY_COLUMN_INDEX = 1; private static final int TUESDAY_COLUMN_INDEX = 2; private static final int WEDNESDAY_COLUMN_INDEX = 3; private static final int THURSDAY_COLUMN_INDEX = 4; private static final int FRIDAY_COLUMN_INDEX = 5; private static final int SATURDAY_COLUMN_INDEX = 6; JLabel m_ValueAtLabel = new JLabel(); public DateTableModel() { super(); } @Override public int getRowCount() { return ROW_COUNT; } @Override public int getColumnCount() { return 7; } /** * Returns the lowest common denominator Class in the column. This is used by the table to set up a default renderer and editor for the column. * * @return the common ancestor class of the object values in the model. */ @Override public Class getColumnClass(int columnIndex) { return javax.swing.JLabel.class; } @Override public Object getValueAt(int rowIndex, int columnIndex) { if (rowIndex == 0) { switch (columnIndex) { case SUNDAY_COLUMN_INDEX: m_ValueAtLabel.setText(daysOfWeek[Calendar.SUNDAY].substring(0, 1)); break; case MONDAY_COLUMN_INDEX: m_ValueAtLabel.setText(daysOfWeek[Calendar.MONDAY].substring(0, 1)); break; case TUESDAY_COLUMN_INDEX: m_ValueAtLabel.setText(daysOfWeek[Calendar.TUESDAY].substring(0, 1)); break; case WEDNESDAY_COLUMN_INDEX: m_ValueAtLabel.setText(daysOfWeek[Calendar.WEDNESDAY].substring(0, 1)); break; case THURSDAY_COLUMN_INDEX: m_ValueAtLabel.setText(daysOfWeek[Calendar.THURSDAY].substring(0, 1)); break; case FRIDAY_COLUMN_INDEX: m_ValueAtLabel.setText(daysOfWeek[Calendar.FRIDAY].substring(0, 1)); break; case SATURDAY_COLUMN_INDEX: default: m_ValueAtLabel.setText(daysOfWeek[Calendar.SATURDAY].substring(0, 1)); break; } } else { m_ValueAtLabel.setText(computeDayOfMonthAtRowAndColumn(rowIndex, columnIndex)); } return m_ValueAtLabel; } } class CellRenderer extends javax.swing.table.DefaultTableCellRenderer { private static final long serialVersionUID = -8022231309078117375L; @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { JLabel inputLabel = (JLabel) dateTableModel.getValueAt(row, column); String text = inputLabel.getText(); // Set the column alignment. switch (column) { default: setHorizontalAlignment(javax.swing.SwingConstants.CENTER); break; } if (isSelected && (text.length() > 0)) { super.setForeground(table.getSelectionForeground()); super.setBackground(table.getSelectionBackground()); } else { super.setForeground(table.getForeground()); super.setBackground(table.getBackground()); } setFont(table.getFont()); if (hasFocus && (row != 0) && (text.length() > 0)) { setBorder(UIManager.getBorder("Table.focusCellHighlightBorder")); if (table.isCellEditable(row, column)) { super.setForeground(UIManager.getColor("Table.focusCellForeground")); super.setBackground(UIManager.getColor("Table.focusCellBackground")); } } else { setBorder(noFocusBorder); } setText(text); setIcon(null); if (row == 0) { super.setForeground(table.getSelectionForeground()); super.setBackground(table.getSelectionBackground()); } return this; } } class MonthComboBoxModel extends DefaultComboBoxModel<String> { private static final long serialVersionUID = 1L; public MonthComboBoxModel() { String[] months = dateFormatSymbols.getMonths(); for (int i = 0; i < 12; i++) { addElement(months[i]); } } } /** * This method is called from within the constructor to initialize the form. WARNING: Do NOT modify this code. The content of this method is always regenerated by the Form Editor. */ // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { datePanel = new javax.swing.JPanel(); monthYearPanel = new javax.swing.JPanel(); monthComboBox = new javax.swing.JComboBox(); yearSpinner = new javax.swing.JSpinner(); dateTablePanel = new javax.swing.JPanel(); dateTable = new javax.swing.JTable(); timePanel = new javax.swing.JPanel(); timeSpinner = new javax.swing.JSpinner(); setLayout(new java.awt.BorderLayout(5, 5)); datePanel.setBorder(javax.swing.BorderFactory.createTitledBorder(new javax.swing.border.LineBorder(new java.awt.Color(0, 0, 0), 1, true), "Date")); datePanel.setLayout(new java.awt.BorderLayout(0, 5)); monthYearPanel.setLayout(new java.awt.BorderLayout(5, 0)); monthComboBox.setModel(new javax.swing.DefaultComboBoxModel(new String[]{"Item 1", "Item 2", "Item 3", "Item 4"})); monthComboBox.setToolTipText("Choose month"); monthComboBox.addItemListener(new java.awt.event.ItemListener() { public void itemStateChanged(java.awt.event.ItemEvent evt) { monthComboBoxItemStateChanged(evt); } }); monthYearPanel.add(monthComboBox, java.awt.BorderLayout.WEST); yearSpinner.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N yearSpinner.setToolTipText("Choose year"); yearSpinner.addChangeListener(new javax.swing.event.ChangeListener() { public void stateChanged(javax.swing.event.ChangeEvent evt) { yearSpinnerStateChanged(evt); } }); monthYearPanel.add(yearSpinner, java.awt.BorderLayout.EAST); datePanel.add(monthYearPanel, java.awt.BorderLayout.NORTH); dateTablePanel.setLayout(new java.awt.BorderLayout()); dateTable.setModel(new DateTableModel()); dateTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_OFF); dateTable.setAutoscrolls(false); dateTable.setCellSelectionEnabled(true); dateTable.setDoubleBuffered(true); dateTable.setShowHorizontalLines(false); dateTable.setShowVerticalLines(false); dateTablePanel.add(dateTable, java.awt.BorderLayout.CENTER); datePanel.add(dateTablePanel, java.awt.BorderLayout.CENTER); add(datePanel, java.awt.BorderLayout.NORTH); timePanel.setBorder(javax.swing.BorderFactory.createTitledBorder(new javax.swing.border.LineBorder(new java.awt.Color(0, 0, 0), 1, true), "Time")); timePanel.setLayout(new java.awt.BorderLayout(0, 5)); timeSpinner.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N timeSpinner.setToolTipText("Choose time"); timeSpinner.addChangeListener(new javax.swing.event.ChangeListener() { public void stateChanged(javax.swing.event.ChangeEvent evt) { timeSpinnerStateChanged(evt); } }); timePanel.add(timeSpinner, java.awt.BorderLayout.CENTER); add(timePanel, java.awt.BorderLayout.SOUTH); }// </editor-fold>//GEN-END:initComponents private void monthComboBoxItemStateChanged(java.awt.event.ItemEvent evt)//GEN-FIRST:event_monthComboBoxItemStateChanged {//GEN-HEADEREND:event_monthComboBoxItemStateChanged int index = monthComboBox.getSelectedIndex(); chooserCalendar.set(Calendar.MONTH, index); dateTableModel.fireTableDataChanged(); selectDateCell(chooserCalendar.get(Calendar.DAY_OF_MONTH)); }//GEN-LAST:event_monthComboBoxItemStateChanged private void timeSpinnerStateChanged(javax.swing.event.ChangeEvent evt)//GEN-FIRST:event_timeSpinnerStateChanged {//GEN-HEADEREND:event_timeSpinnerStateChanged Date time = (Date) timeSpinner.getModel().getValue(); Calendar calendar = Calendar.getInstance(); calendar.setTime(time); chooserCalendar.set(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY)); chooserCalendar.set(Calendar.MINUTE, calendar.get(Calendar.MINUTE)); chooserCalendar.set(Calendar.SECOND, calendar.get(Calendar.SECOND)); }//GEN-LAST:event_timeSpinnerStateChanged private void yearSpinnerStateChanged(javax.swing.event.ChangeEvent evt)//GEN-FIRST:event_yearSpinnerStateChanged {//GEN-HEADEREND:event_yearSpinnerStateChanged Date year = (Date) yearSpinner.getModel().getValue(); Calendar calendar = Calendar.getInstance(); calendar.setTime(year); chooserCalendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR)); dateTableModel.fireTableDataChanged(); selectDateCell(chooserCalendar.get(Calendar.DAY_OF_MONTH)); }//GEN-LAST:event_yearSpinnerStateChanged // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JPanel datePanel; private javax.swing.JTable dateTable; private javax.swing.JPanel dateTablePanel; private javax.swing.JComboBox monthComboBox; private javax.swing.JPanel monthYearPanel; private javax.swing.JPanel timePanel; private javax.swing.JSpinner timeSpinner; private javax.swing.JSpinner yearSpinner; // End of variables declaration//GEN-END:variables }