/*
* Rapid Beans Framework: EditorPropertyDateSwing.java
*
* Copyright (C) 2009 Martin Bluemel
*
* Creation Date: 02/13/2006
*
* This program 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.
* 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 Lesser General Public License for more details.
* You should have received a copies of the GNU Lesser General Public License and the
* GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
package org.rapidbeans.presentation.swing;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import javax.swing.JTextField;
import org.rapidbeans.core.basic.Property;
import org.rapidbeans.core.basic.PropertyDate;
import org.rapidbeans.core.common.PrecisionDate;
import org.rapidbeans.core.exception.RapidBeansRuntimeException;
import org.rapidbeans.core.exception.ValidationException;
import org.rapidbeans.core.type.TypePropertyDate;
import org.rapidbeans.presentation.Application;
import org.rapidbeans.presentation.EditorBean;
import org.rapidbeans.presentation.config.ConfigPropEditorBean;
/**
* a special property editor for Date properties. Has a text field and later on
* will be extended with a calendar widget.
*
* @author Martin Bluemel
*/
public class EditorPropertyDateSwing extends EditorPropertySwing {
/**
* the text field.
*/
private JTextField text = new JTextField();
/**
* @return the editor's widget
*/
public Object getWidget() {
return this.text;
}
/**
* constructor.
*
* @param prop
* the bean property to edit
* @param propBak
* the bean property backup
* @param bizBeanEditor
* the parent bean editor
* @param client
* the client
*/
public EditorPropertyDateSwing(final Application client, final EditorBean bizBeanEditor, final Property prop,
final Property propBak) {
super(client, bizBeanEditor, prop, propBak);
super.initColors();
if (!(prop instanceof PropertyDate)) {
throw new RapidBeansRuntimeException("invalid propperty for a date editor");
}
if (prop.getType().isKeyCandidate() && (!this.getBeanEditor().isInNewMode())) {
this.text.setEditable(false);
}
this.text.addKeyListener(new KeyListener() {
@Override
public void keyTyped(final KeyEvent e) {
}
@Override
public void keyPressed(final KeyEvent e) {
}
@Override
public void keyReleased(final KeyEvent e) {
handleKeyReleased(e);
}
});
this.text.addFocusListener(new FocusListener() {
@Override
public void focusLost(FocusEvent arg0) {
handleFocusLost();
}
@Override
public void focusGained(FocusEvent arg0) {
}
});
this.updateUI();
final ConfigPropEditorBean cfg = getConfig();
if (prop.getReadonly() || (cfg != null && !cfg.getEnabled())) {
this.text.setEnabled(false);
}
}
public void handleKeyReleased(final KeyEvent e) {
try {
switch (e.getKeyCode()) {
case KeyEvent.VK_ENTER:
case KeyEvent.VK_DOWN:
case KeyEvent.VK_UP:
case KeyEvent.VK_TAB:
break;
default:
setUIUpdateLock();
break;
}
fireInputFieldChanged();
} finally {
switch (e.getKeyCode()) {
case KeyEvent.VK_ENTER:
case KeyEvent.VK_DOWN:
case KeyEvent.VK_UP:
case KeyEvent.VK_TAB:
updateUI();
break;
default:
releaseUIUpdateLock();
break;
}
}
switch (e.getKeyCode()) {
case KeyEvent.VK_ENTER:
case KeyEvent.VK_DOWN:
getBeanEditor().rotateFocus(getProperty(), EditorBean.DIRECTION_DOWN);
break;
case KeyEvent.VK_UP:
getBeanEditor().rotateFocus(getProperty(), EditorBean.DIRECTION_UP);
break;
default:
break;
}
}
public void handleFocusLost() {
final Date dateOld = ((PropertyDate) getProperty()).getValue();
final Date dateNew = (Date) getInputFieldValue();
if ((dateOld == null && dateNew != null) || (dateOld != null && dateNew == null)
&& (dateOld != null && dateNew != null && !dateOld.equals(dateNew))) {
fireInputFieldChanged();
} else if (dateOld != null && dateNew != null && dateOld.equals(dateNew)
&& !this.text.equals(getInputFieldValueString())) {
updateUI();
}
}
/**
* update the string presented in the editor.
*/
public void updateUI() {
if (this.getUIUpdateLock()) {
return;
}
try {
this.inputFieldValueCompleted = false;
this.setUIEventLock();
final Object value = this.getProperty().getValue();
String newText = "";
if (value != null) {
newText = DateFormat.getDateInstance(DateFormat.MEDIUM, this.getLocale().getLocale()).format(value);
}
if (!this.text.getText().equals(newText)) {
this.text.setText(newText);
}
} finally {
this.releaseUIEventLock();
}
}
/**
* @return the Text field's content
*/
public Object getInputFieldValue() {
Date ifValue = null;
String s = this.text.getText();
if (s.trim().length() > 0) {
if (this.getLocale().getName().equals("de")) {
ifValue = readDate();
} else {
try {
ifValue = DateFormat.getDateInstance(DateFormat.MEDIUM, this.getLocale().getLocale()).parse(s);
} catch (ParseException e) {
throw new ValidationException(SIG_DATE_INVALID_FORMAT, this,
"invalid localized medium date string \"" + s + "\"" + " for locale "
+ this.getLocale().getLocale().getDisplayName());
}
}
}
return ifValue;
}
/**
* parse a gemen medium date.
*
* [d]d.[m]m.[y][y][y]y
*
* @return a date
*/
private Date readDate() {
this.inputFieldValueCompleted = false;
ParsedDate pd = this.parseLocalDate(true);
this.inputFieldValueCompleted = pd.isCompleted();
return pd.toDate(((TypePropertyDate) ((PropertyDate) this.getProperty()).getType()).getPrecision());
}
/**
* signature for validation exception because of wrong date format.
*/
public static final String SIG_DATE_INVALID_FORMAT = "invalid.prop.date.string.local.format";
/**
* signature for validation exception because of wrong date format.
*/
public static final String SIG_DATE_INVALID_ICOMPLETE = "invalid.prop.date.string.local.incomplete";
/**
* signature for validation exception because of wrong date format.
*/
public static final String SIG_DATE_INVALID_DAY = "invalid.prop.date.string.local.day";
/**
* signature for validation exception because of wrong date format.
*/
public static final String SIG_DATE_INVALID_MONTH = "invalid.prop.date.string.local.month";
/**
* signature for validation exception because of wrong date format.
*/
public static final String SIG_DATE_INVALID_YEAR = "invalid.prop.date.string.local.year";
/**
* validate an input field.
*
* @return if the string in the input field is valid or at least could at
* least get after appending additional characters.
*
* @param ex
* the validation exception
*/
protected boolean hasPotentiallyValidInputField(final ValidationException ex) {
if (ex.getSignature().endsWith("incomplete")) {
return this.checkLocalDate(false);
} else {
return this.inputFieldValueCompleted;
}
}
/**
* show if the input field valuehas been expanded.
*/
private boolean inputFieldValueCompleted = false;
/**
* @return the inputFieldValueCompleted
*/
protected boolean isInputFieldValueCompleted() {
return inputFieldValueCompleted;
}
/**
* check the local date.
*
* @param completenessRequired
* if completeness is required
*
* @return if the local date is ok
*/
protected boolean checkLocalDate(final boolean completenessRequired) {
boolean ok = true;
try {
parseLocalDate(completenessRequired);
} catch (ValidationException e) {
ok = false;
}
return ok;
}
/**
* A helper class for date parsing.
*
* @author Martin Bluemel
*/
private class ParsedDate {
/**
* the day.
*/
private int day = -1;
/**
* the month.
*/
private int month = -1;
/**
* the year.
*/
private int year = -1;
/**
* the day.
*/
private String sday = null;
/**
* the month.
*/
private String smonth = null;
/**
* the year.
*/
private String syear = null;
/**
* the ok flag.
*/
private boolean ok = false;
/**
* the locale.
*/
private Locale locale = null;
/**
* the date string.
*/
private String sDate = null;
/**
* the flag that shows if completion has been performed.
*/
private boolean completed = false;
/**
* getter.
*
* @return the flag that shows if completion has been performed
*/
public boolean isCompleted() {
return this.completed;
}
/**
* constructor.
*
* @param s
* the date string
* @param l
* the locale
*/
ParsedDate(final String s, final Locale l) {
this.sDate = s;
this.locale = l;
}
/**
* @param s
* the day String to set
*/
public void setDay(final String s) {
this.sday = s;
try {
this.day = Integer.parseInt(s);
if (s.length() < 2) {
this.completed = true;
}
} catch (NumberFormatException e) {
throw new ValidationException(SIG_DATE_INVALID_DAY, this, "Invalid day in localized date string \""
+ this.sDate + "\"" + " for locale " + this.locale.getDisplayName());
}
}
/**
* @param s
* the month to set
*/
public void setMonth(final String s) {
this.smonth = s;
try {
this.month = Integer.parseInt(s);
if (s.length() < 2) {
this.completed = true;
}
} catch (NumberFormatException e) {
throw new ValidationException(SIG_DATE_INVALID_MONTH, this, "Invalid month in localized date string \""
+ this.sDate + "\"" + " for locale " + this.locale.getDisplayName());
}
}
// /**
// * @param o the ok to set
// */
// public void setOk(final boolean o) {
// this.ok = o;
// }
/**
* @param s
* the year to set
*/
public void setYear(final String s) {
this.syear = s;
try {
this.year = Integer.parseInt(s);
if (this.year >= 0 && this.year <= 19) {
this.year += 2000;
this.syear = Integer.toString(this.year);
this.sDate = this.sday + '.' + this.smonth + "." + this.syear;
} else if (this.year >= 20 && this.year <= 99) {
this.year += 1900;
this.syear = Integer.toString(this.year);
this.sDate = this.sday + '.' + this.smonth + "." + this.syear;
} else if (this.year >= 100 && this.year <= 999) {
if (this.syear.length() == 3) {
this.syear = '0' + this.syear;
this.sDate = this.sday + '.' + this.smonth + "." + this.syear;
}
}
if (s.length() < 4) {
this.completed = true;
}
} catch (NumberFormatException e) {
throw new ValidationException(SIG_DATE_INVALID_YEAR, this, "Invalid year in localized date string \""
+ this.sDate + "\"" + " for locale " + this.locale.getDisplayName());
}
}
/**
* conversion to a date.
*
* @param precision
* the precision for the date
*
* @return the date
*/
public Date toDate(final PrecisionDate precision) {
if (!this.ok) {
throw new ValidationException(SIG_DATE_INVALID_FORMAT, this, "invalid localized medium date string \""
+ this.sDate + "\" for locale " + this.locale.getDisplayName());
}
GregorianCalendar cal = new GregorianCalendar();
cal.set(this.year, this.month - 1, this.day);
return new Date(PropertyDate.cutPrecisionLong(cal.getTimeInMillis(), precision));
}
/**
* validate the date.
*
* @param completenessRequired
* if completeness is required
*/
public void validate(final boolean completenessRequired) {
if (completenessRequired) {
if (this.syear == null) {
throw new ValidationException(SIG_DATE_INVALID_ICOMPLETE, this,
"Incomplete localized medium date string \"" + this.sDate + "\"" + " for locale "
+ this.locale.getDisplayName());
}
}
if (this.sday != null) {
if (this.smonth != null) {
if (this.syear != null) { // (1,1,1)
if (this.year < 0 || this.year > 9999) {
throw new ValidationException(SIG_DATE_INVALID_YEAR, this,
"Invalid year in localized date string \"" + this.sDate + "\"" + " for locale "
+ this.locale.getDisplayName());
}
if (this.month == 2 && this.day == 29) {
DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, this.locale);
GregorianCalendar cal = new GregorianCalendar();
cal.set(this.year, this.month - 1, this.day);
String normFormat = df.format(cal.getTime());
if (!(normFormat.equals(sDate))) {
throw new ValidationException(SIG_DATE_INVALID_DAY, this,
"Invalid day february 29th in localized date string \"" + this.sDate + "\""
+ " for locale " + this.locale.getDisplayName());
}
}
}
// (1,1,x)
if (this.month == 0) {
if (this.smonth.length() == 1) {
if (completenessRequired) {
throw new ValidationException(SIG_DATE_INVALID_MONTH, this,
"Invalid month localized date string \"" + this.sDate + "\"" + " for locale "
+ this.locale.getDisplayName());
}
} else {
throw new ValidationException(SIG_DATE_INVALID_MONTH, this,
"Invalid month in localized date string \"" + this.sDate + "\"" + " for locale "
+ this.locale.getDisplayName());
}
}
if (this.month < 0 || this.month > 12) {
throw new ValidationException(SIG_DATE_INVALID_MONTH, this,
"Invalid month in localized date string \"" + this.sDate + "\"" + " for locale "
+ this.locale.getDisplayName());
}
switch (this.month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
if (this.day < 1 || this.day > 31) {
throw new ValidationException(SIG_DATE_INVALID_DAY, this,
"Invalid day in localized date string \"" + this.sDate + "\"" + " for locale "
+ this.locale.getDisplayName());
}
break;
case 4:
case 6:
case 9:
case 11:
if (this.day < 1 || this.day > 30) {
throw new ValidationException(SIG_DATE_INVALID_DAY, this,
"Invalid day in localized date string \"" + this.sDate + "\"" + " for locale "
+ this.locale.getDisplayName());
}
break;
case 2:
if (this.day < 1 || this.day > 29) {
throw new ValidationException(SIG_DATE_INVALID_DAY, this,
"Invalid day in localized date string \"" + this.sDate + "\"" + " for locale "
+ this.locale.getDisplayName());
}
break;
default:
break;
}
} else { // (1,0,0)
if (this.day == 0) {
if (this.sday.length() == 1) {
if (completenessRequired) {
throw new ValidationException(SIG_DATE_INVALID_DAY, this,
"Invalid day in localized date string \"" + this.sDate + "\"" + " for locale "
+ this.locale.getDisplayName());
}
} else {
throw new ValidationException(SIG_DATE_INVALID_DAY, this,
"Invalid day in localized date string \"" + this.sDate + "\"" + " for locale "
+ this.locale.getDisplayName());
}
}
if (this.day < 0 || this.day > 31) {
throw new ValidationException(SIG_DATE_INVALID_DAY, this,
"Invalid day in localized date string \"" + this.sDate + "\"" + " for locale "
+ this.locale.getDisplayName());
}
}
}
this.ok = true;
}
}
/**
* validate the date field.
*
* @param completenessRequired
* if the input fields must be complete
*
* @return if the string in the date field is valid or at least could at
* least get after appending additional characters.
*/
private ParsedDate parseLocalDate(final boolean completenessRequired) {
ParsedDate date = new ParsedDate(this.text.getText(), this.getLocale().getLocale());
if (this.getLocale().getName().equals("de")) {
String s = this.text.getText();
int len = s.length();
char c;
StringBuffer sb = new StringBuffer();
int state = 0;
int j = 0;
for (int i = 0; i < len; i++) {
c = s.charAt(i);
switch (state) {
case 0:
case 1:
case 2:
if (c >= '0' && c <= '9') {
if (((state == 2) && (i >= (j + 1) && i <= (j + 4))) || ((state == 0) && (i >= 0 && i <= 1))
|| ((state == 1) && (i >= (j + 1) && i <= (j + 2)))) {
sb.append(c);
} else {
throw new ValidationException(SIG_DATE_INVALID_FORMAT, this,
"invalid localized medium date string \"" + s + "\"" + " for locale "
+ this.getLocale().getLocale().getDisplayName());
}
} else if ((state == 0 || state == 1) && (c == '.')) {
j = i;
switch (state) {
case 0:
date.setDay(sb.toString());
break;
case 1:
date.setMonth(sb.toString());
break;
default:
throw new RapidBeansRuntimeException("Error during parsing a date");
}
sb.setLength(0);
state++;
} else {
throw new ValidationException(SIG_DATE_INVALID_FORMAT, this,
"invalid localized medium date string \"" + s + "\"" + " for locale "
+ this.getLocale().getLocale().getDisplayName());
}
break;
default:
throw new RapidBeansRuntimeException("unexcpected state " + state);
}
}
if (sb.length() > 0) {
switch (state) {
case 0:
date.setDay(sb.toString());
break;
case 1:
date.setMonth(sb.toString());
break;
case 2:
date.setYear(sb.toString());
break;
default:
throw new RapidBeansRuntimeException("Error during parsing a date");
}
}
date.validate(completenessRequired);
}
return date;
}
/**
* @return the input field value as string.
*/
public String getInputFieldValueString() {
return this.text.getText();
}
}