
jfxtras.internal.scene.control.skin.CalendarPickerControlSkin Maven / Gradle / Ivy
/**
* CalendarPickerControlSkin.java
*
* Copyright (c) 2011-2014, JFXtras
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the organization nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package jfxtras.internal.scene.control.skin;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.beans.InvalidationListener;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.css.CssMetaData;
import javafx.css.Styleable;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.SkinBase;
import javafx.scene.control.ToggleButton;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.Priority;
import javafx.util.Callback;
import jfxtras.css.converters.SimpleDateFormatConverter;
import jfxtras.scene.control.CalendarPicker;
import jfxtras.scene.control.CalendarTimePicker;
import jfxtras.scene.control.ListSpinner;
import jfxtras.scene.control.ListSpinner.CycleEvent;
import jfxtras.scene.control.ListSpinnerIntegerList;
import jfxtras.scene.layout.GridPane;
import com.sun.javafx.css.converters.EnumConverter;
/**
* This skin uses regular JavaFX controls
* @author Tom Eugelink
*
*/
public class CalendarPickerControlSkin extends CalendarPickerMonthlySkinAbstract
{
// ==================================================================================================================
// CONSTRUCTOR
/**
*
*/
public CalendarPickerControlSkin(CalendarPicker control)
{
super(control);
construct();
}
/*
* construct the component
*/
private void construct()
{
// setup component
createNodes();
layoutNodes();
// start listening to changes
// if the calendar changes, the display calendar will jump to show that
getSkinnable().calendarProperty().addListener( (InvalidationListener) observable -> {
if (getSkinnable().getCalendar() != null) {
getSkinnable().setDisplayedCalendar(getSkinnable().getCalendar());
}
});
if (getSkinnable().getCalendar() != null) {
getSkinnable().setDisplayedCalendar(getSkinnable().getCalendar());
}
// if the calendars change, the selection must be refreshed
getSkinnable().calendars().addListener( (InvalidationListener) observable -> {
refreshDayButtonToggleState();
});
// react to changes in the locale
getSkinnable().localeProperty().addListener( (InvalidationListener) observable -> {
monthListSpinner.setItems(FXCollections.observableArrayList(getMonthLabels()));
// force change the locale in the displayed calendar
getSkinnable().displayedCalendar().set( (Calendar)getSkinnable().getDisplayedCalendar().clone() );
refresh();
});
// react to changes in the locale
getSkinnable().showTimeProperty().addListener( (InvalidationListener) observable -> {
layoutNodes();
});
// react to changes in the disabled calendars
getSkinnable().disabledCalendars().addListener( new ListChangeListener(){
@Override
public void onChanged(javafx.collections.ListChangeListener.Change extends Calendar> change) {
refreshDayButtonsVisibilityAndLabel();
}
});
// react to changes in the highlighted calendars
getSkinnable().highlightedCalendars().addListener(new ListChangeListener(){
@Override
public void onChanged(javafx.collections.ListChangeListener.Change extends Calendar> arg0) {
refreshDayButtonsVisibilityAndLabel();
}
});
// react to changes in the displayed calendar
getSkinnable().displayedCalendar().addListener( (InvalidationListener) observable -> {
refresh();
});
// update the data
refresh();
}
// ==================================================================================================================
// PROPERTIES
/**
* This skin has the displayed date always pointing to the first of the month
* @param displayedCalendar
* @return
*/
protected Calendar deriveDisplayedCalendar(Calendar displayedCalendar)
{
// always the 1st of the month, without time
Calendar lCalendar = Calendar.getInstance(getSkinnable().getLocale());
lCalendar.set(Calendar.DATE, 1);
lCalendar.set(Calendar.MONTH, displayedCalendar.get(Calendar.MONTH));
lCalendar.set(Calendar.YEAR, displayedCalendar.get(Calendar.YEAR));
lCalendar.set(Calendar.HOUR_OF_DAY, 0);
lCalendar.set(Calendar.MINUTE, 0);
lCalendar.set(Calendar.SECOND, 0);
lCalendar.set(Calendar.MILLISECOND, 0);
return lCalendar;
}
// ==================================================================================================================
// StyleableProperties
/** ShowWeeknumbers: */
public final ObjectProperty showWeeknumbersProperty()
{
if (showWeeknumbers == null)
{
showWeeknumbers = new StyleableObjectProperty(SHOW_WEEKNUMBERS_DEFAULT)
{
@Override public void invalidated()
{
layoutNodes();
}
@Override public CssMetaData getCssMetaData() { return StyleableProperties.SHOW_WEEKNUMBERS; }
@Override public Object getBean() { return CalendarPickerControlSkin.this; }
@Override public String getName() { return "showWeeknumbers"; }
};
}
return showWeeknumbers;
}
private ObjectProperty showWeeknumbers = null;
public final void setShowWeeknumbers(ShowWeeknumbers value) { showWeeknumbersProperty().set(value); }
public final ShowWeeknumbers getShowWeeknumbers() { return showWeeknumbers == null ? SHOW_WEEKNUMBERS_DEFAULT : showWeeknumbers.get(); }
public final CalendarPickerControlSkin withShowWeeknumbers(ShowWeeknumbers value) { setShowWeeknumbers(value); return this; }
public enum ShowWeeknumbers {YES, NO}
static private final ShowWeeknumbers SHOW_WEEKNUMBERS_DEFAULT = ShowWeeknumbers.YES;
/** LabelDateFormat: */
public final ObjectProperty labelDateFormatProperty()
{
if (labelDateFormat == null)
{
labelDateFormat = new StyleableObjectProperty(LABEL_DATEFORMAT_DEFAULT)
{
@Override public void invalidated()
{
refreshDayButtonsVisibilityAndLabel();
}
@Override public CssMetaData getCssMetaData() { return StyleableProperties.LABEL_DATEFORMAT; }
@Override public Object getBean() { return CalendarPickerControlSkin.this; }
@Override public String getName() { return "labelDateFormat"; }
};
}
return labelDateFormat;
}
private ObjectProperty labelDateFormat = null;
public final void setLabelDateFormat(DateFormat value) { labelDateFormatProperty().set(value); }
public final DateFormat getLabelDateFormat() { return labelDateFormat == null ? LABEL_DATEFORMAT_DEFAULT : labelDateFormat.get(); }
public final CalendarPickerControlSkin withLabelDateFormat(DateFormat value) { setLabelDateFormat(value); return this; }
static private final SimpleDateFormat LABEL_DATEFORMAT_DEFAULT = new SimpleDateFormat("d");
static private final SimpleDateFormat ID_DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd");
// ----------------------------
// communicate the styleables
private static class StyleableProperties
{
private static final CssMetaData SHOW_WEEKNUMBERS = new CssMetaData("-fxx-show-weeknumbers", new EnumConverter(ShowWeeknumbers.class), SHOW_WEEKNUMBERS_DEFAULT )
{
@Override public boolean isSettable(CalendarPicker n) { return !((CalendarPickerControlSkin)n.getSkin()).showWeeknumbersProperty().isBound(); }
@Override public StyleableProperty getStyleableProperty(CalendarPicker n) { return (StyleableProperty)((CalendarPickerControlSkin)n.getSkin()).showWeeknumbersProperty(); }
};
private static final CssMetaData LABEL_DATEFORMAT = new CssMetaData("-fxx-label-dateformat", new SimpleDateFormatConverter(), LABEL_DATEFORMAT_DEFAULT )
{
@Override public boolean isSettable(CalendarPicker n) { return !((CalendarPickerControlSkin)n.getSkin()).showWeeknumbersProperty().isBound(); }
@Override public StyleableProperty getStyleableProperty(CalendarPicker n) { return (StyleableProperty)((CalendarPickerControlSkin)n.getSkin()).labelDateFormatProperty(); }
};
private static final List> STYLEABLES;
static
{
final List> styleables = new ArrayList>(SkinBase.getClassCssMetaData());
styleables.add(SHOW_WEEKNUMBERS);
styleables.add(LABEL_DATEFORMAT);
STYLEABLES = Collections.unmodifiableList(styleables);
}
}
/**
* @return The CssMetaData associated with this class, which may include the
* CssMetaData of its super classes.
*/
public static List> getClassCssMetaData()
{
return StyleableProperties.STYLEABLES;
}
/**
* This method should delegate to {@link javafx.scene.Node#getClassCssMetaData()} so that
* a Node's CssMetaData can be accessed without the need for reflection.
* @return The CssMetaData associated with this node, which may include the
* CssMetaData of its super classes.
*/
public List> getCssMetaData()
{
return getClassCssMetaData();
}
// ==================================================================================================================
// DRAW
/**
* construct the nodes
*/
private void createNodes()
{
// setup the grid so all weekday togglebuttons will grow, but the weeknumbers do not
ColumnConstraints lColumnConstraintsAlwaysGrow = new ColumnConstraints();
lColumnConstraintsAlwaysGrow.setHgrow(Priority.ALWAYS);
ColumnConstraints lColumnConstraintsNeverGrow = new ColumnConstraints();
lColumnConstraintsNeverGrow.setHgrow(Priority.NEVER);
// month spinner
List lMonthLabels = getMonthLabels();
monthListSpinner = new ListSpinner(lMonthLabels).withIndex(Calendar.getInstance().get(Calendar.MONTH)).withCyclic(Boolean.TRUE);
monthListSpinner.setId("monthListSpinner");
// on cycle overflow to year
monthListSpinner.setOnCycle(new EventHandler()
{
@Override
public void handle(CycleEvent evt)
{
// if we've cycled down
if (evt.cycledDown())
{
yearListSpinner.increment();
}
else
{
yearListSpinner.decrement();
}
}
});
// if the value changed, update the displayed calendar
monthListSpinner.valueProperty().addListener(new ChangeListener()
{
@Override
public void changed(ObservableValue arg0, String arg1, String arg2)
{
setDisplayedCalendarFromSpinners();
}
});
// year spinner
yearListSpinner = new ListSpinner(new ListSpinnerIntegerList()).withValue(Calendar.getInstance().get(Calendar.YEAR));
yearListSpinner.setId("yearListSpinner");
// if the value changed, update the displayed calendar
yearListSpinner.valueProperty().addListener(new ChangeListener()
{
@Override
public void changed(ObservableValue observableValue, Integer oldValue, Integer newValue)
{
setDisplayedCalendarFromSpinners();
}
});
// double click here to show today
todayLabel = new Label(" ");
todayLabel.onMouseClickedProperty().set(new EventHandler()
{
@Override
public void handle(MouseEvent event)
{
if (event.getClickCount() < 1) return;
setDisplayedCalendarToToday();
}
});
// weekday labels
for (int i = 0; i < 7; i++)
{
// create buttons
Label lLabel = new Label("" + i);
// style class is set together with the label
lLabel.getStyleClass().add("weekday-label");
lLabel.setMaxWidth(Integer.MAX_VALUE); // this is one of those times; why the @#$@#$@#$ do I need to specify this in order to make the damn label centered?
// remember the column it is associated with
lLabel.setUserData(Integer.valueOf(i));
lLabel.onMouseClickedProperty().set(weekdayLabelMouseClickedPropertyEventHandler);
// remember it
weekdayLabels.add(lLabel);
}
// weeknumber labels
for (int i = 0; i < 6; i++)
{
// create buttons
Label lLabel = new Label("" + i);
lLabel.getStyleClass().add("weeknumber");
lLabel.setAlignment(Pos.BASELINE_RIGHT);
// remember it
weeknumberLabels.add(lLabel);
// remember the row it is associated with
lLabel.setUserData(Integer.valueOf(i));
lLabel.onMouseClickedProperty().set(weeknumerLabelMouseClickedPropertyEventHandler);
}
// setup: 6 rows of 7 days per week (which is the maximum number of buttons required in the worst case layout)
for (int i = 0; i < 6 * 7; i++)
{
// create buttons
ToggleButton lToggleButton = new ToggleButton("" + i);
lToggleButton.setId("day" + i);
lToggleButton.getStyleClass().add("day-button");
lToggleButton.selectedProperty().addListener(toggleButtonSelectedPropertyChangeListener); // for minimal memory usage, use a single listener
lToggleButton.onMouseReleasedProperty().set(toggleButtonMouseReleasedPropertyEventHandler); // for minimal memory usage, use a single listener
lToggleButton.onKeyReleasedProperty().set(toggleButtonKeyReleasedPropertyEventHandler); // for minimal memory usage, use a single listener
// remember which button belongs to this property
booleanPropertyToDayToggleButtonMap.put(lToggleButton.selectedProperty(), lToggleButton);
// add it
lToggleButton.setMaxWidth(Double.MAX_VALUE); // make the button grow to fill a GridPane's cell
lToggleButton.setAlignment(Pos.BASELINE_CENTER);
// remember it
dayButtons.add(lToggleButton);
}
// add timepicker
Bindings.bindBidirectional(timePicker.calendarProperty(), getSkinnable().calendarProperty());
Bindings.bindBidirectional(timePicker.valueValidationCallbackProperty(), getSkinnable().valueValidationCallbackProperty());
Bindings.bindBidirectional(timePicker.localeProperty(), getSkinnable().localeProperty());
// add to self
getSkinnable().getStyleClass().add(this.getClass().getSimpleName()); // always add self as style class, because CSS should relate to the skin not the control
}
// the result
private ListSpinner monthListSpinner = null;
private ListSpinner yearListSpinner = null;
private Label todayLabel = new Label(" ");
final private List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy