Package jfxtras.internal.scene.control.skin

Source Code of jfxtras.internal.scene.control.skin.CalendarPickerControlSkin$StyleableProperties

/**
* 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 <COPYRIGHT HOLDER> 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<CalendarPickerControlSkin>
{
  // ==================================================================================================================
  // 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<Calendar>(){
      @Override
      public void onChanged(javafx.collections.ListChangeListener.Change<? extends Calendar> change) {
        refreshDayButtonsVisibilityAndLabel();
      }
      });
       
        // react to changes in the highlighted calendars
        getSkinnable().highlightedCalendars().addListener(new ListChangeListener<Calendar>(){
      @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<ShowWeeknumbers> showWeeknumbersProperty()
    {
        if (showWeeknumbers == null)
        {
            showWeeknumbers = new StyleableObjectProperty<ShowWeeknumbers>(SHOW_WEEKNUMBERS_DEFAULT)
            {
                @Override public void invalidated()
                {
                    layoutNodes();
                }

                @Override public CssMetaData<CalendarPicker,ShowWeeknumbers> getCssMetaData() { return StyleableProperties.SHOW_WEEKNUMBERS; }
                @Override public Object getBean() { return CalendarPickerControlSkin.this; }
                @Override public String getName() { return "showWeeknumbers"; }
            };
        }
        return showWeeknumbers;
    }
    private ObjectProperty<ShowWeeknumbers> 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<DateFormat> labelDateFormatProperty()
    {
        if (labelDateFormat == null)
        {
            labelDateFormat = new StyleableObjectProperty<DateFormat>(LABEL_DATEFORMAT_DEFAULT)
            {
                @Override public void invalidated()
                {
                    refreshDayButtonsVisibilityAndLabel();
                }

                @Override public CssMetaData<CalendarPicker,DateFormat> getCssMetaData() { return StyleableProperties.LABEL_DATEFORMAT; }
                @Override public Object getBean() { return CalendarPickerControlSkin.this; }
                @Override public String getName() { return "labelDateFormat"; }
            };
        }
        return labelDateFormat;
    }
    private ObjectProperty<DateFormat> 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<CalendarPicker, ShowWeeknumbers> SHOW_WEEKNUMBERS = new CssMetaData<CalendarPicker, ShowWeeknumbers>("-fxx-show-weeknumbers", new EnumConverter<ShowWeeknumbers>(ShowWeeknumbers.class), SHOW_WEEKNUMBERS_DEFAULT )
        {
            @Override public boolean isSettable(CalendarPicker n) { return !((CalendarPickerControlSkin)n.getSkin()).showWeeknumbersProperty().isBound(); }
            @Override public StyleableProperty<ShowWeeknumbers> getStyleableProperty(CalendarPicker n) { return (StyleableProperty<ShowWeeknumbers>)((CalendarPickerControlSkin)n.getSkin()).showWeeknumbersProperty(); }
        };

        private static final CssMetaData<CalendarPicker, DateFormat> LABEL_DATEFORMAT = new CssMetaData<CalendarPicker, DateFormat>("-fxx-label-dateformat", new SimpleDateFormatConverter(), LABEL_DATEFORMAT_DEFAULT )
        {
            @Override public boolean isSettable(CalendarPicker n) { return !((CalendarPickerControlSkin)n.getSkin()).showWeeknumbersProperty().isBound(); }
            @Override public StyleableProperty<DateFormat> getStyleableProperty(CalendarPicker n) { return (StyleableProperty<DateFormat>)((CalendarPickerControlSkin)n.getSkin()).labelDateFormatProperty(); }
        };

        private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
        static
        {
            final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<CssMetaData<? extends Styleable, ?>>(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<CssMetaData<? extends Styleable, ?>> 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<CssMetaData<? extends Styleable, ?>> 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<String> lMonthLabels = getMonthLabels();
    monthListSpinner = new ListSpinner<String>(lMonthLabels).withIndex(Calendar.getInstance().get(Calendar.MONTH)).withCyclic(Boolean.TRUE);
    monthListSpinner.setId("monthListSpinner");
    // on cycle overflow to year
    monthListSpinner.setOnCycle(new EventHandler<ListSpinner.CycleEvent>()
    {
      @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<String>()
    {
      @Override
      public void changed(ObservableValue arg0, String arg1, String arg2)
      {
        setDisplayedCalendarFromSpinners();
      }
    });
   
    // year spinner
    yearListSpinner = new ListSpinner<Integer>(new ListSpinnerIntegerList()).withValue(Calendar.getInstance().get(Calendar.YEAR));
    yearListSpinner.setId("yearListSpinner");
    // if the value changed, update the displayed calendar
    yearListSpinner.valueProperty().addListener(new ChangeListener<Integer>()
    {
      @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<MouseEvent>()
    {
      @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<String> monthListSpinner = null;
  private ListSpinner<Integer> yearListSpinner = null;
  private Label todayLabel = new Label("   ");
  final private List<Label> weekdayLabels = new ArrayList<Label>();
  final private List<Label> weeknumberLabels = new ArrayList<Label>();
  final private List<ToggleButton> dayButtons = new ArrayList<ToggleButton>();
  final private CalendarTimePicker timePicker = new CalendarTimePicker();
  final private Map<BooleanProperty, ToggleButton> booleanPropertyToDayToggleButtonMap = new WeakHashMap<BooleanProperty, ToggleButton>();
  final private ChangeListener<Boolean> toggleButtonSelectedPropertyChangeListener = new ChangeListener<Boolean>()
  {
    @Override
    public void changed(ObservableValue<? extends Boolean> observableValue, Boolean oldValue, Boolean newValue)
    {
      refreshDayButtonToggleState();
    }
  };
  final private EventHandler<MouseEvent> toggleButtonMouseReleasedPropertyEventHandler = new EventHandler<MouseEvent>()
  {
    @Override
    public void handle(MouseEvent event)
    {
      ToggleButton lToggleButton = (ToggleButton)event.getSource();         
      toggle(lToggleButton, event.isShiftDown());
    }
  };
  final private EventHandler<KeyEvent> toggleButtonKeyReleasedPropertyEventHandler = new EventHandler<KeyEvent>()
  {
    @Override
    public void handle(KeyEvent event)
    {
      ToggleButton lToggleButton = (ToggleButton)event.getSource();
      if (" ".equals(event.getText()))
      {
        toggle(lToggleButton, event.isShiftDown());
      }
    }
  };
  final private EventHandler<MouseEvent> weekdayLabelMouseClickedPropertyEventHandler = new EventHandler<MouseEvent>()
  {
    @Override
    public void handle(MouseEvent event)
    {
      // in single or range mode this does not do anything
      if (getSkinnable().getMode() == CalendarPicker.Mode.SINGLE) return;
      if (getSkinnable().getMode() == CalendarPicker.Mode.RANGE) return;
     
      // process the calendars
      int lColIdx = ((Integer)((Label)event.getSource()).getUserData()).intValue();
      for (int lRowIdx = 0; lRowIdx < 6; lRowIdx++)
      {     
        int lIdx = (lRowIdx * 7) + lColIdx;
        ToggleButton lToggleButton = dayButtons.get(lIdx)
        if (lToggleButton.isVisible() == true) toggle(lToggleButton, false);
      }
    }
  };
  final private EventHandler<MouseEvent> weeknumerLabelMouseClickedPropertyEventHandler = new EventHandler<MouseEvent>()
  {
    @Override
    public void handle(MouseEvent event)
    {
      // in single mode this does not do anything
      if (getSkinnable().getMode() == CalendarPicker.Mode.SINGLE) return;
     
      // in range mode we clear the existing selection
      if (getSkinnable().getMode() == CalendarPicker.Mode.RANGE)
      {
        getSkinnable().calendars().clear();
      }
     
      // process the calendars
      int lRowIdx = ((Integer)((Label)event.getSource()).getUserData()).intValue();
      for (int i = lRowIdx * 7; i < (lRowIdx * 7) + 7; i++)
      {     
        ToggleButton lToggleButton = dayButtons.get(i)
        if (getSkinnable().getMode() == CalendarPicker.Mode.RANGE)
        {
          Calendar lCalendar = calendarForToggleButton(lToggleButton);
          if (callValueValidationCallback(lCalendar)) {
            getSkinnable().calendars().add(lCalendar);
          }
        }
        else
        {
          toggle(lToggleButton, false);
        }
      }
    }
  };
   
  /**
   * construct the nodes
   */
  private void layoutNodes()
  {
    getChildren().clear();
   
    // the result
    GridPane gridPane = new GridPane();
    gridPane = new GridPane();
    gridPane.setVgap(2.0);
    gridPane.setHgap(2.0);
    //gridPane.setPadding(new javafx.geometry.Insets(0,0,0,0));
    //gridPane.gridLinesVisibleProperty().set(true);
    getChildren().add(gridPane);
   
    // show weeknumbers
    boolean lShowWeeknumbers = ShowWeeknumbers.YES.equals( getShowWeeknumbers() );
    int lWeeknumbersCols = (lShowWeeknumbers ? 1 : 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);
    if (lShowWeeknumbers)
    {
      gridPane.getColumnConstraints().addAll(lColumnConstraintsNeverGrow);
    }
    gridPane.getColumnConstraints().addAll(lColumnConstraintsAlwaysGrow, lColumnConstraintsAlwaysGrow, lColumnConstraintsAlwaysGrow, lColumnConstraintsAlwaysGrow, lColumnConstraintsAlwaysGrow, lColumnConstraintsAlwaysGrow, lColumnConstraintsAlwaysGrow);

    // month spinner
    gridPane.add(monthListSpinner, new GridPane.C().col(lWeeknumbersCols).row(0).colSpan(4).rowSpan(1));
   
    // year spinner
    gridPane.add(yearListSpinner, new GridPane.C().col(lWeeknumbersCols + 4).row(0).colSpan(3).rowSpan(1));
   
    // double click here to show today
    if (lShowWeeknumbers) {
      gridPane.add(todayLabel, new GridPane.C().col(0).row(1));
    }   
   
    // weekday labels
    for (int i = 0; i < 7; i++)
    {
      gridPane.add(weekdayLabels.get(i), new GridPane.C().col(lWeeknumbersCols + i).row(1));
    }
   
    // weeknumber labels
    if (lShowWeeknumbers)
    {
      for (int i = 0; i < 6; i++)
      {
        gridPane.add(weeknumberLabels.get(i), new GridPane.C().col(0).row(i + 2).margin(new javafx.geometry.Insets(0,0,0,0)));
      }
    }
   
    // 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++)
    {
      gridPane.add(dayButtons.get(i), new GridPane.C().col(lWeeknumbersCols + (i % 7)).row((i / 7) + 2));
    }

    // 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)
    {
      gridPane.add(timePicker, new GridPane.C().col(lWeeknumbersCols).row(8).colSpan(7).rowSpan(1));
    }
  }

  /**
   *
   * @param toggleButton
   * @return
   */
  private Calendar calendarForToggleButton(ToggleButton toggleButton)
  {
    // base reference
    int lDayToggleButtonIdx = dayButtons.indexOf(toggleButton);
   
    // calculate the day-of-month
    int lFirstOfMonthIdx = determineFirstOfMonthDayOfWeek();
    int lDayOfMonth = lDayToggleButtonIdx - lFirstOfMonthIdx + 1;

    // create calendar representing the date that was toggled
    Calendar lToggledCalendar = (Calendar)getSkinnable().getDisplayedCalendar().clone();
    lToggledCalendar.set(Calendar.YEAR, getSkinnable().getDisplayedCalendar().get(Calendar.YEAR));
    lToggledCalendar.set(Calendar.MONTH, getSkinnable().getDisplayedCalendar().get(Calendar.MONTH));
    lToggledCalendar.set(Calendar.DATE, lDayOfMonth);
   
    // include time
    if (timePicker.isVisible() && timePicker.getCalendar() != null)
    {
      lToggledCalendar.set(Calendar.HOUR_OF_DAY, timePicker.getCalendar().get(Calendar.HOUR_OF_DAY));
      lToggledCalendar.set(Calendar.MINUTE, timePicker.getCalendar().get(Calendar.MINUTE));
      lToggledCalendar.set(Calendar.SECOND, timePicker.getCalendar().get(Calendar.SECOND));
    }
   
    // return
    return lToggledCalendar;
  }
 
  /**
   *
   * @param toggleButton
   * @param shiftIsPressed
   */
  private void toggle(ToggleButton toggleButton, boolean shiftIsPressed)   
  {
    // create calendar representing the date that was toggled
    Calendar lToggledCalendar = calendarForToggleButton(toggleButton);

    // select or deselect
    List<Calendar> lCalendars = getSkinnable().calendars();
    Calendar lFoundCalendar = find(lCalendars, lToggledCalendar); // find solely on YMD not HMS
    if (lFoundCalendar == null) // if not found
    {
      // make sure it adheres to the mode
      // SINGLE: clear all but the last selected
      // RANGE: if shift is not pressed, behave like single
      if ( getSkinnable().getMode() == CalendarPicker.Mode.SINGLE
        || (getSkinnable().getMode() == CalendarPicker.Mode.RANGE && shiftIsPressed == false)
         ) {
        // Normally one would removed all the old ones and add one new,
        // but this would result in a moment where the value would be null and that can be unwanted.
        // So we first add the new value and then clear away the old ones until 1 remains.
        if (callValueValidationCallback(lToggledCalendar)) {
          lCalendars.add(lToggledCalendar);
          while (lCalendars.size() > 1) {
            lCalendars.remove(0);
          }
        }
      }
      if ( getSkinnable().getMode() == CalendarPicker.Mode.MULTIPLE  && shiftIsPressed == false ) {
        if (callValueValidationCallback(lToggledCalendar)) {
          lCalendars.add(lToggledCalendar);
        }
      }
      if ( (getSkinnable().getMode() == CalendarPicker.Mode.MULTIPLE || getSkinnable().getMode() == CalendarPicker.Mode.RANGE)
        && shiftIsPressed == true
         ) {
        // we must have a last selected
        if (iLastSelected != null)
        {
          // get the other calendar and make sure other <= toggle
          Calendar lOtherCalendar = iLastSelected;
          int lDirection = (lOtherCalendar.after(lToggledCalendar) ? -1 : 1);
         
          // walk towards the target (toggle) date and add all in between
          Calendar lWalker = (Calendar)lOtherCalendar.clone();
          lWalker.add(Calendar.DATE, lDirection);
          Calendar lTarget = (Calendar)lToggledCalendar.clone();
          lTarget.add(Calendar.DATE, lDirection);
          while (lWalker.equals(lTarget) == false)
          {
            // if valid do add to range
            if (callValueValidationCallback(lWalker)) {
              lCalendars.add((Calendar)lWalker.clone());
            }
            else {
              // if a calendar was not valid in range mode (in which ranges must always be uninterrupted), exit the loop
              if (getSkinnable().getMode() == CalendarPicker.Mode.RANGE) {
                break;
              }
            }
           
            // next
            lWalker.add(Calendar.DATE, lDirection);
          }
        }
      }
     
      // remember
      iLastSelected = (Calendar)lToggledCalendar.clone();
    }
        else
        {
            // special behavior for CalendarTextField: reselect in case of not allow null, by inserting the same calendar again, so the current can be removed (the popup will be closed then)
            if (lCalendars.size() == 1 && getSkinnable().getAllowNull() == false)
            {
// for some reason this causes a UnsupportedOperationException:
//              lCalendars.add( (Calendar)lFoundCalendar.clone() );
              Calendar lCalendar = (Calendar)lFoundCalendar.clone();
              if (callValueValidationCallback(lCalendar)) {
                getSkinnable().setCalendar(lCalendar);
              }
            }
           
            if (lCalendars.size() > 1 || getSkinnable().getAllowNull()) {
              // remove
              lCalendars.remove(lFoundCalendar);
              iLastSelected = null;
          }
        }

    // make sure the buttons are toggled correctly
    refreshDayButtonToggleState();
  }
  private Calendar iLastSelected = null;

  /*
   *
   */
  private void setDisplayedCalendarFromSpinners()
  {
    // get spinner values
    int lYear = yearListSpinner.getValue().intValue();
    int lMonth = monthListSpinner.getIndex();
   
    // get new calendar to display
    Calendar lCalendar = (Calendar)getSkinnable().getDisplayedCalendar().clone();
    lCalendar.set(Calendar.YEAR, lYear);
    lCalendar.set(Calendar.MONTH, lMonth);
   
    // set it
    getSkinnable().setDisplayedCalendar(lCalendar);
  }
 

  /*
   *
   */
  private void setDisplayedCalendarToToday()
  {
    // get spinner values
    Calendar lTodayCalendar = Calendar.getInstance();
   
    // get new calendar to display
    Calendar lCalendar = (Calendar)getSkinnable().getDisplayedCalendar().clone();
    lCalendar.set(Calendar.YEAR, lTodayCalendar.get(Calendar.YEAR));
    lCalendar.set(Calendar.MONTH, lTodayCalendar.get(Calendar.MONTH));
   
    // set it
    getSkinnable().setDisplayedCalendar(lCalendar);
  }
 
  /**
   * refresh all
   */
  protected void refresh()
  {
    calendarRangeCallback();
    refreshSpinner();
    refreshWeekdayLabels();
    refreshWeeknumberLabels();
    refreshDayButtonsVisibilityAndLabel();
    refreshDayButtonToggleState();
  }
 
  /*
   *
   */
  private void refreshSpinner()
  {
    // no updating while in callback, because we will update all in one go afterwards
    if (calendarRangeCallbackAtomicInteger.get() !=0) return;
   
    // get calendar
    Calendar lCalendar = (Calendar)getSkinnable().getDisplayedCalendar();

    // get the value for the corresponding index and set that
    List<String> lMonthLabels = getMonthLabels();
    String lMonthLabel = lMonthLabels.get( lCalendar.get(Calendar.MONTH) );
    monthListSpinner.setValue( lMonthLabel );
   
    // set value
    yearListSpinner.setValue(lCalendar.get(Calendar.YEAR));
   
  }
 
  /*
   *
   */
  private void refreshWeekdayLabels()
  {
    // no updating while in callback, because we will update all in one go afterwards
    if (calendarRangeCallbackAtomicInteger.get() !=0) return;
   
    // get labels
    List<String> lWeekdayLabels = getWeekdayLabels();
   
    // set them
    for (int i = 0; i < weekdayLabels.size(); i++)
    {
      Label lLabel = weekdayLabels.get(i);
      lLabel.setText( lWeekdayLabels.get(i) );
      lLabel.getStyleClass().removeAll("weekend", "weekday");
      lLabel.getStyleClass().add(isWeekdayWeekend(i) ? "weekend" : "weekday");
    }
  }
 
  /*
   *
   */
  private void refreshWeeknumberLabels()
  {
    // no updating while in callback, because we will update all in one go afterwards
    if (calendarRangeCallbackAtomicInteger.get() !=0) return;
   
    // get labels
    List<Integer> lWeeknumbers = getWeeknumbers();
   
    // set them
    for (int i = 0; i < lWeeknumbers.size(); i++)
    {
      Label lLabel = weeknumberLabels.get(i);
      lLabel.setText( (lWeeknumbers.get(i).intValue() < 10 ? "0" : "") + lWeeknumbers.get(i).toString() );
    }
  }
 
  /*
   *
   */
  private void refreshDayButtonsVisibilityAndLabel()
  {
    // no updating while in callback, because we will update all in one go afterwards
    if (calendarRangeCallbackAtomicInteger.get() !=0) return;
   
    // prep
    List<Calendar> highlightedCalendars = getSkinnable().highlightedCalendars();
    List<Calendar> disabledCalendars = getSkinnable().disabledCalendars();
   
    // setup the buttons [0..(6*7)-1]
    // displayed calendar always points to the 1st of the month
    int lFirstOfMonthIdx = determineFirstOfMonthDayOfWeek();

    // hide the preceeding buttons
    for (int i = 0; i < lFirstOfMonthIdx; i++)
    {
      ToggleButton lToggleButton = dayButtons.get(i);
      lToggleButton.setVisible(false);
      lToggleButton.setId("day" + i);
    }
    // make all weeklabels invisible
    for (int i = 1; i < 6; i++)
    {
      weeknumberLabels.get(i).setVisible(false);
    }
   
    // set the month buttons
    int lDaysInMonth = determineDaysInMonth();
    Calendar lCalendar = (Calendar)getSkinnable().getDisplayedCalendar().clone();
    for (int i = 1; i <= lDaysInMonth; i++)
    {
      // set the date
      lCalendar.set(java.util.Calendar.DATE, i);

      // determine the index in the buttons
      int lIdx = lFirstOfMonthIdx + i - 1;

      // update the button
      ToggleButton lToggleButton = dayButtons.get(lIdx);
      lToggleButton.setId(ID_DATEFORMAT.format(lCalendar.getTime()));
      lToggleButton.setVisible(true);
      lToggleButton.setText(getLabelDateFormat().format(lCalendar.getTime()));
      lToggleButton.setAlignment(Pos.BASELINE_CENTER);
      lToggleButton.getStyleClass().removeAll("weekend", "weekday");
      lToggleButton.getStyleClass().add(isWeekdayWeekend(lIdx % 7) ? "weekend" : "weekday");
      lToggleButton.setDisable( disabledCalendars != null && find(disabledCalendars, lCalendar) != null );
     
      // make the corresponding weeklabel visible
      weeknumberLabels.get(lIdx / 7).setVisible(true);

      // highlight today
      lToggleButton.getStyleClass().remove("today");
      if (isToday(lCalendar))
      {
        lToggleButton.getStyleClass().add("today");
     

      // highlight
      lToggleButton.getStyleClass().remove("highlight");
      if (highlightedCalendars != null && find(highlightedCalendars, lCalendar) != null)
      {
        lToggleButton.getStyleClass().add("highlight");
      }
    }

    // hide the trailing buttons
    for (int i = lFirstOfMonthIdx + lDaysInMonth; i < 6*7; i++)
    {
      ToggleButton lToggleButton = dayButtons.get(i);
      lToggleButton.setVisible(false);
      lToggleButton.setId("day" + i);
    }
  }

  /*
   *
   */
  private void refreshDayButtonToggleState()
  {
    // no updating while in callback, because we will update all in one go afterwards
    if (calendarRangeCallbackAtomicInteger.get() !=0) return;
   
    iRefreshingSelection.addAndGet(1);
    try
    {
      // setup the buttons [0..(6*7)-1]
      // displayed calendar always points to the 1st of the month
      int lFirstOfMonthIdx = determineFirstOfMonthDayOfWeek();
      List<Calendar> lCalendars = getSkinnable().calendars();
     
      // set the month buttons
      int lDaysInMonth = determineDaysInMonth();
      Calendar lCalendar = (Calendar)getSkinnable().getDisplayedCalendar().clone();
      for (int i = 1; i <= lDaysInMonth; i++)
      {
        // set the date
        lCalendar.set(java.util.Calendar.DATE, i);
 
        // determine the index in the buttons
        int lIdx = lFirstOfMonthIdx + i - 1;
 
        // is selected
        boolean lSelected = (find(lCalendars, lCalendar) != null)
        dayButtons.get(lIdx).setSelected( lSelected );
      }
    }
    finally
    {
      iRefreshingSelection.addAndGet(-1);
    }
  }
  final private AtomicInteger iRefreshingSelection = new AtomicInteger(0);
 
  /**
   * contains only check YMD
   * @param calendars
   * @param calendar
   * @return
   */
  private Calendar find(List<Calendar> calendars, Calendar calendar)
  {
    for (Calendar c : calendars)
    {
      if ( c.get(Calendar.YEAR) == calendar.get(Calendar.YEAR)
        && c.get(Calendar.MONTH) == calendar.get(Calendar.MONTH)
        && c.get(Calendar.DATE) == calendar.get(Calendar.DATE)
         )
      {
        return c;
      }
    }
    return null;
  }

  /**
   *
   * @param value
   * @return
   */
  private boolean callValueValidationCallback(Calendar value) {
    Callback<Calendar, Boolean> lCallback = getSkinnable().getValueValidationCallback();
    if (lCallback == null) {
      return true;
    }
    return lCallback.call(value == null ? null : (Calendar)value.clone());
  }
}
TOP

Related Classes of jfxtras.internal.scene.control.skin.CalendarPickerControlSkin$StyleableProperties

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.