Package jfxtras.scene.control

Source Code of jfxtras.scene.control.ListSpinner

/**
* ListSpinner.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.scene.control;

import java.util.Arrays;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventTarget;
import javafx.event.EventType;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.control.Skin;
import javafx.util.Callback;
import javafx.util.StringConverter;
import jfxtras.internal.scene.control.skin.LocalTimePickerSkin;

/**
* This is a spinner, showing one value at a time from a list.
* This value is set and retrieved from the value property.
* Basically a spinner shows a list of values and can do "next" or "previous" on this.
*
* A spinner can be editable, the user can then type a value instead of selecting it.
* If the value exists in the list, the spinner will simply jump to it.
* If the value does not exist, if defined the AddCallback is called.
* - If the AddCallback returns null, spinner will only refresh the current index.
* - If the AddCallback returns an Integer, spinner will jump to that index (usually the index where the new value was added).  
*
* http://openjdk.java.net/projects/openjfx/ux/spinner/index.html
*
* You can style the text in the control using CSS like so:
* [source,css]
* --
* .ListSpinner .value {
*  -fx-font-weight: bold;
* }
* --
*
* The "value" class applies to the text in both readonly and editable spinners. Use "readonly" or "editable" to style either mode specifically.
*   
* @author Tom Eugelink
*/
public class ListSpinner<T> extends Control
{
  // TODO: implement SelectionModel?
  // ==================================================================================================================
  // CONSTRUCTOR

  /**
   */
  public ListSpinner()
  {
    construct();
  }

  // ------------
  // model
 
  /**
   * @param items The item list used to populate the spinner.
   */
  public ListSpinner(ObservableList<T> items)
  {
    construct();
    setItems(items);
    first();
  }

  /**
   * @param items The item list used to populate the spinner.
   * @param startValue The initial value of the spinner (one of the items).
   */
  public ListSpinner(ObservableList<T> items, T startValue)
  {
    construct();
    setItems(items);
    setValue(startValue);
  }

  // ------------
  // convenience
 
  /**
   * @param list
   */
  public ListSpinner(final java.util.List<T> list)
  {
    this( FXCollections.observableList(list) );
  }

  /**
   * @param list
   */
  public ListSpinner(T... list)
  {
    this( Arrays.asList(list) );
  }

  /**
   * @param from
   * @param to
   */
  public ListSpinner(int from, int to)
  {
    this( (java.util.List<T>) new ListSpinnerIntegerList(from, to) );
  }

  /**
   * @param from
   * @param to
   * @param step
   */
  public ListSpinner(int from, int to, int step)
  {
    this( (java.util.List<T>) new ListSpinnerIntegerList(from, to, step) );
  }

  // ------------
 
  /*
   *
   */
  private void construct()
  {
    // setup the CSS
    // the -fx-skin attribute in the CSS sets which Skin class is used
    this.getStyleClass().add(ListSpinner.class.getSimpleName());
   
    // react to changes of the value
    this.valueObjectProperty.addListener(new ChangeListener<T>()
    {
      @Override
      public void changed(ObservableValue<? extends T> property, T oldValue, T newValue)
      {
        // get the value of the new index
        int lIdx = getItems().indexOf(newValue);
       
        // set the value
        if (ListSpinner.equals(indexObjectProperty.getValue(), lIdx) == false)
        {
          indexObjectProperty.setValue(lIdx);
        }
      }
    });
   
    // react to changes of the index
    this.indexObjectProperty.addListener(new ChangeListener<Integer>()
    {
      @Override
      public void changed(ObservableValue<? extends Integer> property, Integer oldIndex, Integer newIndex)
      {
        // get the value of the new index
        T lValue = newIndex < 0 ? null : getItems().get(newIndex);
       
        // set the value
        if (ListSpinner.equals(valueObjectProperty.getValue(), lValue) == false)
        {
          valueObjectProperty.setValue(lValue);
        }
      }
    });
   
    // react to changes of the items
    this.itemsObjectProperty.addListener(new ChangeListener<ObservableList<T>>()
    {
      @Override
      public void changed(ObservableValue<? extends ObservableList<T>> property, ObservableList<T> oldList, ObservableList<T> newList)
      {
        if (oldList != null) oldList.removeListener(listChangeListener);
        if (newList != null) newList.addListener(listChangeListener);
      }
    });
  }
 
  /*
   * react to observable list changes
   * TODO: what is sticky, index or value? Now: index
   */
  private ListChangeListener<T> listChangeListener = new ListChangeListener<T>()
  {
    @Override
    public void onChanged(javafx.collections.ListChangeListener.Change<? extends T> change)
    {
      // get current index
      int lIndex = getIndex();
     
      // is it still valid?
      if (lIndex >= getItems().size())
      {
        lIndex = getItems().size() - 1;
        setIndex(lIndex);
        return;
      }
     
      // (re)set the value of the index
      valueObjectProperty.setValue( getItems().get(lIndex) );
    }
  };

  /**
   * Return the path to the CSS file so things are setup right
   */
  @Override protected String getUserAgentStylesheet()
  {
    return ListSpinner.class.getResource("/jfxtras/internal/scene/control/" + ListSpinner.class.getSimpleName() + ".css").toExternalForm();
  }

  @Override public Skin<?> createDefaultSkin() {
    return new jfxtras.internal.scene.control.skin.ListSpinnerSkin(this);
  }

  // ==================================================================================================================
  // PROPERTIES

  /** Id */
  public ListSpinner<T> withId(String value) { setId(value); return this; }

  /** Value: */
  public ObjectProperty<T> valueProperty() { return this.valueObjectProperty; }
  final private ObjectProperty<T> valueObjectProperty = new SimpleObjectProperty<T>(this, "value", null)
  {
    public void set(T value)
    {
      if (getItems().indexOf(value) < 0) throw new IllegalArgumentException("Value does not exist in the list: " + value);
      super.set(value);
    }
  };
  // java bean API
  public T getValue() { return this.valueObjectProperty.getValue(); }
  public void setValue(T value) { this.valueObjectProperty.setValue(value); }
  public ListSpinner<T> withValue(T value) { setValue(value); return this; }
 
  /** Index: */
  public ObjectProperty<Integer> indexProperty() { return this.indexObjectProperty; }
  final private ObjectProperty<Integer> indexObjectProperty = new SimpleObjectProperty<Integer>(this, "index", null)
  {
    public void set(Integer value)
    {
      if (value == null) throw new NullPointerException("Null not allowed as the value for index");
      if (value >= getItems().size()) throw new IllegalArgumentException("Index out of bounds: " + value + ", valid values are 0-" + (getItems().size() - 1));
      super.set(value);
    }
  };
  public Integer getIndex() { return this.indexObjectProperty.getValue(); }
  public void setIndex(Integer value) { this.indexObjectProperty.setValue(value); }
  public ListSpinner<T> withIndex(Integer value) { setIndex(value); return this; }
 
  /** Cyclic: */
  public ObjectProperty<Boolean> cyclicProperty() { return this.cyclicObjectProperty; }
  final private ObjectProperty<Boolean> cyclicObjectProperty = new SimpleObjectProperty<Boolean>(this, "cyclic", false)
  {
    public void set(Boolean value)
    {
      if (value == null) throw new NullPointerException("Null not allowed as the value for cyclic");
      super.set(value);
    }
  };
  public Boolean isCyclic() { return this.cyclicObjectProperty.getValue(); }
  public void setCyclic(Boolean value) { this.cyclicObjectProperty.setValue(value); }
  public ListSpinner<T> withCyclic(Boolean value) { setCyclic(value); return this; }

  /** Editable: */
  public ObjectProperty<Boolean> editableProperty() { return this.editableObjectProperty; }
  final private ObjectProperty<Boolean> editableObjectProperty = new SimpleObjectProperty<Boolean>(this, "editable", false)
  {
    public void set(Boolean value)
    {
      if (value == null) throw new NullPointerException("Null not allowed as the value for editable");
      super.set(value);
    }
  };
  public Boolean isEditable() { return this.editableObjectProperty.getValue(); }
  public void setEditable(Boolean value) { this.editableObjectProperty.setValue(value); }
  public ListSpinner<T> withEditable(Boolean value) { setEditable(value); return this; }

  /** Postfix: */
  public ObjectProperty<String> postfixProperty() { return this.postfixObjectProperty; }
  final private ObjectProperty<String> postfixObjectProperty = new SimpleObjectProperty<String>(this, "postfix", "");
  public String getPostfix() { return this.postfixObjectProperty.getValue(); }
  public void setPostfix(String value) { this.postfixObjectProperty.setValue(value); }
  public ListSpinner<T> withPostfix(String value) { setPostfix(value); return this; }

  /** Prefix: */
  public ObjectProperty<String> prefixProperty() { return this.prefixObjectProperty; }
  final private ObjectProperty<String> prefixObjectProperty = new SimpleObjectProperty<String>(this, "prefix", "");
  public String getPrefix() { return this.prefixObjectProperty.getValue(); }
  public void setPrefix(String value) { this.prefixObjectProperty.setValue(value); }
  public ListSpinner<T> withPrefix(String value) { setPrefix(value); return this; }

  /** Items: */
  public ObjectProperty<ObservableList<T>> itemsProperty() { return this.itemsObjectProperty; }
  final private ObjectProperty<ObservableList<T>> itemsObjectProperty = new SimpleObjectProperty<ObservableList<T>>(this, "items", null)
  {
    public void set(ObservableList<T> value)
    {
      if (value == null) throw new NullPointerException("Null not allowed as the value for items");
      super.set(value);
    }
  };
  public ObservableList<T> getItems() { return this.itemsObjectProperty.getValue(); }
  public void setItems(ObservableList<T> value) { this.itemsObjectProperty.setValue(value); }
  public ListSpinner<T> withItems(ObservableList<T> value) { setItems(value); return this; }

  /** CellFactory: */
  public ObjectProperty<Callback<ListSpinner<T>, Node>> cellFactoryProperty() { return this.cellFactoryObjectProperty; }
  final private ObjectProperty<Callback<ListSpinner<T>, Node>> cellFactoryObjectProperty = new SimpleObjectProperty<Callback<ListSpinner<T>, Node>>(this, "cellFactory", new DefaultCellFactory());
  public Callback<ListSpinner<T>, Node> getCellFactory() { return this.cellFactoryObjectProperty.getValue(); }
  public void setCellFactory(Callback<ListSpinner<T>, Node> value) { this.cellFactoryObjectProperty.setValue(value); }
  public ListSpinner<T> withCellFactory(Callback<ListSpinner<T>, Node> value) { setCellFactory(value); return this; }

  /** StringConverter&lt;T&gt;: */
  public ObjectProperty<StringConverter<T>> stringConverterProperty() { return this.stringConverterObjectProperty; }
  final private ObjectProperty<StringConverter<T>> stringConverterObjectProperty = new SimpleObjectProperty<StringConverter<T>>(this, "stringConverter", new DefaultStringConverter());
  public StringConverter<T> getStringConverter() { return this.stringConverterObjectProperty.getValue(); }
  public void setStringConverter(StringConverter<T> value) { this.stringConverterObjectProperty.setValue(value); }
  public ListSpinner<T> withStringConverter(StringConverter<T> value) { setStringConverter(value); return this; }

  /** AddCallback: */
  public ObjectProperty<Callback<T, Integer>> addCallbackProperty() { return this.addCallbackObjectProperty; }
  final private ObjectProperty<Callback<T, Integer>> addCallbackObjectProperty = new SimpleObjectProperty<Callback<T, Integer>>(this, "addCallback", null);
  public Callback<T, Integer> getAddCallback() { return this.addCallbackObjectProperty.getValue(); }
  public void setAddCallback(Callback<T, Integer> value) { this.addCallbackObjectProperty.setValue(value); }
  public ListSpinner<T> withAddCallback(Callback<T, Integer> value) { setAddCallback(value); return this; }

  // ==================================================================================================================
  // StringConverter
 
  /**
   * A string converter that does a simple toString, but cannot convert to an object
   * @see org.jfxextras.util.StringConverterFactory
   */
  class DefaultStringConverter extends StringConverter<T>
  {
    @Override
    public T fromString(String string)
    {
      throw new IllegalStateException("No StringConverter is set. An editable Spinner must have a StringConverter to be able to render and parse the value.");
    }

    @Override
    public String toString(T value)
    {
      return value == null ? "" : value.toString();
    }
  }
 
  // ==================================================================================================================
  // CellFactory
 
  /**
   * Default cell factory
   */
  class DefaultCellFactory implements Callback<ListSpinner<T>, Node>
  {
    private Label label = null;
   
    @Override
    public Node call(ListSpinner<T> spinner)
    {
      // get value
      T lValue = spinner.getValue();
     
      // label not yet created
      if (this.label == null)
      {
        this.label = new Label();
      }
      this.label.setText( lValue == null ? "" : spinner.getPrefix() + getStringConverter().toString(lValue) + spinner.getPostfix() );
      return this.label;
    }
  };
 
  // ==================================================================================================================
  // EVENTS
 
  /** OnCycle: */
  public ObjectProperty<EventHandler<CycleEvent>> onCycleProperty() { return iOnCycleObjectProperty; }
  final private ObjectProperty<EventHandler<CycleEvent>> iOnCycleObjectProperty = new SimpleObjectProperty<EventHandler<CycleEvent>>(null);
  // java bean API
  public EventHandler<CycleEvent> getOnCycle() { return iOnCycleObjectProperty.getValue(); }
  public void setOnCycle(EventHandler<CycleEvent> value) { iOnCycleObjectProperty.setValue(value); }
  public ListSpinner<T> withOnCycle(EventHandler<CycleEvent> value) { setOnCycle(value); return this; }
  final static public String ONCYCLE_PROPERTY_ID = "onCycle";
 
