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

com.sun.javafx.scene.control.skin.TreeViewSkin Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.javafx.scene.control.skin;

import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeItem.TreeModificationEvent;
import javafx.scene.control.TreeView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;

import javafx.event.WeakEventHandler;
import com.sun.javafx.scene.control.behavior.TreeViewBehavior;
import java.lang.ref.WeakReference;

import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.WeakInvalidationListener;
import javafx.collections.ObservableMap;
import javafx.event.EventType;
import javafx.scene.control.*;
import javafx.util.Callback;

public class TreeViewSkin extends VirtualContainerBase, TreeViewBehavior, TreeCell> {

    public TreeViewSkin(final TreeView treeView) {
        super(treeView, new TreeViewBehavior(treeView));

        // init the VirtualFlow
        flow.setPannable(false);
        flow.setFocusTraversable(getSkinnable().isFocusTraversable());
        flow.setCreateCell(new Callback>() {
            @Override public TreeCell call(VirtualFlow flow) {
                return TreeViewSkin.this.createCell();
            }
        });
        flow.setFixedCellSize(treeView.getFixedCellSize());
        getChildren().add(flow);
        
        setRoot(getSkinnable().getRoot());
        
        EventHandler ml = new EventHandler() {
            @Override public void handle(MouseEvent event) { 
                // RT-15127: cancel editing on scroll. This is a bit extreme
                // (we are cancelling editing on touching the scrollbars).
                // This can be improved at a later date.
                if (treeView.getEditingItem() != null) {
                    treeView.edit(null);
                }
                
                // This ensures that the tree maintains the focus, even when the vbar
                // and hbar controls inside the flow are clicked. Without this, the
                // focus border will not be shown when the user interacts with the
                // scrollbars, and more importantly, keyboard navigation won't be
                // available to the user.
                treeView.requestFocus(); }
        };
        flow.getVbar().addEventFilter(MouseEvent.MOUSE_PRESSED, ml);
        flow.getHbar().addEventFilter(MouseEvent.MOUSE_PRESSED, ml);

        // init the behavior 'closures'
        getBehavior().setOnFocusPreviousRow(new Runnable() {
            @Override public void run() { onFocusPreviousCell(); }
        });
        getBehavior().setOnFocusNextRow(new Runnable() {
            @Override public void run() { onFocusNextCell(); }
        });
        getBehavior().setOnMoveToFirstCell(new Runnable() {
            @Override public void run() { onMoveToFirstCell(); }
        });
        getBehavior().setOnMoveToLastCell(new Runnable() {
            @Override public void run() { onMoveToLastCell(); }
        });
        getBehavior().setOnScrollPageDown(new Callback() {
            @Override public Integer call(Integer anchor) { return onScrollPageDown(anchor); }
        });
        getBehavior().setOnScrollPageUp(new Callback() {
            @Override public Integer call(Integer anchor) { return onScrollPageUp(anchor); }
        });
        getBehavior().setOnSelectPreviousRow(new Runnable() {
            @Override public void run() { onSelectPreviousCell(); }
        });
        getBehavior().setOnSelectNextRow(new Runnable() {
            @Override public void run() { onSelectNextCell(); }
        });

        registerChangeListener(treeView.rootProperty(), "ROOT");
        registerChangeListener(treeView.showRootProperty(), "SHOW_ROOT");
        registerChangeListener(treeView.cellFactoryProperty(), "CELL_FACTORY");
        registerChangeListener(treeView.focusTraversableProperty(), "FOCUS_TRAVERSABLE");
        registerChangeListener(treeView.fixedCellSizeProperty(), "FIXED_CELL_SIZE");
        
        updateRowCount();
    }
    
    @Override public void dispose() {
        getBehavior().dispose();
        super.dispose();
    }
    
    @Override protected void handleControlPropertyChanged(String p) {
        super.handleControlPropertyChanged(p);
        
        if ("ROOT".equals(p)) {
            setRoot(getSkinnable().getRoot());
        } else if ("SHOW_ROOT".equals(p)) {
            // if we turn off showing the root, then we must ensure the root
            // is expanded - otherwise we end up with no visible items in
            // the tree.
            if (! getSkinnable().isShowRoot() && getRoot() != null) {
                 getRoot().setExpanded(true);
            }
            // update the item count in the flow and behavior instances
            updateRowCount();
        } else if ("CELL_FACTORY".equals(p)) {
            flow.recreateCells();
        } else if ("FOCUS_TRAVERSABLE".equals(p)) {
            flow.setFocusTraversable(getSkinnable().isFocusTraversable());
        } else if ("FIXED_CELL_SIZE".equals(p)) {
            flow.setFixedCellSize(getSkinnable().getFixedCellSize());
        }
    }
    
//    private boolean needItemCountUpdate = false;
    private boolean needCellsRebuilt = true;
    private boolean needCellsReconfigured = false;
    
    private EventHandler rootListener = new EventHandler() {
        @Override public void handle(TreeModificationEvent e) {
            if (e.wasAdded() && e.wasRemoved() && e.getAddedSize() == e.getRemovedSize()) {
                // Fix for RT-14842, where the children of a TreeItem were changing,
                // but because the overall item count was staying the same, there was 
                // no event being fired to the skin to be informed that the items
                // had changed. So, here we just watch for the case where the number
                // of items being added is equal to the number of items being removed.
                rowCountDirty = true;
                getSkinnable().requestLayout();
            } else if (e.getEventType().equals(TreeItem.valueChangedEvent())) {
                // Fix for RT-14971 and RT-15338. 
                needCellsRebuilt = true;
                getSkinnable().requestLayout();
            } else {
                // Fix for RT-20090. We are checking to see if the event coming
                // from the TreeItem root is an event where the count has changed.
                EventType eventType = e.getEventType();
                while (eventType != null) {
                    if (eventType.equals(TreeItem.expandedItemCountChangeEvent())) {
                        rowCountDirty = true;
                        getSkinnable().requestLayout();
                        break;
                    }
                    eventType = eventType.getSuperType();
                }
            }
        }
    };
    
    private WeakEventHandler weakRootListener;
            
    
    private WeakReference weakRoot;
    private TreeItem getRoot() {
        return weakRoot == null ? null : weakRoot.get();
    }
    private void setRoot(TreeItem newRoot) {
        if (getRoot() != null && weakRootListener != null) {
            getRoot().removeEventHandler(TreeItem.treeNotificationEvent(), weakRootListener);
        }
        weakRoot = new WeakReference(newRoot);
        if (getRoot() != null) {
            weakRootListener = new WeakEventHandler(rootListener);
            getRoot().addEventHandler(TreeItem.treeNotificationEvent(), weakRootListener);
        }
        
        updateRowCount();
    }

    @Override public int getItemCount() {
        return getSkinnable().getExpandedItemCount();
    }

    @Override protected void updateRowCount() {
//        int oldCount = flow.getCellCount();
        int newCount = getItemCount();
        
        // if this is not called even when the count is the same, we get a 
        // memory leak in VirtualFlow.sheet.children. This can probably be 
        // optimised in the future when time permits.
        flow.setCellCount(newCount);

        // Ideally we would be more nuanced here, toggling a cheaper needs* 
        // field, but if we do we hit issues such as those identified in 
        // RT-27852, where the expended item count of the new root equals the
        // EIC of the old root, which would lead to the visuals not updating
        // properly. 
        needCellsRebuilt = true;
        getSkinnable().requestLayout();
    }

    @Override public TreeCell createCell() {
        final TreeCell cell;
        if (getSkinnable().getCellFactory() != null) {
            cell = getSkinnable().getCellFactory().call(getSkinnable());
        } else {
            cell = createDefaultCellImpl();
        }

        // If there is no disclosure node, then add one of my own
        if (cell.getDisclosureNode() == null) {
            final StackPane disclosureNode = new StackPane();
            disclosureNode.getStyleClass().setAll("tree-disclosure-node");

            final StackPane disclosureNodeArrow = new StackPane();
            disclosureNodeArrow.getStyleClass().setAll("arrow");
            disclosureNode.getChildren().add(disclosureNodeArrow);

            cell.setDisclosureNode(disclosureNode);
        }

        cell.updateTreeView(getSkinnable());

        return cell;
    }

    private TreeCell createDefaultCellImpl() {
        return new TreeCell() {
            private HBox hbox;
            
            private WeakReference> treeItemRef;
            
            private InvalidationListener treeItemGraphicListener = new InvalidationListener() {
                @Override public void invalidated(Observable observable) {
                    updateDisplay(getItem(), isEmpty());
                }
            };
            
            private InvalidationListener treeItemListener = new InvalidationListener() {
                @Override public void invalidated(Observable observable) {
                    TreeItem oldTreeItem = treeItemRef == null ? null : treeItemRef.get();
                    if (oldTreeItem != null) {
                        oldTreeItem.graphicProperty().removeListener(weakTreeItemGraphicListener);
                    }
                    
                    TreeItem newTreeItem = getTreeItem();
                    if (newTreeItem != null) {
                        newTreeItem.graphicProperty().addListener(weakTreeItemGraphicListener);
                        treeItemRef = new WeakReference>(newTreeItem);
                    }
                }
            };
            
            private WeakInvalidationListener weakTreeItemGraphicListener =
                    new WeakInvalidationListener(treeItemGraphicListener);
            
            private WeakInvalidationListener weakTreeItemListener =
                    new WeakInvalidationListener(treeItemListener);
            
            {
                treeItemProperty().addListener(weakTreeItemListener);
                
                if (getTreeItem() != null) {
                    getTreeItem().graphicProperty().addListener(weakTreeItemGraphicListener);
                }
            }
            
            private void updateDisplay(T item, boolean empty) {
                if (item == null || empty) {
                    hbox = null;
                    setText(null);
                    setGraphic(null);
                } else {
                    // update the graphic if one is set in the TreeItem
                    TreeItem treeItem = getTreeItem();
                    if (treeItem != null && treeItem.getGraphic() != null) {
                        if (item instanceof Node) {
                            setText(null);
                            
                            // the item is a Node, and the graphic exists, so 
                            // we must insert both into an HBox and present that
                            // to the user (see RT-15910)
                            if (hbox == null) {
                                hbox = new HBox(3);
                            }
                            hbox.getChildren().setAll(treeItem.getGraphic(), (Node)item);
                            setGraphic(hbox);
                        } else {
                            hbox = null;
                            setText(item.toString());
                            setGraphic(treeItem.getGraphic());
                        }
                    } else {
                        hbox = null;
                        if (item instanceof Node) {
                            setText(null);
                            setGraphic((Node)item);
                        } else {
                            setText(item.toString());
                            setGraphic(null);
                        }
                    }
                }                
            }
            
            @Override public void updateItem(T item, boolean empty) {
                super.updateItem(item, empty);
                updateDisplay(item, empty);
            }
        };
    }
    
