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

scalafx.scene.control.TreeTableColumn.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2011-2022, 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.scene.control

import javafx.beans.value as jfxbv
import javafx.scene.control as jfxsc
import javafx.{css as jfxcss, event as jfxe, util as jfxu}
import scalafx.Includes.*
import scalafx.beans.property.{ObjectProperty, ReadOnlyObjectProperty}
import scalafx.beans.value.ObservableValue
import scalafx.collections.ObservableBuffer
import scalafx.delegate.{SFXDelegate, SFXEnumDelegate, SFXEnumDelegateCompanion}
import scalafx.event.{Event, EventType}

import scala.collection.JavaConverters.*
import scala.language.implicitConversions

/**
 * Object companion for [[scalafx.scene.control.TreeTableColumn]]
 */
object TreeTableColumn {

  /**
   * Converts a ScalaFX TreeTableColumn instance to its JavaFX counterpart.
   *
   * @param v ScalaFX TreeTableColumn
   * @return JavaFX TreeTableColumn
   */
  implicit def sfxTreeTableColumn2jfx[S, T](v: TreeTableColumn[S, T]): jfxsc.TreeTableColumn[S, T] =
    if (v != null) v.delegate else null

  /**
   * Object companion for [[scalafx.scene.control.TreeTableColumn.CellDataFeatures]]
   */
  object CellDataFeatures {
    implicit def sfxCellDataFeatures2jfx[S, T](v: CellDataFeatures[S, T])
      : jfxsc.TreeTableColumn.CellDataFeatures[S, T] =
      if (v != null) v.delegate else null
  }

  /**
   * Wraps a JavaFX [[http://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/TreeTableColumn.CellDataFeatures.html]]
   *
   * @constructor Creates a new CellDataFeatures from a JavaFX one.
   * @param delegate A JavaFX CellDataFeatures to be wrapped. Its default value is a new JavaFX CellDataFeatures.
   * @tparam S The TreeTableView type
   * @tparam T The TreeTableColumn type
   * @since 8.0
   */
  class CellDataFeatures[S, T](override val delegate: jfxsc.TreeTableColumn.CellDataFeatures[S, T])
      extends SFXDelegate[jfxsc.TreeTableColumn.CellDataFeatures[S, T]] {

    /**
     * Instantiates a CellDataFeatures instance with the given properties set as read-only values of this instance.
     *
     * @param treeTableView   The TreeTableView that this instance refers to.
     * @param treeTableColumn The TreeTableColumn that this instance refers to.
     * @param value           The value for a row in the TreeTableView.
     * @return
     */
    def this(treeTableView: TreeTableView[S], treeTableColumn: TreeTableColumn[S, T], value: S) =
      this(new jfxsc.TreeTableColumn.CellDataFeatures[S, T](treeTableView, treeTableColumn, new TreeItem[S](value)))

    /**
     * Returns the TreeTableColumn passed in to the constructor.
     */
    def treeTableColumn: TreeTableColumn[S, T] = delegate.getTreeTableColumn

    /**
     * Returns the TreeTableView passed in to the constructor.
     */
    def treeTableView: TreeTableView[S] = delegate.getTreeTableView

    /**
     * Returns the value passed in to the constructor.
     */
    def value: TreeItem[S] = delegate.getValue
  }

  object CellEditEvent {

    /**
     * Converts a ScalaFX CellEditEvent instance to its JavaFX counterpart.
     *
     * @param cee ScalaFX CellEditEvent
     * @return JavaFX CellEditEvent
     */
    implicit def sfxCellEditEvent2jfx[S, T](cee: CellEditEvent[S, T]): jfxsc.TreeTableColumn.CellEditEvent[S, T] =
      if (cee != null) cee.delegate else null

  }