  /**
   * CycleEvent
   */
  static public class CycleEvent extends Event
  {
    /**
     * The only valid EventType for the CycleEvent.
     */
    public static final EventType<CycleEvent> CYCLE = new EventType<CycleEvent>(Event.ANY, "CYCLE");

    /**
     *
     */
    public CycleEvent()
    {
      super(CYCLE);
    }

    /**
     *
     * @param source
     * @param target
     */
    public CycleEvent(Object source, EventTarget target)
    {
      super(source, target, new EventType<CycleEvent>());
    }
   
    public Object getOldIdx() { return this.oldIdx; }
    private Object oldIdx;
   
    public Object getNewIdx() { return this.newIdx; }
    private Object newIdx;
   
   
    public boolean cycledDown() { return cycleDirection == CycleDirection.TOP_TO_BOTTOM; }
    public boolean cycledUp() { return cycleDirection == CycleDirection.BOTTOM_TO_TOP; }
    CycleDirection cycleDirection;
  }
 
  /**
   * we're cycling, fire the event
   */
  public void fireCycleEvent(CycleDirection cycleDirection)
  {
    EventHandler<CycleEvent> lCycleEventHandler = getOnCycle();
    if (lCycleEventHandler != null)
    {
      CycleEvent lCycleEvent = new CycleEvent();
      lCycleEvent.cycleDirection = cycleDirection;
      lCycleEventHandler.handle(lCycleEvent);
    }
  }
  static public enum CycleDirection { TOP_TO_BOTTOM, BOTTOM_TO_TOP }
 
 
  // ==================================================================================================================
  // BEHAVIOR

  /**
   *
   */
  public void first()
  {
    // nothing to do
    if (getItems() == null || getItems().size() == 0) return;
   
    // set the new index (this will update the value)
    indexObjectProperty.setValue(0);
  }
 
  /**
   *
   */
  public void decrement()
  {
    // nothing to do
    if (getItems() == null || getItems().size() == 0) return;
   
    // get the current index
    int lOldIdx = this.indexObjectProperty.getValue();
         
    // get the previous index (usually current - 1)
    int lIdx = lOldIdx - 1;
   
    // if end
    if (lIdx < 0)
    {
      // if we're not cyclic
      if (isCyclic() != null && isCyclic().booleanValue() == false)
      {
        // do nothing
        return;
      }
     
      // cycle to the other end: get the last value
      lIdx = getItems().size() - 1;
     
      // notify listener that we've cycled
      fireCycleEvent(CycleDirection.BOTTOM_TO_TOP);
    }

    // set the new index (this will update the value)
    indexObjectProperty.setValue(lIdx);
  }
 
  /**
   *
   */
  public void increment()
  {
    // nothing to do
    if (getItems() == null || getItems().size() == 0) return;
   
    // get the current index
    int lOldIdx = this.indexObjectProperty.getValue();
   
    // get the next index (usually current + 1)
    int lIdx = lOldIdx + 1;
   
    // if null is return, there is no next index (usually current + 1)
    if (lIdx >= getItems().size())
    {
      // if we're not cyclic
      if (isCyclic() != null && isCyclic().booleanValue() == false)
      {
        // do nothing
        return;
      }
     
      // cycle to the other end: get the first value
      lIdx = 0;
     
      // notify listener that we've cycled
      fireCycleEvent(CycleDirection.TOP_TO_BOTTOM);
    }
   
    // set the new index (this will update the value)
    indexObjectProperty.setValue(lIdx);
  }

  /**
   * Get the last index; if the data provide is endless, this method mail fail!
   */
  public void last()
  {
    // nothing to do
    if (getItems() == null || getItems().size() == 0) return;
   
    // set the new index (this will update the value)
    indexObjectProperty.setValue(getItems().size() - 1);
  }

  /**
   * Does a o1.equals(o2) but also checks if o1 or o2 are null.
   * @param o1
   * @param o2
   * @return True if the two values are equal, false otherwise.
   */
  static public boolean equals(Object o1, Object o2)
  {
    if ( o1 == null && o2 == null ) return true;
    if ( o1 != null && o2 == null ) return false;
    if ( o1 == null && o2 != null ) return false;
    // TODO: compare arrays if (o1.getClass().isArray() && o2.getClass().isArray()) return Arrays.equals( (Object[])o1, (Object[])o2 );   
    return o1.equals(o2);
  }

}
TOP

Related Classes of jfxtras.scene.control.ListSpinner

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.