jfxtras.labs.internal.scene.control.skin.CalendarPickerControlSkin Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jfxtras-labs Show documentation
Show all versions of jfxtras-labs Show documentation
Experimental components for JavaFX 2
/**
* Copyright (c) 2011, 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 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.labs.internal.scene.control.skin;
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.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import jfxtras.labs.scene.control.CalendarPicker;
import jfxtras.labs.scene.control.CalendarTimePicker;
import jfxtras.labs.scene.control.ListSpinner;
import jfxtras.labs.scene.control.ListSpinner.CycleEvent;
import jfxtras.labs.scene.control.ListSpinnerIntegerList;
/**
* 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();
// start listening to changes
// if the calendar changes, the display calendar will jump to show that
getSkinnable().calendarProperty().addListener(new InvalidationListener()
{
@Override
public void invalidated(Observable arg0)
{
if (getSkinnable().getCalendar() != null)
{
setDisplayedCalendar(getSkinnable().getCalendar());
}
}
});
if (getSkinnable().getCalendar() != null)
{
setDisplayedCalendar(getSkinnable().getCalendar());
}
// if the calendars change, the selection must be refreshed
getSkinnable().calendars().addListener(new ListChangeListener()
{
@Override
public void onChanged(javafx.collections.ListChangeListener.Change extends Calendar> arg0)
{
refreshDayButtonToggleState();
}
});
// if the displayed calendar changes, the screen must be refreshed
displayedCalendar().addListener(new InvalidationListener()
{
@Override
public void invalidated(Observable arg0)
{
refresh();
}
});
// update the data
refresh();
}
// ==================================================================================================================
// PROPERTIES
// ==================================================================================================================
// DRAW
/**
* construct the nodes
*/
private void createNodes()
{
// the result
GridPane lGridPane = new GridPane();
lGridPane.setVgap(2.0);
lGridPane.setHgap(2.0);
// 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);
lGridPane.getColumnConstraints().addAll(lColumnConstraintsNeverGrow, lColumnConstraintsAlwaysGrow, lColumnConstraintsAlwaysGrow, lColumnConstraintsAlwaysGrow, lColumnConstraintsAlwaysGrow, lColumnConstraintsAlwaysGrow, lColumnConstraintsAlwaysGrow, lColumnConstraintsAlwaysGrow);
// month spinner
List lMonthLabels = getMonthLabels();
monthListSpinner = new ListSpinner(lMonthLabels).withIndex(Calendar.getInstance().get(Calendar.MONTH)).withCyclic(Boolean.TRUE);
// 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();
}
});
lGridPane.add(monthListSpinner, 1, 0, 4, 1); // col, row, hspan, vspan
// year spinner
yearListSpinner = new ListSpinner(new ListSpinnerIntegerList()).withValue(Calendar.getInstance().get(Calendar.YEAR));
// if the value changed, update the displayed calendar
yearListSpinner.valueProperty().addListener(new ChangeListener()
{
@Override
public void changed(ObservableValue observableValue, Integer oldValue, Integer newValue)
{
setDisplayedCalendarFromSpinners();
}
});
lGridPane.add(yearListSpinner, 5, 0, 3, 1); // col, row, hspan, vspan
// double click here to show today
Label lTodayLabel = new Label(" ");
lTodayLabel.onMouseClickedProperty().set(new EventHandler()
{
@Override
public void handle(MouseEvent event)
{
if (event.getClickCount() < 1) return;
setDisplayedCalendarToToday();
}
});
lGridPane.add(lTodayLabel, 0, 1); // col, row
// 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?
// add it
lGridPane.add(lLabel, i + 1, 1); // col, row
// 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);
// first of a row: add the weeknumber
lGridPane.add(weeknumberLabels.get(i), 0, i + 2); // col, row
}
// 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.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
lGridPane.add(lToggleButton, (i % 7) + 1, (i / 7) + 2); // col, row
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
// TODO: this is done upon construction, we need to make this dynamic based on Mode and showTime
if (getSkinnable().getMode().equals(CalendarPicker.Mode.SINGLE) && getSkinnable().showTimeProperty().get() == true)
{
lGridPane.add(timePicker, 1, 8, 7, 1); // col, row, hspan, vspan
Bindings.bindBidirectional(timePicker.calendarProperty(), getSkinnable().calendarProperty());
}
// add to self
this.getStyleClass().add(this.getClass().getSimpleName()); // always add self as style class, because CSS should relate to the skin not the control
getChildren().add(lGridPane);
}
private ListSpinner monthListSpinner = null;
private ListSpinner yearListSpinner = null;
final private List