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

com.alee.laf.tree.WebTreeUI Maven / Gradle / Ivy

There is a newer version: 1.2.14
Show newest version
/*
 * This file is part of WebLookAndFeel library.
 *
 * WebLookAndFeel library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * WebLookAndFeel library 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with WebLookAndFeel library.  If not, see .
 */

package com.alee.laf.tree;

import com.alee.api.annotations.NotNull;
import com.alee.api.annotations.Nullable;
import com.alee.extended.tree.WebCheckBoxTree;
import com.alee.laf.WebLookAndFeel;
import com.alee.laf.tree.behavior.TreePathHoverBehavior;
import com.alee.managers.icon.Icons;
import com.alee.managers.style.StyleManager;
import com.alee.painter.Painter;
import com.alee.painter.PainterSupport;

import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.Enumeration;

/**
 * Custom UI for {@link JTree} component.
 *
 * @author Mikle Garin
 */
public class WebTreeUI extends WTreeUI
{
    /**
     * Style settings.
     */
    protected TreeSelectionStyle selectionStyle;

    /**
     * Listeners.
     */
    protected transient TreePathHoverBehavior hoverNodeTracker;

    /**
     * Runtime variables.
     */
    protected transient int hoverRow = -1;

    /**
     * Returns an instance of the {@link WebTreeUI} for the specified component.
     * This tricky method is used by {@link UIManager} to create component UIs when needed.
     *
     * @param c component that will use UI instance
     * @return instance of the {@link WebTreeUI}
     */
    @NotNull
    public static ComponentUI createUI ( @NotNull final JComponent c )
    {
        return new WebTreeUI ();
    }

    @Override
    public void installUI ( @NotNull final JComponent c )
    {
        // Installing UI
        super.installUI ( c );

        // todo Probably completely remove this?
        // Overwrite indent in case WebLookAndFeel is not installed as LaF
        if ( !WebLookAndFeel.isInstalled () )
        {
            setRightChildIndent ( 12 );
            setLeftChildIndent ( 12 );
        }

        // Allow each cell to choose its own preferred height
        tree.setRowHeight ( -1 );

        // Modifying default drop mode
        // USE_SELECTION mode is not preferred since WebLaF provides a better visual drop representation
        tree.setDropMode ( DropMode.ON );

        // Use a moderate amount of visible rows by default
        // BasicTreeUI uses 20 rows by default which is too much for most of cases
        tree.setVisibleRowCount ( 10 );

        // Forces tree to save changes when another tree node is selected instead of cancelling them
        tree.setInvokesStopCellEditing ( true );

        // Hover behavior
        hoverNodeTracker = new TreePathHoverBehavior ( tree, true )
        {
            @Override
            public void hoverChanged ( @Nullable final TreePath previous, @Nullable final TreePath current )
            {
                // Updating hover row
                final int previousRow = hoverRow;
                hoverRow = current != null ? tree.getRowForPath ( current ) : -1;

                // Repainting nodes according to hover changes
                // This occurs only if hover highlight is enabled
                final Painter painter = PainterSupport.getPainter ( tree );
                if ( painter instanceof ITreePainter && ( ( ITreePainter ) painter ).isRowHoverDecorationSupported () )
                {
                    repaintRow ( previousRow );
                    repaintRow ( hoverRow );
                }

                // Updating custom WebLaF tooltip display state
                final TreeToolTipProvider provider = getToolTipProvider ();
                if ( provider != null )
                {
                    provider.hoverAreaChanged (
                            tree,
                            previousRow != -1 ? new TreeCellArea ( previousRow ) : null,
                            hoverRow != -1 ? new TreeCellArea ( hoverRow ) : null
                    );
                }

                // Informing {@link com.alee.laf.tree.WebTree} about hover node change
                // This is performed here to avoid excessive listeners usage for the same purpose
                if ( tree instanceof WebTree )
                {
                    final MutableTreeNode p = previous != null ? ( MutableTreeNode ) previous.getLastPathComponent () : null;
                    final MutableTreeNode c = current != null ? ( MutableTreeNode ) current.getLastPathComponent () : null;
                    ( ( WebTree ) tree ).fireHoverChanged ( p, c );
                }
            }

            /**
             * Repaints specified row if it exists and it is visible.
             *
             * @param row row to repaint
             */
            private void repaintRow ( final int row )
            {
                if ( 0 <= row && row < tree.getRowCount () )
                {
                    tree.repaint ( getRowBounds ( row, true ) );
                }
            }
        };
        hoverNodeTracker.install ();

        // Applying skin
        StyleManager.installSkin ( tree );
    }

    @Override
    public void uninstallUI ( @NotNull final JComponent c )
    {
        // Uninstalling applied skin
        StyleManager.uninstallSkin ( tree );

        // Removing custom listeners
        hoverNodeTracker.uninstall ();
        hoverNodeTracker = null;

        // Uninstalling UI
        super.uninstallUI ( c );
    }

    @Override
    public int getHoverRow ()
    {
        return hoverRow;
    }

    @Override
    public int getExactRowForLocation ( @NotNull final Point location )
    {
        return getExactRowForLocation ( location, isFullLineSelection () );
    }

    @Override
    public int getExactRowForLocation ( @NotNull final Point location, final boolean fullRow )
    {
        int row = -1;
        if ( tree != null )
        {
            final Enumeration visiblePaths = getVisiblePaths ();
            if ( visiblePaths != null )
            {
                while ( visiblePaths.hasMoreElements () )
                {
                    final TreePath treePath = visiblePaths.nextElement ();
                    final Rectangle bounds = getPathBounds ( treePath, fullRow );
                    if ( bounds != null && bounds.contains ( location ) )
                    {
                        row = getRowForPath ( tree, treePath );
                        break;
                    }
                }
            }
        }
        return row;
    }

    @NotNull
    @Override
    public Rectangle getRowBounds ( final int row )
    {
        return getRowBounds ( row, isFullLineSelection () );
    }

    @NotNull
    @Override
    public Rectangle getRowBounds ( final int row, final boolean fullRow )
    {
        final TreePath path = getPathForRow ( tree, row );
        final Rectangle rowBounds = fullRow ? getFullPathBounds ( path ) : getPathBounds ( tree, path );
        if ( rowBounds == null )
        {
            throw new RuntimeException ( "Unable to retrieve row bounds: " + row );
        }
        return rowBounds;
    }

    /**
     * Returns full path bounds.
     *
     * @param path    tree path
     * @param fullRow whether take the whole row into account or just node renderer rect
     * @return full path bounds
     */
    @Nullable
    public Rectangle getPathBounds ( @Nullable final TreePath path, final boolean fullRow )
    {
        return fullRow ? getFullPathBounds ( path ) : getPathBounds ( tree, path );
    }

    /**
     * Returns full path bounds.
     *
     * @param path tree path
     * @return full path bounds
     */
    @Nullable
    private Rectangle getFullPathBounds ( @Nullable final TreePath path )
    {
        final Rectangle b = getPathBounds ( tree, path );
        if ( b != null )
        {
            final Insets insets = tree.getInsets ();
            b.x = insets.left;
            b.width = tree.getWidth () - insets.left - insets.right;
        }
        return b;
    }

    /**
     * Returns visible paths enumeration.
     * This is just a small method for convenient enumeration retrieval.
     *
     * @return visible paths enumeration
     */
    @Nullable
    public Enumeration getVisiblePaths ()
    {
        final Enumeration result;
        if ( tree.isShowing () )
        {
            final Rectangle paintBounds = tree.getVisibleRect ();
            final TreePath initialPath = getClosestPathForLocation ( tree, 0, paintBounds.y );
            if ( initialPath != null )
            {
                result = treeState.getVisiblePathsFrom ( initialPath );
            }
            else
            {
                result = null;
            }
        }
        else
        {
            result = null;
        }
        return result;
    }

    @NotNull
    @Override
    protected TreeCellEditor createDefaultCellEditor ()
    {
        return new WebTreeCellEditor ();
    }

    @NotNull
    @Override
    protected TreeCellRenderer createDefaultCellRenderer ()
    {
        return new WebTreeCellRenderer.UIResource> ();
    }

    @Override
    protected void selectPathForEvent ( @NotNull final TreePath path, final MouseEvent e )
    {
        if ( !isLocationInCheckBoxControl ( path, e.getX (), e.getY () ) )
        {
            super.selectPathForEvent ( path, e );
        }
    }

    @Override
    public boolean isLocationInCheckBoxControl ( @NotNull final TreePath path, final int x, final int y )
    {
        final boolean inCheckBox;
        if ( tree instanceof WebCheckBoxTree )
        {
            final WebCheckBoxTree checkBoxTree = ( WebCheckBoxTree ) tree;
            if ( checkBoxTree.isCheckingByUserEnabled () )
            {
                final MutableTreeNode node = ( MutableTreeNode ) path.getLastPathComponent ();
                if ( checkBoxTree.isCheckBoxVisible ( node ) && checkBoxTree.isCheckBoxEnabled ( node ) )
                {
                    final Rectangle checkBoxBounds = checkBoxTree.getCheckBoxBounds ( path );
                    inCheckBox = checkBoxBounds != null && checkBoxBounds.contains ( x, y );
                }
                else
                {
                    inCheckBox = false;
                }
            }
            else
            {
                inCheckBox = false;
            }
        }
        else
        {
            inCheckBox = false;
        }
        return inCheckBox;
    }

    @NotNull
    @Override
    public Icon getExpandedIcon ()
    {
        return tree.isEnabled () ? Icons.squareMinus : Icons.squareMinusDisabled;
    }

    @NotNull
    @Override
    public Icon getCollapsedIcon ()
    {
        return tree.isEnabled () ? Icons.squarePlus : Icons.squarePlusDisabled;
    }

    @NotNull
    @Override
    public CellRendererPane getCellRendererPane ()
    {
        return rendererPane;
    }

    @Nullable
    @Override
    public AbstractLayoutCache getTreeLayoutCache ()
    {
        return treeState;
    }

    @NotNull
    @Override
    public TreeSelectionStyle getSelectionStyle ()
    {
        return selectionStyle;
    }

    @Override
    public void setSelectionStyle ( @NotNull final TreeSelectionStyle style )
    {
        this.selectionStyle = style;
    }

    /**
     * Returns whether tree selection style points that the whole line is a single cell or not.
     *
     * @return true if tree selection style points that the whole line is a single cell, false otherwise
     */
    protected boolean isFullLineSelection ()
    {
        return selectionStyle == TreeSelectionStyle.line;
    }

    /**
     * Returns {@link TreeToolTipProvider} for {@link JTree} that uses this {@link WebTreeUI}.
     *
     * @return {@link TreeToolTipProvider} for {@link JTree} that uses this {@link WebTreeUI}
     */
    @Nullable
    protected TreeToolTipProvider getToolTipProvider ()
    {
        return tree != null ?
                ( TreeToolTipProvider ) tree.getClientProperty ( WebTree.TOOLTIP_PROVIDER_PROPERTY ) :
                null;
    }

    @Override
    public boolean contains ( @NotNull final JComponent c, final int x, final int y )
    {
        return PainterSupport.contains ( c, this, x, y );
    }

    @Override
    public void paint ( @NotNull final Graphics g, @NotNull final JComponent c )
    {
        PainterSupport.paint ( g, c, this, new TreePaintParameters ( currentCellRenderer ) );
    }

    @Nullable
    @Override
    public Dimension getPreferredSize ( @NotNull final JComponent c )
    {
        return PainterSupport.getPreferredSize ( c, super.getPreferredSize ( c ) );
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy