Esempio n. 1
0
/**
 * Tile for the dashboard modules pane. It displays details about the {@link Module} and by clicking
 * it the module will be opened in the Workarea.
 *
 * @author Andrea Vacondio
 */
class ModulesDashboardTile extends Region {

  private static final PseudoClass ARMED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("armed");

  private VBox topTile = new VBox(5);
  private Button button = new Button();
  private VBox toolButtons = new VBox(5);

  ModulesDashboardTile(Module module) {
    getStyleClass().addAll("dashboard-modules-tile");
    Label titleLabel = new Label(module.descriptor().getName());
    titleLabel.getStyleClass().add("dashboard-modules-tile-title");
    titleLabel.setGraphic(module.graphic());
    Label textLabel = new Label(module.descriptor().getDescription());
    textLabel.getStyleClass().add("dashboard-modules-tile-text");
    textLabel.setMinHeight(USE_PREF_SIZE);
    topTile.getChildren().addAll(titleLabel, textLabel);
    button.getStyleClass().add("dashboard-modules-invisible-button");
    button.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
    button.setOnAction(e -> eventStudio().broadcast(activeteModule(module.id())));
    armed.bind(button.armedProperty());
    VBox inner = new VBox(new StackPane(topTile, button));
    prefHeightProperty().bind(inner.heightProperty());
    setMaxHeight(USE_PREF_SIZE);
    setMinHeight(USE_PREF_SIZE);
    inner.getStyleClass().add("dashboard-modules-tile-inner");
    getChildren().add(inner);
    module
        .descriptor()
        .getSupportURL()
        .ifPresent(
            url -> {
              Button playButton = AwesomeDude.createIconButton(AwesomeIcon.YOUTUBE_PLAY, "");
              playButton.getStyleClass().add("pdfsam-toolbar-button");
              playButton.setOnAction(e -> eventStudio().broadcast(new OpenUrlRequest(url)));
              toolButtons.getChildren().add(playButton);
              toolButtons.getStyleClass().add("dashboard-modules-toolbar");
              inner.getChildren().add(toolButtons);
            });
  }

  /** Property telling if the region (acting as a button) is armed */
  private ReadOnlyBooleanWrapper armed =
      new ReadOnlyBooleanWrapper(false) {
        @Override
        protected void invalidated() {
          pseudoClassStateChanged(ARMED_PSEUDOCLASS_STATE, get());
        }
      };

  public final ReadOnlyBooleanProperty armedProperty() {
    return armed.getReadOnlyProperty();
  }

  public final boolean isArmed() {
    return armed.get();
  }
}
Esempio n. 2
0
 @Override
 protected void initializePresenter() {
   view.titleLabel.textProperty().bind(stage.titleProperty());
   view.closeButton.setOnMouseClicked(
       e ->
           Platform.runLater(
               () -> {
                 stage.close();
                 eventAdmin.postEvent(new ApplicationExitEvent());
               }));
   view.minimizeButton.setOnMouseClicked(e -> Platform.runLater(() -> stage.setIconified(true)));
   stage
       .focusedProperty()
       .addListener(
           (dummy, oldVal, newVal) ->
               view.rootContainer.pseudoClassStateChanged(
                   PseudoClass.getPseudoClass("window-focused"), newVal));
 }
Esempio n. 3
0
/**
 * Either a horizontal or vertical bar with increment and decrement buttons and a "thumb" with which
 * the user can interact. Typically not used alone but used for building up more complicated
 * controls such as the ScrollPane and ListView.
 *
 * <p>ScrollBar sets focusTraversable to false.
 *
 * <p>This example creates a vertical ScrollBar :
 *
 * <pre><code>
 * import javafx.scene.control.ScrollBar;
 *
 * ScrollBar s1 = new ScrollBar();
 * s1.setOrientation(Orientation.VERTICAL);
 * </code></pre>
 *
 * Implementation of ScrollBar According to JavaFX UI Control API Specification
 *
 * @since JavaFX 2.0
 */
public class ScrollBar extends Control {

  /**
   * ************************************************************************* * Constructors * *
   * ************************************************************************
   */

  /** Creates a new horizontal ScrollBar (ie getOrientation() == Orientation.HORIZONTAL). */
  public ScrollBar() {
    // TODO : we need to ensure we have a width and height
    setWidth(ScrollBarSkin.DEFAULT_WIDTH);
    setHeight(ScrollBarSkin.DEFAULT_LENGTH);
    getStyleClass().setAll(DEFAULT_STYLE_CLASS);
    // focusTraversable is styleable through css. Calling setFocusTraversable
    // makes it look to css like the user set the value and css will not
    // override. Initializing focusTraversable by calling applyStyle with null
    // for StyleOrigin ensures that css will be able to override the value.
    ((StyleableProperty) focusTraversableProperty()).applyStyle(null, Boolean.FALSE);

    // set pseudo-class state to horizontal
    pseudoClassStateChanged(HORIZONTAL_PSEUDOCLASS_STATE, true);
  }
  /**
   * ************************************************************************* * Properties * *
   * ************************************************************************
   */
  /**
   * The minimum value represented by this {@code ScrollBar}. This should be a value less than or
   * equal to {@link #maxProperty max}.
   */
  private DoubleProperty min;

  public final void setMin(double value) {
    minProperty().set(value);
  }

  public final double getMin() {
    return min == null ? 0 : min.get();
  }

  public final DoubleProperty minProperty() {
    if (min == null) {
      min = new SimpleDoubleProperty(this, "min");
    }
    return min;
  }
  /**
   * The maximum value represented by this {@code ScrollBar}. This should be a value greater than or
   * equal to {@link #minProperty min}.
   */
  private DoubleProperty max;

  public final void setMax(double value) {
    maxProperty().set(value);
  }

  public final double getMax() {
    return max == null ? 100 : max.get();
  }

  public final DoubleProperty maxProperty() {
    if (max == null) {
      max = new SimpleDoubleProperty(this, "max", 100);
    }
    return max;
  }
  /**
   * The current value represented by this {@code ScrollBar}. This value should be between {@link
   * #minProperty min} and {@link #maxProperty max}, inclusive.
   */
  private DoubleProperty value;

  public final void setValue(double value) {
    valueProperty().set(value);
  }

  public final double getValue() {
    return value == null ? 0 : value.get();
  }

  public final DoubleProperty valueProperty() {
    if (value == null) {
      value = new SimpleDoubleProperty(this, "value");
    }
    return value;
  }
  /**
   * The orientation of the {@code ScrollBar} can either be {@link
   * javafx.geometry.Orientation#HORIZONTAL HORIZONTAL} or {@link
   * javafx.geometry.Orientation#VERTICAL VERTICAL}.
   */
  private ObjectProperty<Orientation> orientation;

  public final void setOrientation(Orientation value) {
    orientationProperty().set(value);
  }

  public final Orientation getOrientation() {
    return orientation == null ? Orientation.HORIZONTAL : orientation.get();
  }

  public final ObjectProperty<Orientation> orientationProperty() {
    if (orientation == null) {
      orientation =
          new StyleableObjectProperty<Orientation>(Orientation.HORIZONTAL) {
            @Override
            protected void invalidated() {
              final boolean vertical = (get() == Orientation.VERTICAL);
              pseudoClassStateChanged(VERTICAL_PSEUDOCLASS_STATE, vertical);
              pseudoClassStateChanged(HORIZONTAL_PSEUDOCLASS_STATE, !vertical);
            }

            @Override
            public CssMetaData<ScrollBar, Orientation> getCssMetaData() {
              return StyleableProperties.ORIENTATION;
            }

            @Override
            public Object getBean() {
              return ScrollBar.this;
            }

            @Override
            public String getName() {
              return "orientation";
            }
          };
    }
    return orientation;
  }

  /**
   * The amount by which to adjust the ScrollBar when the {@link #increment() increment} or {@link
   * #decrement() decrement} methods are called.
   */
  private DoubleProperty unitIncrement;

  public final void setUnitIncrement(double value) {
    unitIncrementProperty().set(value);
  }

  public final double getUnitIncrement() {
    return unitIncrement == null ? 1 : unitIncrement.get();
  }

  public final DoubleProperty unitIncrementProperty() {
    if (unitIncrement == null) {
      unitIncrement =
          new StyleableDoubleProperty(1) {

            @Override
            public CssMetaData<ScrollBar, Number> getCssMetaData() {
              return StyleableProperties.UNIT_INCREMENT;
            }

            @Override
            public Object getBean() {
              return ScrollBar.this;
            }

            @Override
            public String getName() {
              return "unitIncrement";
            }
          };
    }
    return unitIncrement;
  }
  /** The amount by which to adjust the scrollbar if the track of the bar is clicked. */
  private DoubleProperty blockIncrement;

  public final void setBlockIncrement(double value) {
    blockIncrementProperty().set(value);
  }

  public final double getBlockIncrement() {
    return blockIncrement == null ? 10 : blockIncrement.get();
  }

  public final DoubleProperty blockIncrementProperty() {
    if (blockIncrement == null) {
      blockIncrement =
          new StyleableDoubleProperty(10) {

            @Override
            public CssMetaData<ScrollBar, Number> getCssMetaData() {
              return StyleableProperties.BLOCK_INCREMENT;
            }

            @Override
            public Object getBean() {
              return ScrollBar.this;
            }

            @Override
            public String getName() {
              return "blockIncrement";
            }
          };
    }
    return blockIncrement;
  }
  /**
   * Visible amount of the scrollbar's range, typically represented by the size of the scroll bar's
   * thumb.
   */
  private DoubleProperty visibleAmount;

  public final void setVisibleAmount(double value) {
    visibleAmountProperty().set(value);
  }

  public final double getVisibleAmount() {
    return visibleAmount == null ? 15 : visibleAmount.get();
  }

