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

impl.org.controlsfx.spreadsheet.CellView Maven / Gradle / Ivy

Go to download

High quality UI controls and other tools to complement the core JavaFX distribution

There is a newer version: 11.2.1
Show newest version
/**
 * Copyright (c) 2013, 2019 ControlsFX
 * 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 ControlsFX, any associated website, 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 CONTROLSFX 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 impl.org.controlsfx.spreadsheet;

import java.util.Objects;
import java.util.Optional;
import java.util.logging.Logger;
import javafx.animation.FadeTransition;
import javafx.beans.binding.When;
import javafx.beans.value.WeakChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.SetChangeListener;
import javafx.collections.WeakSetChangeListener;
import javafx.event.EventHandler;
import javafx.event.WeakEventHandler;
import javafx.geometry.Side;
import javafx.scene.Cursor;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Control;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableCell;
import javafx.scene.control.TablePositionBase;
import javafx.scene.control.TableView;
import javafx.scene.control.TableView.TableViewFocusModel;
import javafx.scene.control.TableView.TableViewSelectionModel;
import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.util.Duration;
import org.controlsfx.control.spreadsheet.Filter;
import org.controlsfx.control.spreadsheet.SpreadsheetCell;
import org.controlsfx.control.spreadsheet.SpreadsheetCellEditor;
import org.controlsfx.control.spreadsheet.SpreadsheetCellType;
import org.controlsfx.control.spreadsheet.SpreadsheetView;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.layout.Region;
import org.controlsfx.control.spreadsheet.CellGraphicFactory;

/**
 *
 * The View cell that will be visible on screen. It holds the
 * {@link SpreadsheetCell}.
 */
public class CellView extends TableCell, SpreadsheetCell> {
    final SpreadsheetHandle handle;
    /**
     * Because we don't want to recreate Tooltip each time the TableCell is
     * re-used. We save it properly here so we avoid recreating it each time
     * since it's really time-consuming.
     */
    private Tooltip tooltip;
    //Handler for drag n drop in lazy instantiation.
    private EventHandler dragOverHandler;
    private EventHandler dragDropHandler;
    //Set to true when the style has changed, and the item has a cellgraphic.
    private boolean dirtyStyle = false;

    /***************************************************************************
     * * Static Fields * *
     **************************************************************************/
    private static final String ANCHOR_PROPERTY_KEY = "table.anchor"; //$NON-NLS-1$
    private static final int TOOLTIP_MAX_WIDTH = 400;
    private static final Duration FADE_DURATION = Duration.millis(200);

    static TablePositionBase getAnchor(Control table, TablePositionBase focusedCell) {
        return hasAnchor(table) ? (TablePositionBase) table.getProperties().get(ANCHOR_PROPERTY_KEY) : focusedCell;
    }

    static boolean hasAnchor(Control table) {
        return table.getProperties().get(ANCHOR_PROPERTY_KEY) != null;
    }

    static void setAnchor(Control table, TablePositionBase anchor) {
        if (table != null && anchor == null) {
            removeAnchor(table);
        } else {
            table.getProperties().put(ANCHOR_PROPERTY_KEY, anchor);
        }
    }

    static void removeAnchor(Control table) {
        table.getProperties().remove(ANCHOR_PROPERTY_KEY);
    }
    /***************************************************************************
     * * Constructor * *
     **************************************************************************/
    public CellView(SpreadsheetHandle handle) {
        this.handle = handle;
        // When we detect a drag, we start the Full Drag so that other event
        // will be fired
        this.addEventHandler(MouseEvent.DRAG_DETECTED, new WeakEventHandler<>(startFullDragEventHandler));
        setOnMouseDragEntered(new WeakEventHandler<>(dragMouseEventHandler));

        itemProperty().addListener(itemChangeListener);
    }

    /***************************************************************************
     * * Public Methods * *
     **************************************************************************/
    @Override
    public void startEdit() {
        /**
         * If this CellView has no parent, this means that it was stacked into
         * the cellsMap of the GridRowSkin, but the weakRef was dropped. So this
         * CellView is still reacting to events, but it's not part of the
         * sceneGraph! So we must deactivate this cell and let the real Cell in
         * the sceneGraph take the edition.
         *
         * This MUST be the first action because if this cell is not part of the
         * scene, we MUST NOT consider it at all.
         */
        if(getParent() == null){
            updateTableView(null);
            updateTableRow(null);
            updateTableColumn(null);
            return;
        }

        if (!isEditable()) {
            getTableView().edit(-1, null);
            return;
        }

        final int column = this.getTableView().getColumns().indexOf(this.getTableColumn());
        final int row = getIndex();
        // We start to edit only if the Cell is a normal Cell (aka visible).
        final SpreadsheetView spv = handle.getView();
        final SpreadsheetView.SpanType type = spv.getSpanType(row, column);
        //FIXME with the reverse algorithm in virtualFlow, is this still necessary?
        if (type == SpreadsheetView.SpanType.NORMAL_CELL || type == SpreadsheetView.SpanType.ROW_VISIBLE) {

            /**
             * We may come to the situation where this method is called two
             * times. One time by the row inside the VirtualFlow. And another by
             * the row inside myFixedCells used by our GridVirtualFlow.
             *
             * In that case, we have to give priority to the one used by the
             * VirtualFlow. So we just check if the row is managed. If not, we
             * know for sure that the our GridVirtualFlow has stepped out.
             */
            if (!getTableRow().isManaged()) {
                return;
            }

            GridCellEditor editor = getEditor(getItem(), spv);
            if (editor != null) {
                super.startEdit();
                setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
                editor.startEdit();
            }else{
                getTableView().edit(-1, null);
            }
        }
    }

    /**
     * Return the Filter associated to this cell or null otherwise.
     * @return
     */
    Filter getFilter() {
        Filter filter = getItem() != null  && getItem().getColumn() < handle.getView().getColumns().size() ? handle.getView().getColumns().get(getItem().getColumn()).getFilter() : null;
        //If we have a span, we check if the filteredRow is contained in the row span of this cell.
        if (filter != null && getItem().getRowSpan() > 1) {
            int rowSpan = handle.getView().getRowSpan(getItem(), getIndex());
            int row = getItem().getRow();
            for (int i = row; i < row + rowSpan; ++i) {
                if (handle.getView().getFilteredRow() == handle.getView().getModelRow(i)) {
                    return filter;
                }
            }
            //If we're here, nothing has been found.
            return null;
        }
        //If not we simply compare the filtered row.
        return filter != null && handle.getView().getFilteredRow() == handle.getView().getModelRow(getIndex()) ? filter : null;
    }

    @Override
    public void commitEdit(SpreadsheetCell newValue) {
        //When commiting, we bring the value smoothly.
        FadeTransition fadeTransition = new FadeTransition(FADE_DURATION, this);
        fadeTransition.setFromValue(0);
        fadeTransition.setToValue(1);
        fadeTransition.play();

        if (!isEditing()) {
            return;
        }
        super.commitEdit(newValue);

        setContentDisplay(ContentDisplay.LEFT);
        updateItem(newValue, false);

        if (getTableView() != null) {
            getTableView().requestFocus();
        }
    }

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

        super.cancelEdit();

        setContentDisplay(ContentDisplay.LEFT);
        updateItem(getItem(), false);