    @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
        return computePrefHeight(-1, topInset, rightInset, bottomInset, leftInset) * 0.618033987;
    }

    @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
        return 400;
    }

    @Override
    protected void layoutChildren(final double x, final double y,
            final double w, final double h) {
        super.layoutChildren(x, y, w, h);
        
        if (needCellsRebuilt) {
            flow.rebuildCells();
        } else if (needCellsReconfigured) {
            flow.reconfigureCells();
        } 
        
        needCellsRebuilt = false;
        needCellsReconfigured = false;
        
        flow.resizeRelocate(x, y, w, h);
    }
    
    private void onFocusPreviousCell() {
        FocusModel fm = getSkinnable().getFocusModel();
        if (fm == null) return;
        flow.show(fm.getFocusedIndex());
    }

    private void onFocusNextCell() {
        FocusModel fm = getSkinnable().getFocusModel();
        if (fm == null) return;
        flow.show(fm.getFocusedIndex());
    }

    private void onSelectPreviousCell() {
        int row = getSkinnable().getSelectionModel().getSelectedIndex();
        flow.show(row);
    }

    private void onSelectNextCell() {
        int row = getSkinnable().getSelectionModel().getSelectedIndex();
        flow.show(row);
    }

    private void onMoveToFirstCell() {
        flow.show(0);
        flow.setPosition(0);
    }

    private void onMoveToLastCell() {
        flow.show(getItemCount());
        flow.setPosition(1);
    }

    /**
     * Function used to scroll the container down by one 'page', although
     * if this is a horizontal container, then the scrolling will be to the right.
     */
    public int onScrollPageDown(int anchor) {
        TreeCell lastVisibleCell = flow.getLastVisibleCellWithinViewPort();
        if (lastVisibleCell == null) return -1;

        int newSelectionIndex = -1;
        int lastVisibleCellIndex = lastVisibleCell.getIndex();
        if (! (lastVisibleCell.isSelected() || lastVisibleCell.isFocused()) || (lastVisibleCellIndex != anchor)) {
            // if the selection is not on the 'bottom' most cell, we firstly move
            // the selection down to that, without scrolling the contents
            newSelectionIndex = lastVisibleCell.getIndex();
        } else {
            // if the last visible cell is selected, we want to shift that cell up
            // to be the top-most cell, or at least as far to the top as we can go.
            flow.showAsFirst(lastVisibleCell);
            
            lastVisibleCell = flow.getLastVisibleCellWithinViewPort();
            newSelectionIndex = lastVisibleCell.getIndex();
        } 

        flow.show(lastVisibleCell);
        
        return newSelectionIndex;
    }

    /**
     * Function used to scroll the container up by one 'page', although
     * if this is a horizontal container, then the scrolling will be to the left.
     */
    public int onScrollPageUp(int anchor) {
        TreeCell firstVisibleCell = flow.getFirstVisibleCellWithinViewPort();
        if (firstVisibleCell == null) return -1;

        int newSelectionIndex = -1;
        int firstVisibleCellIndex = firstVisibleCell.getIndex();
        if (! (firstVisibleCell.isSelected() || firstVisibleCell.isFocused()) || (firstVisibleCellIndex != anchor)) {
            // if the selection is not on the 'top' most cell, we firstly move
            // the selection up to that, without scrolling the contents
            newSelectionIndex = firstVisibleCell.getIndex();
        } else {
            // if the first visible cell is selected, we want to shift that cell down
            // to be the bottom-most cell, or at least as far to the bottom as we can go.
            flow.showAsLast(firstVisibleCell);
            
            firstVisibleCell = flow.getFirstVisibleCellWithinViewPort();
            newSelectionIndex = firstVisibleCell.getIndex();
        } 

        flow.show(firstVisibleCell);
        
        return newSelectionIndex;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy