package org.aperteworkflow.view.impl.history;
import com.vaadin.data.Property;
import com.vaadin.ui.*;
import org.aperteworkflow.util.vaadin.VaadinUtility;
import org.aperteworkflow.util.vaadin.ui.AligningHorizontalLayout;
import org.aperteworkflow.util.vaadin.ui.OrderedLayoutFactory;
import org.aperteworkflow.util.vaadin.ui.date.OptionalDateField;
import org.vaadin.addon.customfield.CustomField;
import org.vaadin.hene.flexibleoptiongroup.FlexibleOptionGroup;
import org.vaadin.hene.flexibleoptiongroup.FlexibleOptionGroupItemComponent;
import org.vaadin.risto.stepper.IntStepper;
import pl.net.bluesoft.rnd.util.i18n.I18NSource;
import pl.net.bluesoft.util.eventbus.EventListener;
import pl.net.bluesoft.util.eventbus.listenables.Listenable;
import pl.net.bluesoft.util.eventbus.listenables.ListenableSupport;
import pl.net.bluesoft.util.lang.DateUtil;
import pl.net.bluesoft.util.lang.Transformer;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Date;
/**
* @author: amichalak@bluesoft.net.pl
*/
public class DateRangeField extends CustomField implements Listenable<DateRangeField.DateRangeListener> {
private ListenableSupport<DateRangeListener> listenable = ListenableSupport.strongListenable();
private I18NSource messageSource;
private OptionalDateField fromDate;
private OptionalDateField toDate;
private PopupDateField latestDate;
private IntStepper stepper;
private ConfigurableOptionGroupField<Mode> optionGroup;
public enum Mode {
RANGE, LATEST
}
private Mode[] allowedModes = new Mode[] {Mode.LATEST, Mode.RANGE};
private Mode currentMode = Mode.LATEST;
private boolean allowValueUpdate = false;
public DateRangeField(I18NSource messageSource) {
this.messageSource = messageSource;
init();
}
@Override
public Class<?> getType() {
return DateRange.class;
}
private void init() {
if (allowedModes.length == 0) {
throw new IllegalArgumentException("Allowed modes cannot be empty!");
}
VerticalLayout root = new VerticalLayout();
root.setWidth("100%");
root.setSpacing(true);
if (allowedModes.length > 1) {
optionGroup = new ConfigurableOptionGroupField<Mode>(Arrays.asList(allowedModes))
.setMultiSelect(false)
.setItemComponentGenerator(new ConfigurableOptionGroupField.ItemComponentGenerator<Mode>() {
@Override
public Component generate(FlexibleOptionGroup optionGroup, Mode itemId, FlexibleOptionGroupItemComponent itemComponent,
Transformer<Mode, String> itemCaptionResolver) {
AligningHorizontalLayout ahl = new AligningHorizontalLayout(Alignment.MIDDLE_LEFT);
ahl.setSpacing(true);
ahl.setMargin(false, true, false, false);
ahl.setData(itemCaptionResolver);
ahl.addComponent(itemComponent);
ahl.addComponent(createControlsForMode(itemId));
ahl.setData(itemComponent);
return ahl;
}
})
.init();
optionGroup.setValue(currentMode);
optionGroup.addOptionChangedListener(new ValueChangeListener() {
@Override
public void valueChange(Property.ValueChangeEvent event) {
currentMode = (Mode) event.getProperty().getValue();
updateValues();
}
});
root.addComponent(optionGroup);
}
else {
currentMode = allowedModes[0];
Component controls = createControlsForMode(currentMode);
root.addComponent(controls);
}
allowValueUpdate = true;
setCompositionRoot(root);
}
public void setOptionGroupLayoutFactory(OrderedLayoutFactory layoutFactory) {
optionGroup.setLayoutFactory(layoutFactory).init();
}
public void setAllowedModes(Mode[] allowedModes) {
this.allowedModes = new Mode[allowedModes.length];
System.arraycopy(allowedModes, 0, this.allowedModes, 0, allowedModes.length);
init();
}
public void setDateFormat(String dateFormat) {
this.fromDate.setDateFormat(dateFormat);
this.toDate.setDateFormat(dateFormat);
this.latestDate.setDateFormat(dateFormat);
}
public void setDateStepperMaxValue(int maxValue) {
stepper.setMaxValue(Math.max(maxValue, 0));
}
public void setDateStepperBaseValue(int value) {
stepper.setValue(value);
}
private HorizontalLayout createLatestDateControls() {
latestDate = new PopupDateField();
latestDate.setDateFormat(VaadinUtility.SIMPLE_DATE_FORMAT_STRING);
latestDate.setEnabled(false);
latestDate.setWidth("100px");
latestDate.setCaption(messageSource.getMessage("date.field.from"));
stepper = new IntStepper();
stepper.setMaxValue(15);
stepper.setMinValue(0);
stepper.setStepAmount(1);
stepper.setWidth("50px");
stepper.setImmediate(true);
stepper.setValue(5);
stepper.addListener(new ValueChangeListener() {
@Override
public void valueChange(Property.ValueChangeEvent event) {
updateLatestVisualDate();
updateValues();
}
});
updateLatestVisualDate();
return VaadinUtility.horizontalLayout(Alignment.MIDDLE_CENTER, new Label(messageSource.getMessage("date.field.latest")), stepper,
new Label(messageSource.getMessage("date.field.days")) {{
setWidth("20px");
}}, latestDate);
}
private void updateLatestVisualDate() {
int value = stepper.getValue() != null ? (Integer) stepper.getValue() : 0;
latestDate.setValue(DateUtil.addDays(new Date(), -value));
}
private HorizontalLayout createDateRangeControls() {
fromDate = new OptionalDateField(messageSource);
fromDate.setValue(DateUtil.addDays(new Date(), -5));
fromDate.addDateChangedListener(new ValueChangeListener() {
@Override
public void valueChange(Property.ValueChangeEvent event) {
updateValues();
}
});
toDate = new OptionalDateField(messageSource);
toDate.setValue(new Date());
toDate.addDateChangedListener(new ValueChangeListener() {
@Override
public void valueChange(Property.ValueChangeEvent event) {
updateValues();
}
});
fromDate.setCaption(messageSource.getMessage("date.field.from"));
toDate.setCaption(messageSource.getMessage("date.field.to"));
return VaadinUtility.horizontalLayout(Alignment.MIDDLE_CENTER, fromDate, toDate);
}
private Component createControlsForMode(Mode itemId) {
switch (itemId) {
case LATEST:
return createLatestDateControls();
case RANGE:
return createDateRangeControls();
}
throw new IllegalArgumentException("Cannot determine date range mode: " + itemId);
}
private void updateValues() {
if (allowValueUpdate) {
DateRange value = (DateRange) getValue();
if (value != null) {
value.setStartDate(getStartDate());
value.setEndDate(getEndDate());
value.setMode(currentMode);
}
else {
setValue(new DateRange(getStartDate(), getEndDate(), currentMode));
}
fireDateRangeChangedEvent();
}
}
@Override
protected void setInternalValue(Object newValue) {
if (!(newValue instanceof DateRange)) {
throw new IllegalArgumentException("Unable to handle non-date-range values");
}
super.setInternalValue(newValue);
updateValuesFromOriginal((DateRange) newValue);
}
private void updateValuesFromOriginal(DateRange originalValue) {
if (originalValue.getMode() == null) {
originalValue.setMode(Mode.RANGE);
}
allowValueUpdate = false;
currentMode = originalValue.getMode();
switch (currentMode) {
case LATEST:
latestDate.setValue(originalValue.getStartDate());
Long diff = DateUtil.diffDays(originalValue.getStartDate(), new Date());
stepper.setValue(diff.intValue());
break;
case RANGE:
fromDate.setValue(originalValue.getStartDate());
toDate.setValue(originalValue.getEndDate());
break;
}
optionGroup.setValue(currentMode);
allowValueUpdate = true;
fireDateRangeChangedEvent();
}
public Date getStartDate() {
switch (currentMode) {
case RANGE:
return fromDate.dateValue();
case LATEST:
return (Date) latestDate.getValue();
default:
return null;
}
}
public Date getEndDate() {
switch (currentMode) {
case RANGE:
return toDate.dateValue();
case LATEST:
default:
return new Date();
}
}
private void fireDateRangeChangedEvent() {
listenable.fireEvent(new DateRangeChangedEvent(this));
}
@Override
public void addListener(DateRangeListener listener) {
listenable.addListener(listener);
}
@Override
public void removeListener(DateRangeListener listener) {
listenable.removeListener(listener);
}
public static class DateRange implements Serializable {
private Date startDate;
private Date endDate;
private Mode mode;
public DateRange() {
}
public DateRange(DateRange copy) {
this(copy.getStartDate(), copy.getEndDate(), copy.getMode());
}
public DateRange(Date startDate, Date endDate, Mode mode) {
this.startDate = DateUtil.copyDate(startDate);
this.endDate = DateUtil.copyDate(endDate);
this.mode = mode;
}
public void update(DateRange copy) {
this.startDate = DateUtil.copyDate(copy.getStartDate());
this.endDate = DateUtil.copyDate(copy.getEndDate());
this.mode = copy.getMode();
}
public Mode getMode() {
return mode;
}
public void setMode(Mode mode) {
this.mode = mode;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
public Date getStartDate() {
return startDate;
}
public Date getEndDate() {
return endDate;
}
public boolean isSet() {
return Mode.LATEST.equals(mode) && startDate != null || Mode.RANGE.equals(mode);
}
}
public static class DateRangeChangedEvent {
private DateRangeField source;
public DateRangeChangedEvent(DateRangeField source) {
this.source = source;
}
public DateRangeField getSource() {
return source;
}
public Date getStartDate() {
return getSource().getStartDate();
}
public Date getEndDate() {
return getSource().getEndDate();
}
}
public static interface DateRangeListener extends EventListener<DateRangeChangedEvent> {
}
}