        //We release the editor if it has not been done.
        GridCellEditor editor = handle.getCellsViewSkin().getSpreadsheetCellEditorImpl();
        if (editor.isEditing()) {
            editor.endEdit(false);
        }
        if (getTableView() != null) {
            getTableView().requestFocus();
        }
    }

    @Override
    public void updateItem(final SpreadsheetCell item, boolean empty) {
        final boolean emptyRow = getTableView().getItems().size() < getIndex() + 1;
        /**
         * don't call super.updateItem() because it will trigger cancelEdit() if
         * the cell is being edited. It causes calling commitEdit() ALWAYS call
         * cancelEdit as well which is undesired.
         *
         */
        if (!isEditing()) {
            super.updateItem(item, empty && emptyRow);
        }
        if (empty && isSelected()) {
            updateSelected(false);
        }
        if (empty && emptyRow) {
            textProperty().unbind();
            setText(null);
            //Release any browser we might have
            releaseCellGraphic();
        } else if (!isEditing() && item != null) {
            show(item);
            if (item.getGraphic() == null && !item.isCellGraphic()) {
                setGraphic(null);
            }
        }
    }

    /**
     * Releases and remove the graphic if any has been defined. Useful when the
     * graphic comes from the {@link SpreadsheetView#getCellGraphicFactory() }
     * and explicitly needs to be given back.
     */
    public void releaseCellGraphic() {
        //Release any browser we might have
        CellGraphicFactory browserImpl = handle.getView().getCellGraphicFactory();
        if (browserImpl != null && getGraphic() != null && browserImpl.getType().isAssignableFrom(getGraphic().getClass())) {
            browserImpl.setUnusedNode(getGraphic());
            setGraphic(null);
            parentProperty().removeListener(parentListener);
        }
    }

    @Override
    protected void layoutChildren() {
        super.layoutChildren();
        //When layout is called, the cell style has been set on the cell, we can give it to the CellGraphicFactory
        if (dirtyStyle && getItem().isCellGraphic()) {
            CellGraphicFactory browserImpl = handle.getView().getCellGraphicFactory();
            if (browserImpl != null) {
                //Send the graphic only if it's the right type, otherwise send null.
                Node node = getGraphic() != null && browserImpl.getType().isAssignableFrom(getGraphic().getClass()) ? getGraphic() : null;
                browserImpl.loadStyle(node, getItem(), getFont(), getTextFill(), getAlignment(), getBackground());
                dirtyStyle = false;
            }
        }
    }

    /**
     * Called in the gridRowSkinBase when doing layout This allow not to
     * override opacity in the row and let the cell handle itself
     * @param cell
     */
    public void show(final SpreadsheetCell cell) {
        // We reset the settings
        textProperty().bind(cell.textProperty());
        setCellGraphic(cell);

        Optional tooltipText = cell.getTooltip();
        String trimTooltip = tooltipText.isPresent() ? tooltipText.get().trim() : null;

        if (trimTooltip != null && !trimTooltip.isEmpty()) {
            /**
             * Here we check if the Tooltip has not been created in order NOT TO
             * re-create it for nothing as it is a really time-consuming
             * operation.
             */
            Tooltip localTooltip = getAvailableTooltip();
            if (localTooltip != null) {
                if (!Objects.equals(localTooltip.getText(), trimTooltip)) {
                    getTooltip().setText(trimTooltip);
                }
            } else {
                /**
                 * Ensure that modification of ToolTip are set on the JFX thread
                 * because an exception can be thrown otherwise.
                 */
                getValue(() -> {
                    Tooltip newTooltip = new Tooltip(tooltipText.get());
                    newTooltip.setWrapText(true);
                    newTooltip.setMaxWidth(TOOLTIP_MAX_WIDTH);
                    setTooltip(newTooltip);
                }
                );
            }
        } else {
            //We save that tooltip
            if(getTooltip() != null){
                tooltip = getTooltip();
            }
            setTooltip(null);
        }

        setWrapText(cell.isWrapText());

        setEditable(cell.hasPopup() ? false : cell.isEditable());

        if (cell.hasPopup()) {
            setOnMouseClicked(weakActionhandler);
            setCursor(Cursor.HAND);
        } else {
            setOnMouseClicked(null);
            setCursor(Cursor.DEFAULT);
        }
        if (cell.getCellType().acceptDrop()) {
            setOnDragOver(getDragOverHandler());
            // Dropping over surface
            setOnDragDropped(getDragDropHandler());
        } else {
            setOnDragOver(null);
            setOnDragDropped(null);
        }
    }

    /***************************************************************************
     * * Private Methods * *
     **************************************************************************/

    /**
     * See if a tootlip is available (either on the TableCell already, or in the
     * Stack). And then set it to the TableCell.
     *
     * @return
     */
    private Tooltip getAvailableTooltip(){
        if(getTooltip() != null){
            return getTooltip();
        }
        if(tooltip != null){
            setTooltip(tooltip);
            return tooltip;
        }
        return null;
    }

    /**
     * Return true if this cell is the original cell (including filters) even
     * when scrolling.
     *
     * @return true if this cell is the original cell (including filters) even
     * when scrolling
     */
    public boolean isOriginalCell() {
        return handle.getView().getReverseRowSpan(getItem(), getIndex()) <= 1;
    }

    /**
     * Sets the browser in the cell.
     *
     * @param item
     */
    private void setBrowserGraphic(SpreadsheetCell item) {
        CellGraphicFactory browserImpl = handle.getView().getCellGraphicFactory();
        if (browserImpl == null) {
            return;
        }
        textProperty().unbind();
        setText(null);
        if (getGraphic() != null && browserImpl.getType().isAssignableFrom(getGraphic().getClass())) {
            browserImpl.load(getGraphic(), item);
        } else {
            //In any case, the WebView will need to update its style to match the cell.
            dirtyStyle = true;
            setGraphic(browserImpl.getNode(item));
            //If row or column is fixed, don't try to remove the graphic because constant layout can happen
            if (!handle.getView().getFixedRows().contains(getIndex()) && !handle.getView().getFixedColumns().contains(item.getColumn())) {
                parentProperty().addListener(parentListener);
            }
        }
    }

    /**
     * Set the cell graphic if any.
     *
     * If we have an Image, we try to make it take its full size (never more)
     * within the cell's space. If we have some row span, we want to compute the
     * image's size one time and then
     *
     * @param item the {@code SpreadsheetCell} to set the graphic to
     */
    private void setCellGraphic(SpreadsheetCell item) {
        if (isEditing()) {
            return;
        }
        if (item.isCellGraphic() && item.getItem() != null) {
            setBrowserGraphic(item);
            return;
        } else if (getGraphic() != null) {
            releaseCellGraphic();
        }
        Node graphic = item.getGraphic();
        if (graphic != null) {
            if (graphic instanceof ImageView) {
                ImageView image = (ImageView) graphic;
                image.setCache(true);
                image.setPreserveRatio(true);
                image.setSmooth(true);
                if(image.getImage() != null){
                    image.fitHeightProperty().bind(
                            new When(heightProperty().greaterThan(image.getImage().getHeight())).then(
                                    image.getImage().getHeight()).otherwise(heightProperty()));
                    image.fitWidthProperty().bind(
                            new When(widthProperty().greaterThan(image.getImage().getWidth())).then(
                                    image.getImage().getWidth()).otherwise(widthProperty()));
                }
                /**
                 * If we have a Region and no text, we force it to take full
                 * space. But we want to impact the minSize in order to let the
                 * prefSize to be computed if necessary.
                 */
            } else if (graphic instanceof Region && item.getItem() == null) {
                Region region = (Region) graphic;
                region.minHeightProperty().bind(heightProperty());
                region.minWidthProperty().bind(widthProperty());
            }
            setGraphic(graphic);
            /**
             * In case of a resize of the column, we have new cells that steal
             * the image from the original TableCell. So we check here if we are
             * not in that case so that the Graphic of the SpreadsheetCell will
             * always be on the latest tableView and therefore fully visible.
             */
            if (!getChildren().contains(graphic)) {
                getChildren().add(graphic);
            }
        } else {
            setGraphic(null);
        }
    }

    /**
     * Return an instance of Editor specific to the Cell type We are not using
     * the build-in editor-Cell because we cannot know in advance which editor
     * we will need. Furthermore, we want to control the behavior very closely
     * in regards of the spanned cell (invisible etc).
     *
     * @param cell
     *            The SpreadsheetCell
     * @param bc
     *            The SpreadsheetCell
     * @return
     */
    private GridCellEditor getEditor(final SpreadsheetCell cell, final SpreadsheetView spv) {
        SpreadsheetCellType cellType = cell.getCellType();
        Optional cellEditor = spv.getEditor(cellType);

        if (cellEditor.isPresent()) {
            GridCellEditor editor = handle.getCellsViewSkin().getSpreadsheetCellEditorImpl();
            /**
             * Sometimes, we end up here with the editor already editing. But
             * this case should not happen. If a cell is calling startEdit,
             * this means we want to edit the cell and the editor should not be
             * editing another cell. So we just cancel the edition and give the
             * editor to the cell because we may not be able to edit anything.
             */
            if (editor.isEditing()) {
                if (editor.getModelCell() != null) {
                    StringBuilder builder = new StringBuilder();
                    builder.append("The cell at row ").append(editor.getModelCell().getRow())
                            .append(" and column ").append(editor.getModelCell().getColumn())
                            .append(" was in edition and cell at row ").append(cell.getRow())
                            .append(" and column ").append(cell.getColumn())
                            .append(" requested edition. This situation should not happen as the previous cell should not be in edition.");
                    Logger.getLogger("root").warning(builder.toString());
                }

                editor.endEdit(false);
            }

            editor.updateSpreadsheetCell(this);
            editor.updateDataCell(cell);
            editor.updateSpreadsheetCellEditor(cellEditor.get());
            return editor;
        } else {
            return null;
        }
    }

    private EventHandler getDragOverHandler() {
        if (dragOverHandler == null) {
            dragOverHandler = new EventHandler() {

                @Override
                public void handle(DragEvent event) {
                    Dragboard db = event.getDragboard();
                    if (db.hasFiles()) {
                        event.acceptTransferModes(TransferMode.ANY);
                    } else {
                        event.consume();
                    }
                }
            };
        }
        return dragOverHandler;
    }

    private EventHandler getDragDropHandler() {
        if (dragDropHandler == null) {
            dragDropHandler = new EventHandler() {
                @Override
                public void handle(DragEvent event) {
                    Dragboard db = event.getDragboard();
                    boolean success = false;
                    if (db.hasFiles() && db.getFiles().size() == 1) {
                        if (getItem().getCellType().match(db.getFiles().get(0), getItem().getOptionsForEditor())) {
                            handle.getView().getGrid().setCellValue(getItem().getRow(), getItem().getColumn(),
                                    getItem().getCellType().convertValue(db.getFiles().get(0)));
                            success = true;
                        }
                    }
                    event.setDropCompleted(success);
                    event.consume();
                }
            };
        }
        return dragDropHandler;
    }

    private final ChangeListener graphicListener = new ChangeListener() {
        @Override
        public void changed(ObservableValue arg0, Node arg1, Node newGraphic) {
            setCellGraphic(getItem());
        }
    };

    private final WeakChangeListener weakGraphicListener = new WeakChangeListener<>(graphicListener);

    private final SetChangeListener styleClassListener = new SetChangeListener() {
        @Override
        public void onChanged(javafx.collections.SetChangeListener.Change arg0) {
            if (arg0.wasAdded()) {
                getStyleClass().add(arg0.getElementAdded());
            } else if (arg0.wasRemoved()) {
                getStyleClass().remove(arg0.getElementRemoved());
            }
            if (getItem() != null && getItem().isCellGraphic()) {
                dirtyStyle = true;
            }
        }
    };

    private final WeakSetChangeListener weakStyleClassListener = new WeakSetChangeListener<>(styleClassListener);

    //Listeners for the styles, not initialized by default in order not to impact performance
    private ChangeListener styleListener;
    private WeakChangeListener weakStyleListener;

    /**
     * Method that will select all the cells between the drag place and that
     * cell.
     *
     * @param e
     */
    private void dragSelect(MouseEvent e) {
        // If the mouse event is not contained within this tableCell, then
        // we don't want to react to it.
        if (!this.contains(e.getX(), e.getY())) {
            return;
        }
        final TableView> tableView = getTableView();
        if (tableView == null) {
            return;
        }

        final int count = tableView.getItems().size();
        if (getIndex() >= count) {
            return;
        }

        final TableViewSelectionModel> sm = tableView.getSelectionModel();
        if (sm == null) {
            return;
        }

        final int row = getIndex();
        final int column = tableView.getVisibleLeafIndex(getTableColumn());

        // For spanned Cells
        final SpreadsheetCell cell = (SpreadsheetCell) getItem();
        final int rowCell = getIndex() + handle.getView().getRowSpan(cell, getIndex()) - 1;
        final int columnCell = handle.getView().getViewColumn(cell.getColumn()) + handle.getView().getColumnSpan(cell) - 1;

        final TableViewFocusModel fm = tableView.getFocusModel();
        if (fm == null) {
            return;
        }

        final TablePositionBase focusedCell = fm.getFocusedCell();
        final MouseButton button = e.getButton();
        if (button == MouseButton.PRIMARY) {
            // we add all cells/rows between the current selection focus and
            // this cell/row (inclusive) to the current selection.
            final TablePositionBase anchor = getAnchor(tableView, focusedCell);

            /**
             * FIXME We need to clarify how we want to select the cells. If a
             * spanned cell is in the way, which minRow/maxRow will be taken?
             * Where the mouse is exactly? Or where the "motherCell" is? This
             * needs some thinking.
             */
            // and then determine all row and columns which must be selected
            int minRow = Math.min(anchor.getRow(), row);
            minRow = Math.min(minRow, rowCell);
            int maxRow = Math.max(anchor.getRow(), row);
            maxRow = Math.max(maxRow, rowCell);
            int minColumn = Math.min(anchor.getColumn(), column);
            minColumn = Math.min(minColumn, columnCell);
            int maxColumn = Math.max(anchor.getColumn(), column);
            maxColumn = Math.max(maxColumn, columnCell);

            // clear selection, but maintain the anchor
            if (!e.isShortcutDown())
                sm.clearSelection();
            if (minColumn != -1 && maxColumn != -1)
                sm.selectRange(minRow, tableView.getVisibleLeafColumn(minColumn), maxRow,
                        tableView.getVisibleLeafColumn(maxColumn));
            setAnchor(tableView, anchor);
        }

    }

    /**
     * Will safely execute the request on the JFX thread by checking whether we
     * are on the JFX thread or not.
     *
     * @param runnable
     */
    public static void getValue(final Runnable runnable) {
        if (Platform.isFxApplicationThread()) {
            runnable.run();
        } else {
            Platform.runLater(runnable);
        }
    }

    @Override
    protected javafx.scene.control.Skin createDefaultSkin() {
        return new CellViewSkin(this);
    };
    
    private final EventHandler startFullDragEventHandler = new EventHandler() {
        @Override
        public void handle(MouseEvent arg0) {
            if (handle.getGridView().getSelectionModel().getSelectionMode().equals(SelectionMode.MULTIPLE)) {
                setAnchor(getTableView(), getTableView().getFocusModel().getFocusedCell());
                startFullDrag();
            }
        }
    };

    private final EventHandler dragMouseEventHandler = new EventHandler() {
        @Override
        public void handle(MouseEvent arg0) {
            dragSelect(arg0);
        }
    };

    private final ChangeListener itemChangeListener = new ChangeListener() {

        @Override
        public void changed(ObservableValue arg0, SpreadsheetCell oldItem,
                SpreadsheetCell newItem) {
            if (oldItem != null) {
                oldItem.getStyleClass().removeListener(weakStyleClassListener);
                oldItem.graphicProperty().removeListener(weakGraphicListener);

                if (oldItem.styleProperty() != null) {
                    oldItem.styleProperty().removeListener(weakStyleListener);
                }
            }
            if (newItem != null) {
                getStyleClass().clear();
                getStyleClass().setAll(newItem.getStyleClass());

                newItem.getStyleClass().addListener(weakStyleClassListener);
                setCellGraphic(newItem);
                newItem.graphicProperty().addListener(weakGraphicListener);

                if (newItem.styleProperty() != null) {
                    initStyleListener();
                    newItem.styleProperty().addListener(weakStyleListener);
                    setStyle(newItem.getStyle());
                } else {
                    //We clear the previous style.
                    setStyle(null);
                }
                if (getItem() != null && getItem().isCellGraphic()) {
                    dirtyStyle = true;
                }
            }
        }
    };

    /**
     * Event Handler when the cell is simply clicked in order to display the
     * possible actions in MenuItem.
     */
    private final EventHandler actionEventHandler = new EventHandler() {
        @Override
        public void handle(MouseEvent event) {
            /**
             * If we have some items to show and also we don't have a current
             * filter on this cell showing. If it is, we must block this Popup,
             * otherwise we will have two contextMenu overlapping each others.
             */
            if (getItem() != null && getItem().hasPopup() && MouseButton.PRIMARY.equals(event.getButton())
                    && (getFilter() == null || !getFilter().getMenuButton().isShowing())) {
                ContextMenu menu = new ContextMenu();
                menu.getScene().getStylesheets().add(SpreadsheetView.class.getResource("spreadsheet.css").toExternalForm());
                menu.getStyleClass().add("popup-button");
                menu.getItems().setAll(getItem().getPopupItems());
                menu.show(CellView.this, Side.BOTTOM, 0, 0);
            }
        }
    };
    private final WeakEventHandler weakActionhandler = new WeakEventHandler(actionEventHandler);

    /**
     * When this cell looses its parent, we release the Graphic if we had one.
     */
    private ChangeListener parentListener = new ChangeListener() {
        @Override
        public void changed(ObservableValue observable, Parent oldValue, Parent newParent) {
            if (newParent == null && getGraphic() != null) {
                releaseCellGraphic();
            }
        }
    };
    
    private void initStyleListener(){
        if(styleListener == null){
            styleListener = (ObservableValue observable, String oldValue, String newValue) -> {
                styleProperty().set(newValue);
                if (getItem() != null && getItem().isCellGraphic()) {
                    dirtyStyle = true;
                }
            };
        }
        weakStyleListener = new WeakChangeListener<>(styleListener);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy