1 /*
2  * Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package javax.swing.plaf.metal;
27 
28 import javax.swing.*;
29 import javax.swing.event.*;
30 import java.awt.*;
31 import java.awt.event.*;
32 import java.beans.*;
33 import java.io.*;
34 import java.util.*;
35 import javax.swing.plaf.*;
36 import javax.swing.tree.*;
37 
38 import javax.swing.plaf.basic.*;
39 
40 /**
41  * The metal look and feel implementation of <code>TreeUI</code>.
42  * <p>
43  * <code>MetalTreeUI</code> allows for configuring how to
44  * visually render the spacing and delineation between nodes. The following
45  * hints are supported:
46  *
47  * <table class="striped">
48  * <caption>Descriptions of supported hints: Angled, Horizontal, and None
49  * </caption>
50  * <thead>
51  *   <tr>
52  *     <th scope="col">Hint
53  *     <th scope="col">Description
54  * </thead>
55  * <tbody>
56  *   <tr>
57  *     <th scope="row">Angled
58  *     <td>A line is drawn connecting the child to the parent. For handling of
59  *     the root node refer to {@link JTree#setRootVisible} and
60  *     {@link JTree#setShowsRootHandles}.
61  *   <tr>
62  *     <th scope="row">Horizontal
63  *     <td>A horizontal line is drawn dividing the children of the root node.
64  *   <tr>
65  *     <th scope="row">None
66  *     <td>Do not draw any visual indication between nodes.
67  * </tbody>
68  * </table>
69  * <p>
70  * As it is typically impractical to obtain the <code>TreeUI</code> from
71  * the <code>JTree</code> and cast to an instance of <code>MetalTreeUI</code>
72  * you enable this property via the client property
73  * <code>JTree.lineStyle</code>. For example, to switch to
74  * <code>Horizontal</code> style you would do:
75  * <code>tree.putClientProperty("JTree.lineStyle", "Horizontal");</code>
76  * <p>
77  * The default is <code>Angled</code>.
78  *
79  * @author Tom Santos
80  * @author Steve Wilson (value add stuff)
81  */
82 public class MetalTreeUI extends BasicTreeUI {
83 
84     private static Color lineColor;
85 
86     private static final String LINE_STYLE = "JTree.lineStyle";
87 
88     private static final String LEG_LINE_STYLE_STRING = "Angled";
89     private static final String HORIZ_STYLE_STRING = "Horizontal";
90     private static final String NO_STYLE_STRING = "None";
91 
92     private static final int LEG_LINE_STYLE = 2;
93     private static final int HORIZ_LINE_STYLE = 1;
94     private static final int NO_LINE_STYLE = 0;
95 
96     private int lineStyle = LEG_LINE_STYLE;
97     private PropertyChangeListener lineStyleListener = new LineListener();
98 
99     /**
100      * Constructs the {@code MetalTreeUI}.
101      *
102      * @param x a component
103      * @return the instance of the {@code MetalTreeUI}
104      */
createUI(JComponent x)105     public static ComponentUI createUI(JComponent x) {
106         return new MetalTreeUI();
107     }
108 
109     /**
110      * Constructs the {@code MetalTreeUI}.
111      */
MetalTreeUI()112     public MetalTreeUI() {
113         super();
114     }
115 
getHorizontalLegBuffer()116     protected int getHorizontalLegBuffer() {
117         return 3;
118     }
119 
installUI( JComponent c )120     public void installUI( JComponent c ) {
121         super.installUI( c );
122         lineColor = UIManager.getColor( "Tree.line" );
123 
124         Object lineStyleFlag = c.getClientProperty( LINE_STYLE );
125         decodeLineStyle(lineStyleFlag);
126         c.addPropertyChangeListener(lineStyleListener);
127 
128     }
129 
uninstallUI( JComponent c)130     public void uninstallUI( JComponent c) {
131          c.removePropertyChangeListener(lineStyleListener);
132          super.uninstallUI(c);
133     }
134 
135     /**
136      * Converts between the string passed into the client property
137      * and the internal representation (currently and int)
138      *
139      * @param lineStyleFlag a flag
140      */
decodeLineStyle(Object lineStyleFlag)141     protected void decodeLineStyle(Object lineStyleFlag) {
142         if ( lineStyleFlag == null ||
143                     lineStyleFlag.equals(LEG_LINE_STYLE_STRING)) {
144             lineStyle = LEG_LINE_STYLE; // default case
145         } else {
146             if ( lineStyleFlag.equals(NO_STYLE_STRING) ) {
147                 lineStyle = NO_LINE_STYLE;
148             } else if ( lineStyleFlag.equals(HORIZ_STYLE_STRING) ) {
149                 lineStyle = HORIZ_LINE_STYLE;
150             }
151         }
152     }
153 
154     /**
155      * Returns {@code true} if a point with X coordinate {@code mouseX}
156      * and Y coordinate {@code mouseY} is in expanded control.
157      *
158      * @param row a row
159      * @param rowLevel a row level
160      * @param mouseX X coordinate
161      * @param mouseY Y coordinate
162      * @return {@code true} if a point with X coordinate {@code mouseX}
163      *         and Y coordinate {@code mouseY} is in expanded control.
164      */
isLocationInExpandControl(int row, int rowLevel, int mouseX, int mouseY)165     protected boolean isLocationInExpandControl(int row, int rowLevel,
166                                                 int mouseX, int mouseY) {
167         if(tree != null && !isLeaf(row)) {
168             int                     boxWidth;
169 
170             if(getExpandedIcon() != null)
171                 boxWidth = getExpandedIcon().getIconWidth() + 6;
172             else
173                 boxWidth = 8;
174 
175             Insets i = tree.getInsets();
176             int    boxLeftX = (i != null) ? i.left : 0;
177 
178 
179             boxLeftX += (((rowLevel + depthOffset - 1) * totalChildIndent) +
180                         getLeftChildIndent()) - boxWidth/2;
181 
182             int boxRightX = boxLeftX + boxWidth;
183 
184             return mouseX >= boxLeftX && mouseX <= boxRightX;
185         }
186         return false;
187     }
188 
paint(Graphics g, JComponent c)189     public void paint(Graphics g, JComponent c) {
190         super.paint( g, c );
191 
192 
193         // Paint the lines
194         if (lineStyle == HORIZ_LINE_STYLE && !largeModel) {
195             paintHorizontalSeparators(g,c);
196         }
197     }
198 
199     /**
200      * Paints the horizontal separators.
201      *
202      * @param g an instance of {@code Graphics}
203      * @param c a component
204      */
paintHorizontalSeparators(Graphics g, JComponent c)205     protected void paintHorizontalSeparators(Graphics g, JComponent c) {
206         g.setColor( lineColor );
207 
208         Rectangle clipBounds = g.getClipBounds();
209 
210         int beginRow = getRowForPath(tree, getClosestPathForLocation
211                                      (tree, 0, clipBounds.y));
212         int endRow = getRowForPath(tree, getClosestPathForLocation
213                              (tree, 0, clipBounds.y + clipBounds.height - 1));
214 
215         if ( beginRow <= -1 || endRow <= -1 ) {
216             return;
217         }
218 
219         for ( int i = beginRow; i <= endRow; ++i ) {
220             TreePath        path = getPathForRow(tree, i);
221 
222             if(path != null && path.getPathCount() == 2) {
223                 Rectangle       rowBounds = getPathBounds(tree,getPathForRow
224                                                           (tree, i));
225 
226                 // Draw a line at the top
227                 if(rowBounds != null)
228                     g.drawLine(clipBounds.x, rowBounds.y,
229                                clipBounds.x + clipBounds.width, rowBounds.y);
230             }
231         }
232 
233     }
234 
paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, TreePath path)235     protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds,
236                                           Insets insets, TreePath path) {
237         if (lineStyle == LEG_LINE_STYLE) {
238             super.paintVerticalPartOfLeg(g, clipBounds, insets, path);
239         }
240     }
241 
paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf)242     protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds,
243                                             Insets insets, Rectangle bounds,
244                                             TreePath path, int row,
245                                             boolean isExpanded,
246                                             boolean hasBeenExpanded, boolean
247                                             isLeaf) {
248         if (lineStyle == LEG_LINE_STYLE) {
249             super.paintHorizontalPartOfLeg(g, clipBounds, insets, bounds,
250                                            path, row, isExpanded,
251                                            hasBeenExpanded, isLeaf);
252         }
253     }
254 
255     /** This class listens for changes in line style */
256     class LineListener implements PropertyChangeListener {
propertyChange(PropertyChangeEvent e)257         public void propertyChange(PropertyChangeEvent e) {
258             String name = e.getPropertyName();
259             if ( name.equals( LINE_STYLE ) ) {
260                 decodeLineStyle(e.getNewValue());
261             }
262         }
263     } // end class PaletteListener
264 
265 }
266