  public final DoubleProperty visibleAmountProperty() {
    if (visibleAmount == null) {
      visibleAmount = new SimpleDoubleProperty(this, "visibleAmount");
    }
    return visibleAmount;
  }

  /**
   * ************************************************************************* * Methods * *
   * ************************************************************************
   */

  /**
   * Adjusts the {@link #valueProperty() value} property by {@link #blockIncrementProperty()
   * blockIncrement}. The {@code position} is the fractional amount between the {@link #minProperty
   * min} and {@link #maxProperty max}. For example, it might be 50%. If {@code #minProperty min}
   * were 0 and {@code #maxProperty max} were 100 and {@link #valueProperty() value} were 25, then a
   * position of .5 would indicate that we should increment {@link #valueProperty() value} by {@code
   * blockIncrement}. If {@link #valueProperty() value} were 75, then a position of .5 would
   * indicate that we should decrement {@link #valueProperty() value} by {@link
   * #blockIncrementProperty blockIncrement}.
   *
   * @expert This function is intended to be used by experts, primarily by those implementing new
   *     Skins or Behaviors. It is not common for developers or designers to access this function
   *     directly.
   */
  public void adjustValue(double position) {
    // figure out the "value" associated with the specified position
    double posValue = ((getMax() - getMin()) * Utils.clamp(0, position, 1)) + getMin();
    double newValue;
    if (Double.compare(posValue, getValue()) != 0) {
      if (posValue > getValue()) {
        newValue = getValue() + getBlockIncrement();
      } else {
        newValue = getValue() - getBlockIncrement();
      }

      boolean incrementing = position > ((getValue() - getMin()) / (getMax() - getMin()));
      if (incrementing && newValue > posValue) newValue = posValue;
      if (!incrementing && newValue < posValue) newValue = posValue;
      setValue(Utils.clamp(getMin(), newValue, getMax()));
    }
  }

  /**
   * Increments the value of the {@code ScrollBar} by the {@link #unitIncrementProperty
   * unitIncrement}
   */
  public void increment() {
    setValue(Utils.clamp(getMin(), getValue() + getUnitIncrement(), getMax()));
  }

  /**
   * Decrements the value of the {@code ScrollBar} by the {@link #unitIncrementProperty
   * unitIncrement}
   */
  public void decrement() {
    setValue(Utils.clamp(getMin(), getValue() - getUnitIncrement(), getMax()));
  }

  /** {@inheritDoc} */
  @Override
  protected Skin<?> createDefaultSkin() {
    return new ScrollBarSkin(this);
  }

  /**
   * ************************************************************************* * Stylesheet Handling
   * * * ************************************************************************
   */

  /**
   * Initialize the style class to 'scroll-bar'.
   *
   * <p>This is the selector class from which CSS can be used to style this control.
   */
  private static final String DEFAULT_STYLE_CLASS = "scroll-bar";

  private static class StyleableProperties {
    private static final CssMetaData<ScrollBar, Orientation> ORIENTATION =
        new CssMetaData<ScrollBar, Orientation>(
            "-fx-orientation",
            new EnumConverter<Orientation>(Orientation.class),
            Orientation.HORIZONTAL) {

          @Override
          public Orientation getInitialValue(ScrollBar node) {
            // A vertical ScrollBar should remain vertical
            return node.getOrientation();
          }

          @Override
          public boolean isSettable(ScrollBar n) {
            return n.orientation == null || !n.orientation.isBound();
          }

          @Override
          public StyleableProperty<Orientation> getStyleableProperty(ScrollBar n) {
            return (StyleableProperty<Orientation>) n.orientationProperty();
          }
        };

    private static final CssMetaData<ScrollBar, Number> UNIT_INCREMENT =
        new CssMetaData<ScrollBar, Number>("-fx-unit-increment", SizeConverter.getInstance(), 1.0) {

          @Override
          public boolean isSettable(ScrollBar n) {
            return n.unitIncrement == null || !n.unitIncrement.isBound();
          }

          @Override
          public StyleableProperty<Number> getStyleableProperty(ScrollBar n) {
            return (StyleableProperty<Number>) n.unitIncrementProperty();
          }
        };

    private static final CssMetaData<ScrollBar, Number> BLOCK_INCREMENT =
        new CssMetaData<ScrollBar, Number>(
            "-fx-block-increment", SizeConverter.getInstance(), 10.0) {

          @Override
          public boolean isSettable(ScrollBar n) {
            return n.blockIncrement == null || !n.blockIncrement.isBound();
          }

          @Override
          public StyleableProperty<Number> getStyleableProperty(ScrollBar n) {
            return (StyleableProperty<Number>) n.blockIncrementProperty();
          }
        };

    private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;

    static {
      final List<CssMetaData<? extends Styleable, ?>> styleables =
          new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
      styleables.add(ORIENTATION);
      styleables.add(UNIT_INCREMENT);
      styleables.add(BLOCK_INCREMENT);
      STYLEABLES = Collections.unmodifiableList(styleables);
    }
  }

  /**
   * @return The CssMetaData associated with this class, which may include the CssMetaData of its
   *     super classes.
   * @since JavaFX 8.0
   */
  public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
    return StyleableProperties.STYLEABLES;
  }

  /**
   * {@inheritDoc}
   *
   * @since JavaFX 8.0
   */
  @Override
  public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
    return getClassCssMetaData();
  }

  /** Pseud-class indicating this is a vertical ScrollBar. */
  private static final PseudoClass VERTICAL_PSEUDOCLASS_STATE =
      PseudoClass.getPseudoClass("vertical");

  /** Pseudo-class indicating this is a horizontal ScrollBar. */
  private static final PseudoClass HORIZONTAL_PSEUDOCLASS_STATE =
      PseudoClass.getPseudoClass("horizontal");

  /**
   * Most Controls return true for focusTraversable, so Control overrides this method to return
   * true, but ScrollBar returns false for focusTraversable's initial value; hence the override of
   * the override. This method is called from CSS code to get the correct initial value.
   *
   * @treatAsPrivate implementation detail
   */
  @Deprecated
  @Override
  protected /*do not make final*/ Boolean impl_cssGetFocusTraversableInitialValue() {
    return Boolean.FALSE;
  }
}
/** @author Craig Cavanaugh */
public class DetailedDecimalTextFieldSkin extends ComboBoxPopupControl<BigDecimal> {

  private final DetailedDecimalTextField detailedDecimalTextField;

  private DecimalTextField textField;

  /** Reference is needed to prevent premature garbage collection */
  @SuppressWarnings("FieldCanBeLocal")
  private final ChangeListener<Boolean> detailedDecimalFocusChangeListener;

  /** Reference is needed to prevent premature garbage collection */
  @SuppressWarnings("FieldCanBeLocal")
  private ChangeListener<Boolean> focusChangeListener;

  public DetailedDecimalTextFieldSkin(final DetailedDecimalTextField detailedDecimalTextField) {
    super(detailedDecimalTextField, new DetailedDecimalTextFieldBehavior(detailedDecimalTextField));
    this.detailedDecimalTextField = detailedDecimalTextField;

    // editable input node
    textField = getInputNode();

    if (textField != null) {
      getChildren().add(textField);
    }

    // move focus in to the text field if the detailedDecimalTextField is editable
    detailedDecimalFocusChangeListener =
        (observable, oldValue, hasFocus) -> {
          if (detailedDecimalTextField.isEditable() && hasFocus) {
            Platform.runLater(textField::requestFocus);
          }
        };

    detailedDecimalTextField
        .focusedProperty()
        .addListener(new WeakChangeListener<>(detailedDecimalFocusChangeListener));
  }

  @Override
  protected Node getPopupContent() {
    return null;
  }

  @Override
  protected TextField getEditor() {
    return null;
  }

  @Override
  protected StringConverter<BigDecimal> getConverter() {
    return null;
  }

  @Override
  public Node getDisplayNode() {
    Node displayNode = getInputNode();
    updateDisplayNode();
    return displayNode;
  }

  private BigDecimal initialDecimalFieldValue = null;

  private DecimalTextField getInputNode() {
    if (textField != null) return textField;

    textField = detailedDecimalTextField.getEditor();
    textField.setFocusTraversable(true);
    textField.promptTextProperty().bindBidirectional(detailedDecimalTextField.promptTextProperty());

    initialDecimalFieldValue = textField.getDecimal();

    focusChangeListener =
        (observable, oldValue, hasFocus) -> {
          // RT-21454 starts here
          if (!hasFocus) {
            setTextFromTextFieldIntoComboBoxValue();
            pseudoClassStateChanged(CONTAINS_FOCUS_PSEUDO_CLASS_STATE, false);
          } else {
            pseudoClassStateChanged(CONTAINS_FOCUS_PSEUDO_CLASS_STATE, true);
          }
        };

    textField.focusedProperty().addListener(new WeakChangeListener<>(focusChangeListener));

    return textField;
  }

  @Override
  protected void updateDisplayNode() {
    final BigDecimal value = detailedDecimalTextField.getValue();

    if (initialDecimalFieldValue != null) {
      textField.setDecimal(initialDecimalFieldValue);
      initialDecimalFieldValue = null;
    } else {
      detailedDecimalTextField.setValue(value);
    }
  }

  private static final PseudoClass CONTAINS_FOCUS_PSEUDO_CLASS_STATE =
      PseudoClass.getPseudoClass("contains-focus");
}
Esempio n. 5
0
/**
 * Represents a single row/column intersection in a {@link TableView}. To represent this
 * intersection, a TableCell contains an {@link #indexProperty() index} property, as well as a
 * {@link #tableColumnProperty() tableColumn} property. In addition, a TableCell instance knows what
 * {@link TableRow} it exists in.
 *
 * <p><strong>A note about selection:</strong> A TableCell visually shows it is selected when two
 * conditions are met:
 *
 * <ol>
 *   <li>The {@link TableSelectionModel#isSelected(int, TableColumnBase)} method returns true for
 *       the row / column that this cell represents, and
 *   <li>The {@link javafx.scene.control.TableSelectionModel#cellSelectionEnabledProperty() cell
 *       selection mode} property is set to true (to represent that it is allowable to select
 *       individual cells (and not just rows of cells)).
 * </ol>
 *
 * @see TableView
 * @see TableColumn
 * @see Cell
 * @see IndexedCell
 * @see TableRow
 * @param <S> The type of the TableView generic type (i.e. S == TableView&lt;S&gt;). This should
 *     also match with the first generic type in TableColumn.
 * @param <T> The type of the item contained within the Cell.
 * @since JavaFX 2.0
 */
public class TableCell<S, T> extends IndexedCell<T> {

  /**
   * ************************************************************************* * Constructors * *
   * ************************************************************************
   */

  /** Constructs a default TableCell instance with a style class of 'table-cell' */
  public TableCell() {
    getStyleClass().addAll(DEFAULT_STYLE_CLASS);

    updateColumnIndex();
  }

  /**
   * ************************************************************************* * Private fields * *
   * ************************************************************************
   */

  // package for testing
  boolean lockItemOnEdit = false;

  /**
   * ************************************************************************* * Callbacks and
   * Events * * ************************************************************************
   */
  private boolean itemDirty = false;

  /*
   * This is the list observer we use to keep an eye on the SelectedCells
   * ObservableList in the table view. Because it is possible that the table can
   * be mutated, we create this observer here, and add/remove it from the
   * storeTableView method.
   */
  private ListChangeListener<TablePosition> selectedListener =
      new ListChangeListener<TablePosition>() {
        @Override
        public void onChanged(Change<? extends TablePosition> c) {
          updateSelection();
        }
      };

  // same as above, but for focus
  private final InvalidationListener focusedListener =
      new InvalidationListener() {
        @Override
        public void invalidated(Observable value) {
          updateFocus();
        }
      };

  // same as above, but for for changes to the properties on TableRow
  private final InvalidationListener tableRowUpdateObserver =
      new InvalidationListener() {
        @Override
        public void invalidated(Observable value) {
          itemDirty = true;
          requestLayout();
        }
      };

  private final InvalidationListener editingListener =
      new InvalidationListener() {
        @Override
        public void invalidated(Observable value) {
          updateEditing();
        }
      };

  private ListChangeListener<TableColumn<S, ?>> visibleLeafColumnsListener =
      new ListChangeListener<TableColumn<S, ?>>() {
        @Override
        public void onChanged(Change<? extends TableColumn<S, ?>> c) {
          updateColumnIndex();
        }
      };

  private ListChangeListener<String> columnStyleClassListener =
      new ListChangeListener<String>() {
        @Override
        public void onChanged(Change<? extends String> c) {
          while (c.next()) {
            if (c.wasRemoved()) {
              getStyleClass().removeAll(c.getRemoved());
            }

            if (c.wasAdded()) {
              getStyleClass().addAll(c.getAddedSubList());
            }
          }
        }
      };

  private final WeakListChangeListener<TablePosition> weakSelectedListener =
      new WeakListChangeListener<>(selectedListener);
  private final WeakInvalidationListener weakFocusedListener =
      new WeakInvalidationListener(focusedListener);
  private final WeakInvalidationListener weaktableRowUpdateObserver =
      new WeakInvalidationListener(tableRowUpdateObserver);
  private final WeakInvalidationListener weakEditingListener =
      new WeakInvalidationListener(editingListener);
  private final WeakListChangeListener<TableColumn<S, ?>> weakVisibleLeafColumnsListener =
      new WeakListChangeListener<>(visibleLeafColumnsListener);
  private final WeakListChangeListener<String> weakColumnStyleClassListener =
      new WeakListChangeListener<String>(columnStyleClassListener);

  /**
   * ************************************************************************* * Properties * *
   * ************************************************************************
   */

  // --- TableColumn
  private ReadOnlyObjectWrapper<TableColumn<S, T>> tableColumn =
      new ReadOnlyObjectWrapper<TableColumn<S, T>>() {
        @Override
        protected void invalidated() {
          updateColumnIndex();
        }

        @Override
        public Object getBean() {
          return TableCell.this;
        }

        @Override
        public String getName() {
          return "tableColumn";
        }
      };
  /** The TableColumn instance that backs this TableCell. */
  public final ReadOnlyObjectProperty<TableColumn<S, T>> tableColumnProperty() {
    return tableColumn.getReadOnlyProperty();
  }

  private void setTableColumn(TableColumn<S, T> value) {
    tableColumn.set(value);
  }

  public final TableColumn<S, T> getTableColumn() {
    return tableColumn.get();
  }

  // --- TableView
  private ReadOnlyObjectWrapper<TableView<S>> tableView;

  private void setTableView(TableView<S> value) {
    tableViewPropertyImpl().set(value);
  }

  public final TableView<S> getTableView() {
    return tableView == null ? null : tableView.get();
  }

  /** The TableView associated with this TableCell. */
  public final ReadOnlyObjectProperty<TableView<S>> tableViewProperty() {
    return tableViewPropertyImpl().getReadOnlyProperty();
  }

  private ReadOnlyObjectWrapper<TableView<S>> tableViewPropertyImpl() {
    if (tableView == null) {
      tableView =
          new ReadOnlyObjectWrapper<TableView<S>>() {
            private WeakReference<TableView<S>> weakTableViewRef;

            @Override
            protected void invalidated() {
              TableView.TableViewSelectionModel<S> sm;
              TableViewFocusModel<S> fm;

              if (weakTableViewRef != null) {
                cleanUpTableViewListeners(weakTableViewRef.get());
              }

              if (get() != null) {
                sm = get().getSelectionModel();
                if (sm != null) {
                  sm.getSelectedCells().addListener(weakSelectedListener);
                }

                fm = get().getFocusModel();
                if (fm != null) {
                  fm.focusedCellProperty().addListener(weakFocusedListener);
                }

                get().editingCellProperty().addListener(weakEditingListener);
                get().getVisibleLeafColumns().addListener(weakVisibleLeafColumnsListener);

                weakTableViewRef = new WeakReference<TableView<S>>(get());
              }

              updateColumnIndex();
            }

            @Override
            public Object getBean() {
              return TableCell.this;
            }

            @Override
            public String getName() {
              return "tableView";
            }
          };
    }
    return tableView;
  }

  // --- TableRow
  /** The TableRow that this TableCell currently finds itself placed within. */
  private ReadOnlyObjectWrapper<TableRow> tableRow =
      new ReadOnlyObjectWrapper<TableRow>(this, "tableRow");

  private void setTableRow(TableRow value) {
    tableRow.set(value);
  }

  public final TableRow getTableRow() {
    return tableRow.get();
  }

  public final ReadOnlyObjectProperty<TableRow> tableRowProperty() {
    return tableRow;
  }

  /**
   * ************************************************************************* * Editing API * *
   * ************************************************************************
   */

  /** {@inheritDoc} */
  @Override
  public void startEdit() {
    final TableView table = getTableView();
    final TableColumn column = getTableColumn();
    if (!isEditable()
        || (table != null && !table.isEditable())
        || (column != null && !getTableColumn().isEditable())) {
      return;
    }

    // We check the boolean lockItemOnEdit field here, as whilst we want to
    // updateItem normally, when it comes to unit tests we can't have the
    // item change in all circumstances.
    if (!lockItemOnEdit) {
      updateItem();
    }

    // it makes sense to get the cell into its editing state before firing
    // the event to listeners below, so that's what we're doing here
    // by calling super.startEdit().
    super.startEdit();

    if (column != null) {
      CellEditEvent editEvent =
          new CellEditEvent(table, table.getEditingCell(), TableColumn.editStartEvent(), null);

      Event.fireEvent(column, editEvent);
    }
  }

  /** {@inheritDoc} */
  @Override
  public void commitEdit(T newValue) {
    if (!isEditing()) return;

    final TableView table = getTableView();
    if (table != null) {
      // Inform the TableView of the edit being ready to be committed.
      CellEditEvent editEvent =
          new CellEditEvent(table, table.getEditingCell(), TableColumn.editCommitEvent(), newValue);

      Event.fireEvent(getTableColumn(), editEvent);
    }

    // inform parent classes of the commit, so that they can switch us
    // out of the editing state.
    // This MUST come before the updateItem call below, otherwise it will
    // call cancelEdit(), resulting in both commit and cancel events being
    // fired (as identified in RT-29650)
    super.commitEdit(newValue);

    // update the item within this cell, so that it represents the new value
    updateItem(newValue, false);

    if (table != null) {
      // reset the editing cell on the TableView
      table.edit(-1, null);

      // request focus back onto the table, only if the current focus
      // owner has the table as a parent (otherwise the user might have
      // clicked out of the table entirely and given focus to something else.
      // It would be rude of us to request it back again.
      ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(table);
    }
  }

  /** {@inheritDoc} */
  @Override
  public void cancelEdit() {
    if (!isEditing()) return;

    final TableView table = getTableView();

    super.cancelEdit();

    // reset the editing index on the TableView
    if (table != null) {
      TablePosition editingCell = table.getEditingCell();
      if (updateEditingIndex) table.edit(-1, null);

      // request focus back onto the table, only if the current focus
      // owner has the table as a parent (otherwise the user might have
      // clicked out of the table entirely and given focus to something else.
      // It would be rude of us to request it back again.
      ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(table);

      CellEditEvent editEvent =
          new CellEditEvent(table, editingCell, TableColumn.editCancelEvent(), null);

      Event.fireEvent(getTableColumn(), editEvent);
    }
  }

  /* *************************************************************************
   *                                                                         *
   * Overriding methods                                                      *
   *                                                                         *
   **************************************************************************/

  /** {@inheritDoc} */
  @Override
  public void updateSelected(boolean selected) {
    // copied from Cell, with the first conditional clause below commented
    // out, as it is valid for an empty TableCell to be selected, as long
    // as the parent TableRow is not empty (see RT-15529).
    /*if (selected && isEmpty()) return;*/
    if (getTableRow() == null || getTableRow().isEmpty()) return;
    setSelected(selected);
  }

  /** {@inheritDoc} */
  @Override
  protected Skin<?> createDefaultSkin() {
    return new TableCellSkin(this);
  }

  //    @Override public void dispose() {
  //        cleanUpTableViewListeners(getTableView());
  //
  //        if (currentObservableValue != null) {
  //            currentObservableValue.removeListener(weaktableRowUpdateObserver);
  //        }
  //
  //        super.dispose();
  //    }

  /* *************************************************************************
   *                                                                         *
   * Private Implementation                                                  *
   *                                                                         *
   **************************************************************************/

  private void cleanUpTableViewListeners(TableView<S> tableView) {
    if (tableView != null) {
      TableView.TableViewSelectionModel sm = tableView.getSelectionModel();
      if (sm != null) {
        sm.getSelectedCells().removeListener(weakSelectedListener);
      }

      TableViewFocusModel fm = tableView.getFocusModel();
      if (fm != null) {
        fm.focusedCellProperty().removeListener(weakFocusedListener);
      }

      tableView.editingCellProperty().removeListener(weakEditingListener);
      tableView.getVisibleLeafColumns().removeListener(weakVisibleLeafColumnsListener);
    }
  }

  @Override
  void indexChanged() {
    super.indexChanged();
    // Ideally we would just use the following two lines of code, rather
    // than the updateItem() call beneath, but if we do this we end up with
    // RT-22428 where all the columns are collapsed.
    // itemDirty = true;
    // requestLayout();
    updateItem();
    updateSelection();
    updateFocus();
  }

  private boolean isLastVisibleColumn = false;
  private int columnIndex = -1;

  private void updateColumnIndex() {
    TableView tv = getTableView();
    TableColumn tc = getTableColumn();
    columnIndex = tv == null || tc == null ? -1 : tv.getVisibleLeafIndex(tc);

    // update the pseudo class state regarding whether this is the last
    // visible cell (i.e. the right-most).
    isLastVisibleColumn =
        getTableColumn() != null
            && columnIndex != -1
            && columnIndex == getTableView().getVisibleLeafColumns().size() - 1;
    pseudoClassStateChanged(PSEUDO_CLASS_LAST_VISIBLE, isLastVisibleColumn);
  }

  private void updateSelection() {
    /*
     * This cell should be selected if the selection mode of the table
     * is cell-based, and if the row and column that this cell represents
     * is selected.
     *
     * If the selection mode is not cell-based, then the listener in the
     * TableRow class might pick up the need to set an entire row to be
     * selected.
     */
    if (isEmpty()) return;

    final boolean isSelected = isSelected();
    if (!isInCellSelectionMode()) {
      if (isSelected) {
        updateSelected(false);
      }
      return;
    }

    final TableView<S> tableView = getTableView();
    if (getIndex() == -1 || tableView == null) return;

    TableSelectionModel<S> sm = tableView.getSelectionModel();
    if (sm == null) return;

    boolean isSelectedNow = sm.isSelected(getIndex(), getTableColumn());
    if (isSelected == isSelectedNow) return;

    updateSelected(isSelectedNow);
  }

  private void updateFocus() {
    final boolean isFocused = isFocused();
    if (!isInCellSelectionMode()) {
      if (isFocused) {
        setFocused(false);
      }
      return;
    }

    final TableView<S> tableView = getTableView();
    if (getIndex() == -1 || tableView == null) return;

    final TableViewFocusModel<S> fm = tableView.getFocusModel();
    if (fm == null) return;

    boolean isFocusedNow = fm != null && fm.isFocused(getIndex(), getTableColumn());

    setFocused(isFocusedNow);
  }

  private void updateEditing() {
    if (getIndex() == -1 || getTableView() == null) return;

    TablePosition editCell = getTableView().getEditingCell();
    boolean match = match(editCell);

    if (match && !isEditing()) {
      startEdit();
    } else if (!match && isEditing()) {
      // If my index is not the one being edited then I need to cancel
      // the edit. The tricky thing here is that as part of this call
      // I cannot end up calling list.edit(-1) the way that the standard
      // cancelEdit method would do. Yet, I need to call cancelEdit
      // so that subclasses which override cancelEdit can execute. So,
      // I have to use a kind of hacky flag workaround.
      updateEditingIndex = false;
      cancelEdit();
      updateEditingIndex = true;
    }
  }

  private boolean updateEditingIndex = true;

  private boolean match(TablePosition pos) {
    return pos != null && pos.getRow() == getIndex() && pos.getTableColumn() == getTableColumn();
  }

  private boolean isInCellSelectionMode() {
    TableView<S> tableView = getTableView();
    if (tableView == null) return false;
    TableSelectionModel<S> sm = tableView.getSelectionModel();
    return sm != null && sm.isCellSelectionEnabled();
  }

  /*
   * This was brought in to fix the issue in RT-22077, namely that the
   * ObservableValue was being GC'd, meaning that changes to the value were
   * no longer being delivered. By extracting this value out of the method,
   * it is now referred to from TableCell and will therefore no longer be
   * GC'd.
   */
  private ObservableValue<T> currentObservableValue = null;

  private boolean isFirstRun = true;

  /*
   * This is called when we think that the data within this TableCell may have
   * changed. You'll note that this is a private function - it is only called
   * when one of the triggers above call it.
   */
  private void updateItem() {
    if (currentObservableValue != null) {
      currentObservableValue.removeListener(weaktableRowUpdateObserver);
    }

    // get the total number of items in the data model
    final TableView tableView = getTableView();
    final List<T> items =
        tableView == null ? FXCollections.<T>emptyObservableList() : tableView.getItems();
    final TableColumn tableColumn = getTableColumn();
    final int itemCount = items == null ? -1 : items.size();
    final int index = getIndex();
    final boolean isEmpty = isEmpty();
    final T oldValue = getItem();

    final boolean indexExceedsItemCount = index >= itemCount;

    // there is a whole heap of reasons why we should just punt...
    if (indexExceedsItemCount
        || index < 0
        || columnIndex < 0
        || !isVisible()
        || tableColumn == null
        || !tableColumn.isVisible()) {

      // RT-30484 We need to allow a first run to be special-cased to allow
      // for the updateItem method to be called at least once to allow for
      // the correct visual state to be set up. In particular, in RT-30484
      // refer to Ensemble8PopUpTree.png - in this case the arrows are being
      // shown as the new cells are instantiated with the arrows in the
      // children list, and are only hidden in updateItem.
      // RT-32621: There are circumstances where we need to updateItem,
      // even when the index is greater than the itemCount. For example,
      // RT-32621 identifies issues where a TreeTableView collapses a
      // TreeItem but the custom cells remain visible. This is now
      // resolved with the check for indexExceedsItemCount.
      if ((!isEmpty && oldValue != null) || isFirstRun || indexExceedsItemCount) {
        updateItem(null, true);
        isFirstRun = false;
      }
      return;
    } else {
      currentObservableValue = tableColumn.getCellObservableValue(index);
      final T newValue = currentObservableValue == null ? null : currentObservableValue.getValue();

      // There used to be conditional code here to prevent updateItem from
      // being called when the value didn't change, but that led us to
      // issues such as RT-33108, where the value didn't change but the item
      // we needed to be listening to did. Without calling updateItem we
      // were breaking things, so once again the conditionals are gone.
      updateItem(newValue, false);
    }

    if (currentObservableValue == null) {
      return;
    }

    // add property change listeners to this item
    currentObservableValue.addListener(weaktableRowUpdateObserver);
  }

  @Override
  protected void layoutChildren() {
    if (itemDirty) {
      updateItem();
      itemDirty = false;
    }
    super.layoutChildren();
  }

  /**
   * ************************************************************************* * Expert API * *
   * ************************************************************************
   */

  /**
   * Updates the TableView associated with this TableCell. This is typically only done once when the
   * TableCell is first added to the TableView.
   *
   * @expert This function is intended to be used by experts, primarily by those implementing new
   *     Skins. It is not common for developers or designers to access this function directly.
   */
  public final void updateTableView(TableView tv) {
    setTableView(tv);
  }

  /**
   * Updates the TableRow associated with this TableCell.
   *
   * @expert This function is intended to be used by experts, primarily by those implementing new
   *     Skins. It is not common for developers or designers to access this function directly.
   */
  public final void updateTableRow(TableRow tableRow) {
    this.setTableRow(tableRow);
  }

  /**
   * Updates the TableColumn associated with this TableCell.
   *
   * @expert This function is intended to be used by experts, primarily by those implementing new
   *     Skins. It is not common for developers or designers to access this function directly.
   */
  public final void updateTableColumn(TableColumn col) {
    // remove style class of existing table column, if it is non-null
    TableColumn<S, T> oldCol = getTableColumn();
    if (oldCol != null) {
      oldCol.getStyleClass().removeListener(weakColumnStyleClassListener);
      getStyleClass().removeAll(oldCol.getStyleClass());
    }

    setTableColumn(col);

    if (col != null) {
      getStyleClass().addAll(col.getStyleClass());
      col.getStyleClass().addListener(weakColumnStyleClassListener);
    }
  }

  /**
   * ************************************************************************* * Stylesheet Handling
   * * * ************************************************************************
   */
  private static final String DEFAULT_STYLE_CLASS = "table-cell";

  private static final PseudoClass PSEUDO_CLASS_LAST_VISIBLE =
      PseudoClass.getPseudoClass("last-visible");
}