  /**
   * Wraps a JavaFX [[http://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/TreeTableColumn.CellEditEvent.html]]
   *
   * @constructor Creates a new CellDataFeatures from a JavaFX one.
   * @param delegate A JavaFX CellDataFeatures to be wrapped. Its default value is a new JavaFX CellDataFeatures.
   * @tparam S The TreeTableView type
   * @tparam T The TreeTableColumn type
   * @since 8.0
   */
  class CellEditEvent[S, T](override val delegate: jfxsc.TreeTableColumn.CellEditEvent[S, T])
      extends Event(delegate)
      with SFXDelegate[jfxsc.TreeTableColumn.CellEditEvent[S, T]] {

    /**
     * Creates a new event that can be subsequently fired to the relevant listeners.
     *
     * @param table     The TreeTableView on which this event occurred.
     * @param pos       The position upon which this event occurred.
     * @param eventType The type of event that occurred.
     * @param newValue  The value input by the end user.
     */
    def this(
      table: TreeTableView[S],
      pos: TreeTablePosition[S, T],
      eventType: jfxe.EventType[jfxsc.TreeTableColumn.CellEditEvent[S, T]],
      newValue: T
    ) =
      this(new jfxsc.TreeTableColumn.CellEditEvent(table, pos, eventType, newValue))

    /**
     * Returns the TreeTableView upon which this event occurred.
     */
    def treeTableView: TreeTableView[S] = delegate.getTreeTableView

    /**
     * Returns the TreeTableColumn upon which this event occurred.
     */
    def tableColumn: TreeTableColumn[S, T] = delegate.getTableColumn

    /**
     * Returns the position upon which this event occurred.
     */
    def treeTablePosition: TreeTablePosition[S, T] = delegate.getTreeTablePosition

    /**
     * Returns the new value input by the end user.
     */
    def newValue: T = delegate.getNewValue

    /**
     * Attempts to return the old value at the position referred to in the TreeTablePosition returned by `treeTablePosition`.
     */
    def oldValue: T = delegate.getOldValue

    /**
     * Convenience method that returns the value for the row (that is, from the TableView items list), for the row
     * contained within the TablePosition returned in `tablePosition`.
     */
    def rowValue: TreeItem[S] = delegate.getRowValue

  }

  object SortType extends SFXEnumDelegateCompanion[jfxsc.TreeTableColumn.SortType, SortType] {

    /** Column will be sorted in an ascending order. */
    case object Ascending extends SortType(jfxsc.TreeTableColumn.SortType.ASCENDING)

    /** Column will be sorted in a descending order. */
    case object Descending extends SortType(jfxsc.TreeTableColumn.SortType.DESCENDING)

    protected override def unsortedValues: Array[SortType] = Array(Ascending, Descending)

  }

  /** Wrapper for [[http://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/TreeTableColumn.SortType.html]] */
  sealed abstract class SortType(override val delegate: jfxsc.TreeTableColumn.SortType)
      extends SFXEnumDelegate[jfxsc.TreeTableColumn.SortType]

  /**
   * If no cellFactory is specified on a TreeTableColumn instance, then this one will be used by default.
   */
  val DefaultCellFactory: TreeTableColumn[?, ?] => TreeTableCell[?, ?] =
    (column: TreeTableColumn[?, ?]) => jfxsc.TreeTableColumn.DEFAULT_CELL_FACTORY.call(column)

  /**
   * Parent event for any TreeTableColumn edit event.
   */
  def editAnyEvent: EventType[jfxsc.TreeTableColumn.CellEditEvent[Nothing, Nothing]] =
    new EventType(jfxsc.TreeTableColumn.editAnyEvent)

  /**
   * Indicates that the editing has been canceled, meaning that no change should be made to the backing data source.
   */
  def editCancelEvent: EventType[jfxsc.TreeTableColumn.CellEditEvent[Nothing, Nothing]] =
    new EventType(jfxsc.TreeTableColumn.editCancelEvent)

  /**
   * Indicates that the editing has been committed by the user, meaning that a change should be made to the backing
   * data source to reflect the new data.
   */
  def editCommitEvent: EventType[jfxsc.TreeTableColumn.CellEditEvent[Nothing, Nothing]] =
    new EventType(jfxsc.TreeTableColumn.editCommitEvent)

  /**
   * Indicates that the user has performed some interaction to start an edit event, or alternatively the
   * TableView.edit(Int, TableColumn) method has been called.
   */
  def editStartEvent: EventType[jfxsc.TreeTableColumn.CellEditEvent[Nothing, Nothing]] =
    new EventType(jfxsc.TreeTableColumn.editStartEvent)

  /**
   * The CssMetaData of this Styleable. This may be returned as an unmodifiable list.
   */
  def classCssMetaData: Seq[jfxcss.CssMetaData[? <: jfxcss.Styleable, ?]] =
    jfxsc.TreeTableColumn.getClassCssMetaData.asScala.toSeq

}

/**
 * A `TreeTableView` is made up of a number of `TreeTableColumn` instances.
 * Each `TreeTableColumn` in a `TreeTableView` is responsible for displaying (and editing) the contents of that column.
 *
 * Wraps a JavaFX [[https://openjfx.io/javadoc/16/javafx.controls/javafx/scene/control/TreeTableColumn.html TreeTableColumn]]
 *
 * @constructor Creates a new `TreeTableColumn` from a JavaFX one.
 * @param delegate A JavaFX TreeTableColumn to be wrapped. Its default value is a new JavaFX TreeTableColumn.
 * @tparam S The type of the TreeTableView generic type (i.e. S == TreeTableView)
 * @tparam T The type of the content in all cells in this TreeTableColumn.
 * @since 8.0
 */
class TreeTableColumn[S, T](override val delegate: jfxsc.TreeTableColumn[S, T] = new jfxsc.TreeTableColumn[S, T]())
    extends TableColumnBase[jfxsc.TreeItem[S], T](delegate)
    with SFXDelegate[jfxsc.TreeTableColumn[S, T]] {

  /**
   * Creates a TreeTableColumn with the text set to the provided string, with default cell factory, comparator,
   * and onEditCommit implementation.
   *
   * @param text The string to show when the TreeTableColumn is placed within the TreeTableView.
   */
  def this(text: String) = this(new jfxsc.TreeTableColumn[S, T](text))

  /**
   * The cell factory for all cells in this column. The cell factory is responsible for rendering the data contained
   * within each TreeTableCell for a single TreeTableColumn.
   *
   * By default TreeTableColumn uses a default cell factory, but this can be replaced with a custom implementation,
   * for example to show data in a different way or to support editing. There is a lot of documentation on creating
   * custom cell factories elsewhere (see Cell and TreeTableView for example).
   *
   * Finally, there are a number of pre-built cell factories available in the javafx.scene.control.cell package.
   */
  def cellFactory: ObjectProperty[jfxu.Callback[jfxsc.TreeTableColumn[S, T], jfxsc.TreeTableCell[S, T]]] =
    delegate.cellFactoryProperty
  def cellFactory_=(callback: jfxu.Callback[jfxsc.TreeTableColumn[S, T], jfxsc.TreeTableCell[S, T]]): Unit = {
    delegate.cellFactoryProperty.setValue(callback)
  }
  def cellFactory_=(f: TreeTableColumn[S, T] => TreeTableCell[S, T]): Unit = {
    delegate.cellFactoryProperty.setValue(
      new jfxu.Callback[jfxsc.TreeTableColumn[S, T], jfxsc.TreeTableCell[S, T]] {
        def call(v: jfxsc.TreeTableColumn[S, T]): jfxsc.TreeTableCell[S, T] = {
          f(v)
        }
      }
    )
  }

  /**
   * This is a helper method for easy creation of custom cell factories.
   * The custom cell is automatically created, and it handles rendering of empty cells.
   * The user is only responsible for providing an operation `op` that renders non-empty cells.
   *
   * The operation `op` provides as input the already created custom `cell` and `value` of that cell.
   * The `value` is provided by the `cellValueFactory`. The `value` is guaranteed to be non `null`.
   * The `null` values are automatically rendered as empty cells by the provided `cell` object.
   *
   * Here is an example where value's type is a case class `Person` that contains two text fields: `firstName` and `lastName`.
   * {{{
   *   case class Person(firstName:String, lastName:String)
   *   ...
   *
   *   cellFactory = (cell, value) => {
   *     cell.text = value.firstName + " " + value.lastName
   *   }
   * }}}
   *
   * Another example where 'value' is of type 'Color' and the cell factory creates a circle representing that color:
   * {{{
   *   cellFactory = (cell, value) => {
   *     cell.graphic = new Circle {
   *        fill = value
   *        radius = 8
   *     }
   *   }
   * }}}
   *
   * @param op a method that will create content for a given `cell`.
   *           It gets as an input automatically created custom `cell` and a non-null `value` of that cell.
   *           `op` is called in the cell's `updateItem` method.
   */
  def cellFactory_=(op: (TreeTableCell[S, T], T) => Unit): Unit = {
    val callback =
      Option(op)
        .map { op =>
          new jfxu.Callback[jfxsc.TreeTableColumn[S, T], jfxsc.TreeTableCell[S, T]] {
            def call(tv: jfxsc.TreeTableColumn[S, T]): jfxsc.TreeTableCell[S, T] = {
              new jfxsc.TreeTableCell[S, T] {
                val sfxThis = new TreeTableCell(this)
                override def updateItem(item: T, empty: Boolean): Unit = {
                  super.updateItem(item, empty)
                  if (empty || item == null) {
                    setText(null)
                    setGraphic(null)
                  } else {
                    op(sfxThis, item)
                  }
                }
              }
            }
          }
        }
        .orNull

    delegate.cellFactoryProperty.setValue(callback)
  }

  /**
   * The TreeTableView that this TreeTableColumn belongs to.
   */
  def treeTableView: ReadOnlyObjectProperty[jfxsc.TreeTableView[S]] = delegate.treeTableViewProperty

  /**
   * The cell value factory needs to be set to specify how to populate all cells within a single TreeTableColumn. A cell
   * value factory is a Callback that provides a TreeTableColumn.CellDataFeatures instance, and expects an
   * ObservableValue to be returned. The returned ObservableValue instance will be observed internally to allow for
   * updates to the value to be immediately reflected on screen.
   *
   * An example of how to set a cell value factory is:
   * {{{
   *   // p.value returns the TreeItem[Person] instance for a particular TreeTableView row,
   *   // p.value.value returns the Person instance inside the TreeItem[Person]
   *   cellValueFactory = { p => p.value.value.firstName }
   * }}}
   * A simple complete example (from `scalafx-demos` `SimpleTreeTableView`):
   * {{{
   * import scalafx.application.JFXApp3
   * import scalafx.application.JFXApp3.PrimaryStage
   * import scalafx.controls.tableview.Person
   * import scalafx.scene.Scene
   * import scalafx.scene.control.TreeTableColumn._
   * import scalafx.scene.control.{TreeItem, TreeTableColumn, TreeTableView}
   *
   * object SimpleTreeTableView extends JFXApp3 {
   *
   *  override def start(): Unit = {
   *
   *    val treeRoot = new TreeItem[Person](new Person("Peggy", "Sue", "555-6798"))
   *
   *    treeRoot.children += new TreeItem[Person](new Person("Rocky", "Raccoon", "555-6798"))
   *
   *    stage = new PrimaryStage {
   *      title = "Simple Table View"
   *      scene = new Scene {
   *        content = new TreeTableView[Person](treeRoot) {
   *          columns ++= Seq(
   *            new TreeTableColumn[Person, String] {
   *              text = "First Name"
   *              cellValueFactory = _.value.getValue.firstName
   *              prefWidth = 180
   *            },
   *            new TreeTableColumn[Person, String]() {
   *              text = "Last Name"
   *              cellValueFactory = _.value.getValue.lastName
   *              prefWidth = 180
   *            }
   *          )
   *        }
   *      }
   *    }
   *  }
   * }
   * }}}
   *
   * A common approach is to want to populate cells in a TreeTableColumn using a single value from a Java bean. To
   * support this common scenario, there is the TreeItemPropertyValueFactory class. Refer to this class for more
   * information on how to use it, but briefly here is how the above use case could be simplified using the
   * TreeItemPropertyValueFactory class:
   *
   * firstNameCol.cellValueFactory = new TreeItemPropertyValueFactory[Person,String]("firstName"))
   */
  def cellValueFactory: ObjectProperty[TreeTableColumn.CellDataFeatures[S, T] => ObservableValue[T, T]] =
    ObjectProperty((features: TreeTableColumn.CellDataFeatures[S, T]) =>
      jfxObservableValue2sfx[T](delegate.cellValueFactoryProperty.getValue.call(features))
    )

  def cellValueFactory_=(f: TreeTableColumn.CellDataFeatures[S, T] => ObservableValue[T, T]): Unit = {
    delegate.cellValueFactoryProperty.setValue(
      new jfxu.Callback[jfxsc.TreeTableColumn.CellDataFeatures[S, T], jfxbv.ObservableValue[T]] {
        def call(v: jfxsc.TreeTableColumn.CellDataFeatures[S, T]): jfxbv.ObservableValue[T] = {
          Option(f(v)).map(_.delegate).orNull
        }
      }
    )
  }

  /**
   * Used to state whether this column, if it is part of the TableView.sortOrder ObservableList, should be sorted in
   * ascending or descending order.
   */
  def sortType: ObjectProperty[jfxsc.TreeTableColumn.SortType] = delegate.sortTypeProperty

  def sortType_=(v: TreeTableColumn.SortType): Unit = {
    sortType() = v
  }

  /**
   * This event handler will be fired when the user successfully initiates editing.
   */
  def onEditStart: ObjectProperty[jfxe.EventHandler[jfxsc.TreeTableColumn.CellEditEvent[S, T]]] =
    delegate.onEditCommitProperty

  def onEditStart_=(v: jfxe.EventHandler[jfxsc.TreeTableColumn.CellEditEvent[S, T]]): Unit = {
    onEditStart() = v
  }

  /**
   * This event handler will be fired when the user successfully commits their editing.
   */
  def onEditCommit: ObjectProperty[jfxe.EventHandler[jfxsc.TreeTableColumn.CellEditEvent[S, T]]] =
    delegate.onEditCommitProperty

  def onEditCommit_=(v: jfxe.EventHandler[jfxsc.TreeTableColumn.CellEditEvent[S, T]]): Unit = {
    onEditCommit() = v
  }

  /**
   * This event handler will be fired when the user cancels editing a cell.
   */
  def onEditCancel: ObjectProperty[jfxe.EventHandler[jfxsc.TreeTableColumn.CellEditEvent[S, T]]] =
    delegate.onEditCancelProperty

  def onEditCancel_=(v: jfxe.EventHandler[jfxsc.TreeTableColumn.CellEditEvent[S, T]]): Unit = {
    onEditCancel() = v
  }

  /**
   * This enables support for nested columns, which can be useful to group together related data. For example, we may
   * have a 'Name' column with two nested columns for 'First' and 'Last' names.
   *
   * This has no impact on the table as such - all column indices point to the leaf columns only, and it isn't
   * possible to sort using the parent column, just the leaf columns. In other words, this is purely a visual feature.
   *
   * @return An ObservableBuffer containing TableColumnBase instances (or subclasses) that are the children of this
   *         TableColumnBase. If these children TableColumnBase instances are set as visible, they will appear
   *         beneath this table column.
   */
  def columns: ObservableBuffer[jfxsc.TreeTableColumn[S, ?]] = delegate.getColumns

  /**
   * Attempts to return an ObservableValue for the item in the given index (which is of type S). In other words,
   * this method expects to receive an integer value that is greater than or equal to zero, and less than the size
   * of the underlying data model. If the index is valid, this method will return an ObservableValue for
   * this specific column.
   *
   * This is achieved by calling the cell value factory, and returning whatever it returns when passed a
   * CellDataFeatures (see, for example, the CellDataFeatures classes belonging to TableColumn and TreeTableColumn
   * for more information).
   *
   * @param index The index of the item (of type S) for which an ObservableValue is sought.
   * @return An ObservableValue for this specific table column.
   */
  def cellObservableValue(index: Int): ObservableValue[T, T] = delegate.getCellObservableValue(index)

  /**
   * Attempts to return an ObservableValue for the given item (which is of type S). In other words, this method
   * expects to receive an object from the underlying data model for the entire 'row' in the table, and it must
   * return an ObservableValue for the value in this specific column.
   *
   * This is achieved by calling the cell value factory, and returning whatever it returns when passed a
   * CellDataFeatures (see, for example, the CellDataFeatures classes belonging to TableColumn and TreeTableColumn
   * for more information).
   *
   * @param item The item (of type S) for which an ObservableValue is sought.
   * @return An ObservableValue for this specific table column.
   */
  def cellObservableValue(item: TreeItem[S]): ObservableValue[T, T] = delegate.getCellObservableValue(item)

  /**
   * The CssMetaData of this Styleable. This may be returned as an unmodifiable list.
   */
  override def cssMetaData: Seq[jfxcss.CssMetaData[? <: jfxcss.Styleable, ?]] = delegate.getCssMetaData.asScala.toSeq

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy