- import java.awt.Color;
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- import java.awt.event.KeyEvent;
- import java.beans.PropertyChangeEvent;
- import java.beans.PropertyChangeListener;
- import java.time.Instant;
- import java.util.Calendar;
- import java.util.GregorianCalendar;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- import javax.accessibility.Accessible;
- import javax.swing.AbstractAction;
- import javax.swing.JComboBox;
- import javax.swing.JComponent;
- import javax.swing.JPopupMenu;
- import javax.swing.JTextField;
- import javax.swing.KeyStroke;
- import javax.swing.event.DocumentEvent;
- import javax.swing.event.DocumentListener;
- import javax.swing.event.PopupMenuEvent;
- import javax.swing.event.PopupMenuListener;
- /*
- * カレンダー部品
- */
- public class JDateField extends JComboBox<String> implements CalendarListener{
- /*******************************************************************************
- * 定数
- ******************************************************************************/
- /*******************************************************************************
- * 部品
- ******************************************************************************/
- private JTextField textField = null;
- // private JButton buttonArrow = null;
- private JPopupMenu popupOrg = null;
- private JPopupMenu popup = null;
- private JCalendar calendar = null;
- private JCalendar.Style style = JCalendar.Style.Button;
- private boolean allowNoYear = false;
- private GregorianCalendar calMinDate = null;
- private GregorianCalendar calMaxDate = null;
- private Instant last_shown = null;
- /*******************************************************************************
- * コンストラクタ
- ******************************************************************************/
- public JDateField() {
- this(JCalendar.Style.Button);
- }
- public JDateField(JCalendar.Style s){
- super();
- style = s;
- setEditable(true);
- setMaximumRowCount(0);
- createComponents();
- }
- /*******************************************************************************
- * 公開メソッド
- ******************************************************************************/
- /*
- * ポップアップを表示する
- *
- * @see javax.swing.JComboBox#showPopup()
- */
- @Override
- public void showPopup(){
- setSelectedDate(getCalendar(getText()));
- if (last_shown != null && Instant.now().minusMillis(100).isBefore(last_shown))
- return;
- popup.show(this, 0, getBounds().height);
- last_shown = Instant.now();
- }
- /*
- * ポップアップを非表示にする
- *
- * @see javax.swing.JComboBox#hidePopup()
- */
- @Override
- public void hidePopup(){
- if (popup.isVisible())
- popup.setVisible(false);
- }
- /*
- * 年なしを許すかをセットする
- */
- public void setAllowNoYear(boolean b){
- allowNoYear = b;
- }
- /*
- * アクションリスナーを追加する
- */
- @Override
- public void addActionListener(ActionListener al){
- textField.addActionListener(al);
- }
- /*
- * 選択日をYYYY/MM/DD形式で指定する
- */
- public void setSelectedDate(String date){
- setSelectedDate(getCalendar(date));
- }
- /*
- * 選択日をGregorianCalendarクラスで指定する
- */
- public void setSelectedDate(GregorianCalendar cal){
- cleanUpCalendarTime(cal);
- if (calendar != null)
- calendar.setSelectedDate(cal);
- }
- /*
- * 有効な最小日をYYYY/MM/DD形式で指定する
- */
- public void setMinDate(String date){
- setMinDate(getCalendar(date));
- }
- /*
- * 有効な最小日をGregorianCalendarクラスで指定する
- */
- public void setMinDate(GregorianCalendar cal){
- cleanUpCalendarTime(cal);
- calMinDate = cal;
- if (calendar != null)
- calendar.setMinDate(cal);
- }
- /*
- * 有効な最大日をYYYY/MM/DD形式で指定する
- */
- public void setMaxDate(String date){
- setMaxDate(getCalendar(date));
- }
- /*
- * 有効な最大日をGregorianCalendarクラスで指定する
- */
- public void setMaxDate(GregorianCalendar cal){
- cleanUpCalendarTime(cal);
- calMaxDate = cal;
- if (calendar != null)
- calendar.setMaxDate(cal);
- }
- /*
- * テキストを取得する
- */
- public String getText(){
- return textField.getText();
- }
- /*
- * テキストをセットする
- */
- public void setText(String s){
- setSelectedItem(s);
- setSelectedDate(getCalendar(s));
- }
- /*******************************************************************************
- * コンポーネント
- ******************************************************************************/
- /*
- * コンポーネントを作成する
- */
- private void createComponents(){
- // オリジナルのポップアップを取得する
- Accessible accessible = this.getUI().getAccessibleChild(this, 0);
- if (accessible instanceof JPopupMenu ){
- popupOrg = (JPopupMenu) accessible;
- popupOrg.addPropertyChangeListener(pcl_popupOrg);
- }
- // テキストフィールドを取得する
- try{
- // buttonArrow = (JButton)this.getComponent(0);
- textField = (JTextField) this.getEditor().getEditorComponent();
- textField.getDocument().addDocumentListener(dl_textField);
- }
- catch(ClassCastException e){
- }
- // カレンダーのポップアップを作成する
- calendar = new JCalendar(style, false);
- calendar.setListener(this);
- String ESCKEYACTION = "escape";
- calendar.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
- .put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), ESCKEYACTION);
- calendar.getActionMap().put(ESCKEYACTION, aa_calendarEscape);
- popup = new JPopupMenu();
- popup.add(calendar);
- popup.addPopupMenuListener(pml_popup);
- }
- /*******************************************************************************
- * リスナー
- ******************************************************************************/
- /*
- * オリジナルのポップアップのプロパティ変更リスナー
- */
- private PropertyChangeListener pcl_popupOrg = new PropertyChangeListener(){
- @Override
- public void propertyChange(PropertyChangeEvent evt) {
- // オリジナルが表示されたら強制的に非表示にして、カレンダーをポップアップする
- if (evt.getPropertyName().equals("visible") && evt.getNewValue() == Boolean.TRUE){
- popupOrg.setVisible(false);
- showPopup();
- }
- }
- };
- /*
- * カレンダーのポップアップのリスナー
- */
- private PopupMenuListener pml_popup = new PopupMenuListener(){
- @Override
- public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
- }
- @Override
- public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
- // 非表示になる時に時刻を保存する
- last_shown = Instant.now();
- }
- @Override
- public void popupMenuCanceled(PopupMenuEvent e) {
- }
- };
- /*
- * テキストフィールドの文書変更リスナー
- *
- * 文書が変更されたら前景色を更新してポップアップを非表示にする
- */
- private DocumentListener dl_textField = new DocumentListener(){
- @Override
- public void insertUpdate(DocumentEvent e) {
- updateTextFieldForeground();
- hidePopup();
- }
- @Override
- public void removeUpdate(DocumentEvent e) {
- updateTextFieldForeground();
- hidePopup();
- }
- @Override
- public void changedUpdate(DocumentEvent e) {
- updateTextFieldForeground();
- hidePopup();
- }
- };
- /*
- * カレンダーの時刻変更リスナー
- *
- * @see tainavi.CalendarListener#notifyDateChange(tainavi.JCalendar, java.lang.String)
- */
- @Override
- public void notifyDateChange(JCalendar cal, String date) {
- // テキストをセットしてポップアップを非表示にする
- setText(date);
- hidePopup();
- }
- /*
- * カレンダーをESCキーで抜けるための仮想アクション
- */
- private AbstractAction aa_calendarEscape = new AbstractAction(){
- @Override
- public void actionPerformed(ActionEvent e) {
- hidePopup();
- }
- };
- /*******************************************************************************
- * 内部関数
- ******************************************************************************/
- /*
- * テキストフィールドの前景色を更新する
- */
- private void updateTextFieldForeground(){
- textField.setForeground(isValidDate((textField.getText())) ? Color.BLACK : Color.RED);
- }
- /*
- * 指定された文字列が有効な日付かどうかを返す
- */
- private boolean isValidDate(String s){
- if (s == null)
- return false;
- if (s.isEmpty())
- return true;
- // YYYY/MM/DD形式であること
- GregorianCalendar cal = getCalendar(s);
- if (cal == null)
- return false;
- // 最小値、最大値の間にあること
- if (!isValidDateRange(cal))
- return false;
- return true;
- }
- /*
- * 指定された日が最小値、最大値の間にあるかを返す
- */
- private boolean isValidDateRange(GregorianCalendar cal){
- long time = cal.getTimeInMillis();
- if (calMinDate != null){
- long timeMin = calMinDate.getTimeInMillis();
- if (time < timeMin)
- return false;
- }
- if (calMaxDate != null){
- long timeMax = calMaxDate.getTimeInMillis();
- if (time > timeMax)
- return false;
- }
- return true;
- }
- /*
- * カレンダーの時分秒をクリアする
- */
- private void cleanUpCalendarTime(GregorianCalendar cal){
- if (cal == null)
- return;
- cal.set(Calendar.HOUR, 0);
- cal.set(Calendar.MINUTE, 0);
- cal.set(Calendar.SECOND, 0);
- cal.set(Calendar.MILLISECOND, 0);
- }
- /*
- * YYYY/MM/DD, MM/DD形式からGregorianCalendarクラスを取得する
- */
- private GregorianCalendar getCalendar(String date) {
- if (date == null)
- return null;
- GregorianCalendar cal = new GregorianCalendar();
- int year, month, day;
- // YYYY/MM/DD形式
- Matcher ma = Pattern.compile("^(\\d{4})/(\\d{2})/(\\d{2})$").matcher(date);
- if ( ma.find()) {
- year = Integer.parseInt(ma.group(1));
- month = Integer.parseInt(ma.group(2));
- day = Integer.parseInt(ma.group(3));
- }
- else{
- if (!allowNoYear)
- return null;
- // MM/DD形式
- Matcher mb = Pattern.compile("^(\\d{2})/(\\d{2})$").matcher(date);
- if (mb.find()){
- year = cal.get(Calendar.YEAR);
- month = Integer.parseInt(mb.group(1));
- day = Integer.parseInt(mb.group(2));
- }
- else
- return null;
- }
- // 年、月、日の値が有効な範囲であること
- if (!isValidDate(year, month, day))
- return null;
- cal.set(Calendar.YEAR, year);
- cal.set(Calendar.MONTH, month-1);
- cal.set(Calendar.DATE, day);
- cleanUpCalendarTime(cal);
- return cal;
- }
- /*
- * 年、月、日の値が有効な範囲であるかを返す
- */
- private boolean isValidDate(int year, int month, int day){
- if (year < 1900 || year > 2100)
- return false;
- if (month < 1 || month > 12)
- return false;
- GregorianCalendar cal = new GregorianCalendar(year, month-1, 1, 0, 0, 0);
- if (day < 1 || day > cal.getActualMaximum(Calendar.DATE))
- return false;
- return true;
- }
- }