All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scalafx.collections.ObservableBuffer.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2011-2015, ScalaFX Project
 * 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 ScalaFX Project 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 THE SCALAFX PROJECT OR ITS CONTRIBUTORS 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 scalafx.collections

import java.{util => ju}
import javafx.collections.ObservableList
import javafx.{collections => jfxc}

import scala.collection.JavaConversions._
import scala.collection.generic.{CanBuildFrom, GenericCompanion, GenericTraversableTemplate, SeqFactory}
import scala.collection.mutable.ArrayBuffer
import scala.collection.{GenTraversableOnce, TraversableOnce, mutable}
import scala.language.implicitConversions
import scala.reflect.runtime.universe._
import scalafx.beans.Observable
import scalafx.delegate.SFXDelegate
import scalafx.event.subscriptions.Subscription

/**
  * Companion Object for [[scalafx.collections.ObservableBuffer]].
  *
  * @define OB `ObservableBuffer`
  * @define OL [[http://docs.oracle.com/javase/8/javafx/api/javafx/collections/ObservableList.html `ObservableList`]]
  * @define buf `Buffer`
  */
object ObservableBuffer extends SeqFactory[ObservableBuffer] {

  /**
    * Extracts an $OL from an $OB.
    *
    * @param ob ObservableBuffer
    */
  implicit def observableBuffer2ObservableList[T](ob: ObservableBuffer[T]): ObservableList[T] = if (ob != null) ob.delegate else null

  /**
    * The standard `CanBuildFrom` instance for $OB objects.
    */
  implicit def canBuildFrom[T]: CanBuildFrom[Coll, T, ObservableBuffer[T]] = new GenericCanBuildFrom[T] {
    override def apply() = newBuilder[T]
  }

  /**
    * The default builder for $OB objects.
    */
  def newBuilder[T]: mutable.Builder[T, ObservableBuffer[T]] = new ObservableBuffer

  // CHANGING INDICATORS - BEGIN

  /**
    * Trait that indicates a Change in an $OB. It is a simpler version of JavaFX's
    * [[http://docs.oracle.com/javase/8/javafx/api/javafx/collections/ListChangeListener.Change.html `ListChangeListener.Change`]],
    * where each subclass indicates a specific change operation.
    * Unlike JavaFX, all subclasses are exclusive to each other. This enables using pattern matching:
    * {{{
    * items.onChange((_, changes) => {
    *   for (change <- changes)
    *     change match {
    *       case Add(pos, added)                => ...
    *       case Remove(pos, removed)           => ...
    *       case Reorder(from, to, permutation) => ...
    *       case Update(pos, updated)           => ...
    *     }
    * })
    * }}}
    *
    * "replace" is represented as two changes `Remove` and `Add`.
    */
  sealed trait Change[T]

  /**
    * Indicates an Addition in an $OB.
    *
    * @param position Position from where new elements were added
    * @param added elements added
    *
    * @see [[http://docs.oracle.com/javase/8/javafx/api/javafx/collections/ListChangeListener.Change.html#wasUpdated() `ListChangeListener.Change.wasAdded()`]]
    * @see [[http://docs.oracle.com/javase/8/javafx/api/javafx/collections/ListChangeListener.Change.html#getFrom() `ListChangeListener.Change.getFrom()`]]
    * @see [[http://docs.oracle.com/javase/8/javafx/api/javafx/collections/ListChangeListener.Change.html#getAddedSubList() `ListChangeListener.Change.getAddedSubList()`]]
    */
  case class Add[T](position: Int, added: Traversable[T]) extends Change[T]

  /**
    * Indicates a Removal in an $OB.
    *
    * @param position Position from where elements were removed
    * @param removed elements removed
    *
    * @see [[http://docs.oracle.com/javase/8/javafx/api/javafx/collections/ListChangeListener.Change.html#wasUpdated() `ListChangeListener.Change.wasRemoved()`]]
    * @see [[http://docs.oracle.com/javase/8/javafx/api/javafx/collections/ListChangeListener.Change.html#getFrom() `ListChangeListener.Change.getFrom()`]]
    * @see [[http://docs.oracle.com/javase/8/javafx/api/javafx/collections/ListChangeListener.Change.html#getRemoved() `ListChangeListener.Change.getRemoved()`]]
    */
  case class Remove[T](position: Int, removed: Traversable[T]) extends Change[T]

  /**
    * Indicates a Reordering in an $OB.
    *
    * @param start The start of the change interval.
    * @param end The end of the change interval.
    * @param permutation Function that indicates the permutation that happened. The argument indicates the old index
    *                    that contained the element prior to this change. Its return is the new index of the same element.
    *
    * @see [[http://docs.oracle.com/javase/8/javafx/api/javafx/collections/ListChangeListener.Change.html#wasUpdated() `ListChangeListener.Change.wasPermutated()`]]
    * @see [[http://docs.oracle.com/javase/8/javafx/api/javafx/collections/ListChangeListener.Change.html#getFrom() `ListChangeListener.Change.getFrom()`]]
    * @see [[http://docs.oracle.com/javase/8/javafx/api/javafx/collections/ListChangeListener.Change.html#getTo() `ListChangeListener.Change.getTo()`]]
    * @see [[http://docs.oracle.com/javase/8/javafx/api/javafx/collections/ListChangeListener.Change.html#getPermutation(int) `ListChangeListener.Change.getPermutation(int)`]]
    */
  case class Reorder[T](start: Int, end: Int, permutation: (Int => Int)) extends Change[T]

  /**
    * Indicates an Update in an $OB.
    *
    * @param from Position from where elements were updated
    * @param to Position to where elements were updated (exclusive)
    * @see [[http://docs.oracle.com/javase/8/javafx/api/javafx/collections/ListChangeListener.Change.html#wasUpdated() `ListChangeListener.Change.wasUpdated()`]]
    * @see [[http://docs.oracle.com/javase/8/javafx/api/javafx/collections/ListChangeListener.Change.html#getFrom() `ListChangeListener.Change.getFrom()`]]
    * @see [[http://docs.oracle.com/javase/8/javafx/api/javafx/collections/ListChangeListener.Change.html#getTo() `ListChangeListener.Change.getTo()`]]
    */
  case class Update[T](from: Int, to: Int) extends Change[T]


  // CHANGING INDICATORS - END

  // CREATION METHODS - BEGIN

  /**
    * Creates a new $OB from a sequence of elements.
    *
    * @param items Sequence of elements
    * @return new $OB from items
    */
  def apply[T](items: Seq[T]): ObservableBuffer[T] =
    new ObservableBuffer[T](jfxc.FXCollections.observableArrayList[T](items))

  // CREATION METHODS - END

  // HELPER METHODS (ORIGINATED FROM FXCOLLECTIONS) - BEGIN

  /**
    * Shuffles all elements in the $OB. Fires only '''one''' change notification on the $buf.
    *
    * @param buffer Buffer to be shuffled
    */
  def shuffle[T](buffer: ObservableBuffer[T]) {
    jfxc.FXCollections.shuffle(buffer)
  }

  /**
    * Shuffles all elements in the $OB. Fires only '''one''' change notification on the $buf.
    *
    * @param buffer Buffer to be shuffled
    * @param rnd the random generator used for shuffling
    */
  def shuffle[T](buffer: ObservableBuffer[T], rnd: ju.Random) {
    jfxc.FXCollections.shuffle(buffer, rnd)
  }

  /**
    * Concatenates more $OB's into one.
    *
    * @param buffers $buf to concatenate
    */
  def concat[T](buffers: ObservableBuffer[T]*): ObservableBuffer[T] = {
    val lists: java.util.List[jfxc.ObservableList[T]] = new java.util.ArrayList[jfxc.ObservableList[T]]
    buffers.foreach(buf => lists.add(buf.delegate))

    ObservableBuffer[T](jfxc.FXCollections.concat(lists: _*))
  }

  /**
    * Revert the order in the $OB. Fires only '''one''' change notification on the list.
    *
    * ''Implementation note'': This method uses
    * [[http://docs.oracle.com/javase/8/javafx/api/javafx/collections/FXCollections.html#reverse(javafx.collections.ObservableList) `reverse`]]
    * method from
    * [[http://docs.oracle.com/javase/8/javafx/api/javafx/collections/FXCollections.html `javafx.collections.FXCollections`]].
    * It is not called `reverse` to not confuse with method with same name from [[scala.collection.mutable.Buffer]]
    *
    * @param buffer $buf to be reverted.
    */
  def revertBuffer[T](buffer: ObservableBuffer[T]) {
    jfxc.FXCollections.reverse(buffer.delegate)
  }

  /**
    * Fills the provided $buf with obj. Fires only one change notification on the $buf.
    *
    * @param buffer $buf to Fill
    * @param obj the object to fill the $buf with
    */
  def fillAll[T](buffer: ObservableBuffer[T], obj: T) {
    jfxc.FXCollections.fill(buffer, obj)
  }

  /**
    * Rotates the $buf by distance. Fires only one change notification on the $buf.
    *
    * @param buffer the $buf to be rotated
    * @param distance the distance of rotation
    */
  def rotate[T](buffer: ObservableBuffer[T], distance: Int) {
    jfxc.FXCollections.rotate(buffer, distance)
  }

  // HELPER METHODS - END

}

/**
  * Wrapper class to JavaFX's
  * [[http://docs.oracle.com/javase/8/javafx/api/javafx/collections/ObservableList.html $OL]].
  *
  * @tparam T Type of this $buf
  *
  * @define OB `ObservableBuffer`
  * @define OL `ObservableList`
  * @define ownOB The $OB itself.
  * @define buf `Buffer`
  * @define WhyOverride Overridden method to make it behave like a wrapped $OL.
  * @define noCL The new $OB won't have Change and Invalidation Listeners from original $buf.
  *
  */
class ObservableBuffer[T](override val delegate: jfxc.ObservableList[T] = jfxc.FXCollections.observableArrayList[T])
  extends mutable.Buffer[T]
    with mutable.BufferLike[T, ObservableBuffer[T]]
  with GenericTraversableTemplate[T, ObservableBuffer]
    with mutable.Builder[T, ObservableBuffer[T]]
  with Observable
  with SFXDelegate[jfxc.ObservableList[T]] {

  /**
    * The factory companion object that builds instances of class $OB.
    */
  override def companion: GenericCompanion[ObservableBuffer] = ObservableBuffer

  /**
    * Produces an $OB from the added elements.
    */
  def result() = this

  /**
    * Creates a new $OB containing both the elements of this $buf and the
    * provided traversable object. $WhyOverride $noCL
    *
    * @param xs The traversable object.
    * @return A new $OB consisting of all the elements of this $buf and `xs`. $noCL
    */
  override def ++(xs: GenTraversableOnce[T]) = {
    val ob = new ObservableBuffer[T]
    ob.appendAll(this)
    ob.appendAll(xs.toList)
    ob
  }

  /**
    * Appends a single element to this $OB. $WhyOverride
    *
    * @param elem the element to add.
    * @return $ownOB
    */
  def +=(elem: T) = {
    delegate.add(elem)
    this
  }

  /**
    * Appends two or more elements to this $OB. $WhyOverride
    *
    * @param elem1 First element to add
    * @param elem2 Second element to add
    * @param elems Other elements to add
    * @return $ownOB
    */
  override def +=(elem1: T, elem2: T, elems: T*) =
    this ++= (mutable.Buffer(elem1, elem2) ++= elems).toTraversable

  /**
    * Adds all elements produced by a TraversableOnce to this $OB. $WhyOverride
    *
    * @param xs traversable object.
    * @return $ownOB
    */
  override def ++=(xs: TraversableOnce[T]) = {
    delegate.addAll(xs.toIterable)
    this
  }

  /**
    * Prepends a single element to this buffer. $WhyOverride
    *
    * @param elem Element to prepend
    * @return $ownOB
    */
  def +=:(elem: T) = {
    delegate.add(0, elem)
    this
  }

  /**
    * Appends a single element to this buffer. $WhyOverride
    *
    * @param elem Element to append
    * @return $ownOB
    */
  def :+=(elem: T) = {
    delegate.add(elem)
    this
  }

  /**
    * Creates a new $OB with all the elements of this collection except `elem`. $noCL
    *
    * @param elem Element to remove
    * @return A new $OB consisting of all the elements of this $buf except `elem`. $noCL
    */
  override def -(elem: T) = {
    val ob = new ObservableBuffer[T]
    this.filterNot(_ != elem).foreach(ob += _)
    ob
  }

  /**
    * Creates a new collection with all the elements of this collection except the two
    * or more specified elements. $noCL
    *
    * @param elem1 First element to remove
    * @param elem2 Second element to remove
    * @param elems Other elements to remove
    * @return a new $OB consisting of all the elements of this $buf except `elem1`, `elem2` and
    *         those in `elems`. $noCL
    */
  override def -(elem1: T, elem2: T, elems: T*) = {
    val xs = mutable.Buffer(elem1, elem2) ++= elems
    val ob = new ObservableBuffer[T]
    this.filterNot(xs.contains(_)).foreach(ob += _)
    ob
  }

  /**
    * Creates a new $OB with all the elements of this $buf except those provided by
    * the specified traversable object. $noCL
    *
    * @param xs The traversable object.
    * @return A new $OB with all the elements of this $buf except those in `xs`. $noCL
    */
  override def --(xs: GenTraversableOnce[T]) = {
    val ob = new ObservableBuffer[T]
    val list = xs.toList
    this.filterNot(list.contains(_)).foreach(ob += _)
    ob
  }

  /**
    * Removes two or more elements from this $OB.
    *
    * @param elem1 First element to remove
    * @param elem2 Second element to remove
    * @param elems Other elements to remove
    * @return $ownOB
    */
  override def -=(elem1: T, elem2: T, elems: T*) = {
    delegate.removeAll(mutable.Buffer(elem1, elem2) ++= elems)
    this
  }

  /**
    * Removes all elements produced by an iterator from this buffer.
    *
    * @param xs the traversable object with elements to remove.
    * @return $ownOB
    */
  override def --=(xs: TraversableOnce[T]) = {
    delegate.removeAll(xs.toIterable)
    this
  }

  /**
    * Selects an element by its index in the buffer.
    *
    * @param n index
    * @return Element at position `n`
    */
  def apply(n: Int) = delegate.get(n)

  /**
    * Clears the $OB's contents. After this operation, the $buf is empty.
    */
  def clear() {
    delegate.clear()
  }

  /**
    * Inserts new elements at a given index into this $buf.
    *
    * @param n the index where new elements are inserted.
    * @param elems  the traversable collection containing the elements to insert.
    */
  def insertAll(n: Int, elems: Traversable[T]) {
    delegate.addAll(n, elems.toIterable)
  }

  /**
    * Creates a new [[http://www.scala-lang.org/api/current/scala/collection/Iterator.html `Iterator`]].
    */
  def iterator = new Iterator[T] {
    val it = delegate.iterator

    def hasNext = it.hasNext

    def next() = it.next()
  }

  /**
    * Length of this $OB.
    */
  def length = delegate.size

  /**
    * Removes the element at a given index from this $OB.
    *
    * @param n index the index of the element to be removed
    * @return Removed element
    */
  def remove(n: Int) = delegate.remove(n)

  /**
    * Removes a number of elements from a given index position. $WhyOverride
    *
    * '''Note''': This method conflicts with method with same signature in
    * [[http://docs.oracle.com/javase/8/javafx/api/javafx/collections/ObservableList.html#remove(int,int) $OL]].
    * There the arguments indicate a range of index of elements to be removed. Here the arguments indicate the first
    * index of range and the quantity of elements to be removed. If you want a functionality equivalent to JavaFX
    * $OL, use `removeRange`.
    *
    * @param n the index which refers to the first element to remove.
    * @param count  the number of elements to remove.
    */
  override def remove(n: Int, count: Int) {
    delegate.subList(n, n + count).clear()
  }

  /**
    * Remove a range of elements. Use this method if you want a functionality such as
    * [[http://docs.oracle.com/javase/8/javafx/api/javafx/collections/ObservableList.html#remove(int,int) the method]]
    * with same signature in $OL.
    *
    * @param from the start of the range to remove (inclusive)
    * @param to  the end of the range to remove (exclusive)
    */
  def removeRange(from: Int, to: Int) {
    delegate.remove(from, to)
  }

  /**
    * Replaces element at given index with a new value.
    *
    * @param n the index of the element to replace.
    * @param newelem new value to be positioned at position n.
    */
  def update(n: Int, newelem: T) {
    delegate.set(n, newelem)
  }

  /**
    * Retains only the elements in this list that are contained in the specified collection.
    * In other words, removes from this list all the elements that are not contained in the
    * specified collection.
    *
    * @param elems the traversable collection containing elements to be retained in this list
    */
  def retainAll(elems: T*) {
    delegate.retainAll(elems)
  }

  /**
    * Retains only the elements in this list that are contained in the specified collection.
    * In other words, removes from this list all the elements that are not contained in the
    * specified collection.
    *
    * @param elems the traversable collection containing elements to be retained in this list
    */
  def retainAll(elems: Iterable[T]) {
    delegate.retainAll(elems)
  }

  /**
    * Replace all oldVal elements in the list with newVal element. Fires only '''one''' change
    * notification on the list.
    *
    * @param oldVal The element that is going to be replace
    * @param newVal The replacement
    * @return `true` if the list was modified
    */
  def replaceAll(oldVal: T, newVal: T): Boolean = jfxc.FXCollections.replaceAll(this.delegate, oldVal, newVal)

  /**
    * Sorts this $OB if its type implements ''natural ordering''. This type must be a
    * [[http://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html `java.util.Comparable`]] subclass.
    * Otherwise it will throws a `IllegalStateException`.
    *
    * @param typeTag information about if this type is a `Comparable` subclass or not.
    */
  def sort()(implicit typeTag: WeakTypeTag[T]) {
    if (typeTag.tpe <:< typeOf[Comparable[_]]) {
      jfxc.FXCollections.sort(delegate, new ju.Comparator[T] {
        def compare(p1: T, p2: T) = p1.asInstanceOf[Comparable[T]].compareTo(p2)
      })
    } else {
      throw new IllegalStateException("Type of this Observable List does not implement " +
        "java.util.Comparable. Please use a Comparator function.")
    }
  }

  /**
    * Sorts this $OB using a Comparator function
    *
    * @param lt Comparator function that returns `true` if first element was lesser than second
    *           or `false` otherwise.
    */
  def sort(lt: (T, T) => Boolean) {
    jfxc.FXCollections.sort(delegate, new ju.Comparator[T] {
      def compare(p1: T, p2: T) = if (lt(p1, p2)) -1 else if (lt(p2, p1)) 1 else 0
    })
  }

  import scalafx.collections.ObservableBuffer._

  /**
    * Add a listener function to list's changes. This function '''will handle''' this buffer's
    * modifications data.
    *
    * @param op Function that will handle this $OB's modifications data to be activated when
    *           some change was made.
    * @return A subscription object
    */
  def onChange[T1 >: T](op: (ObservableBuffer[T], Seq[Change[T1]]) => Unit): Subscription = {
    val listener = new jfxc.ListChangeListener[T1] {
      def onChanged(c: jfxc.ListChangeListener.Change[_ <: T1]) {
        var changes = ArrayBuffer.empty[Change[T1]]
        while (c.next()) {
          if (c.wasPermutated()) {
            changes += Reorder(c.getFrom, c.getTo, {
              x => c.getPermutation(x)
            })
          } else if (c.wasUpdated()) {
            changes += Update(c.getFrom, c.getTo)
          } else {
            if (c.wasRemoved()) {
              changes += Remove(c.getFrom, c.getRemoved)
            }
            if (c.wasAdded()) {
              changes += Add(c.getFrom, c.getAddedSubList)
            }
          }
        }
        op(ObservableBuffer.this, changes)
      }
    }

    delegate.addListener(listener)

    new Subscription {
      def cancel() {
        delegate.removeListener(listener)
      }
    }
  }

  /**
    * Add a listener function to list's changes. This function '''will not handle''' this buffer's
    * modifications data.
    *
    * @param op No-argument function to be activated when some change in this $OB was made.
    * @return A `subscription` object
    */
  def onChange[T1 >: T](op: => Unit): Subscription = {
    val listener = new jfxc.ListChangeListener[T1] {
      def onChanged(c: jfxc.ListChangeListener.Change[_ <: T1]) {
        op
      }
    }

    delegate.addListener(listener)

    new Subscription {
      def cancel() {
        delegate.removeListener(listener)
      }
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy