001/* MetalTreeUI.java
002   Copyright (C) 2005 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.plaf.metal;
040
041import java.awt.Graphics;
042import java.awt.Insets;
043import java.awt.Rectangle;
044import java.beans.PropertyChangeEvent;
045import java.beans.PropertyChangeListener;
046
047import javax.swing.JComponent;
048import javax.swing.JTree;
049import javax.swing.UIManager;
050import javax.swing.tree.TreePath;
051import javax.swing.plaf.ComponentUI;
052import javax.swing.plaf.basic.BasicTreeUI;
053
054/**
055 * A UI delegate for the {@link JTree} component.
056 */
057public class MetalTreeUI extends BasicTreeUI
058{
059  /**
060   * Listens for property changes of the line style and updates the
061   * internal setting.
062   */
063  private class LineStyleListener
064    implements PropertyChangeListener
065  {
066
067    public void propertyChange(PropertyChangeEvent e)
068    {
069      if (e.getPropertyName().equals(LINE_STYLE_PROPERTY))
070        decodeLineStyle(e.getNewValue());
071    }
072      
073  }
074
075  /**
076   * The key to the lineStyle client property.
077   */
078  private static final String LINE_STYLE_PROPERTY = "JTree.lineStyle";
079
080  /**
081   * The property value indicating no line style.
082   */
083  private static final String LINE_STYLE_VALUE_NONE = "None";
084
085  /**
086   * The property value indicating angled line style.
087   */
088  private static final String LINE_STYLE_VALUE_ANGLED = "Angled";
089
090  /**
091   * The property value indicating horizontal line style.
092   */
093  private static final String LINE_STYLE_VALUE_HORIZONTAL = "Horizontal";
094
095  /**
096   * The line style for None.
097   */
098  private static final int LINE_STYLE_NONE = 0;
099
100  /**
101   * The line style for Angled.
102   */
103  private static final int LINE_STYLE_ANGLED = 1;
104
105  /**
106   * The line style for Horizontal.
107   */
108  private static final int LINE_STYLE_HORIZONTAL = 2;
109
110  /**
111   * The current line style.
112   */
113  private int lineStyle;
114
115  /**
116   * Listens for changes on the line style property and updates the
117   * internal settings.
118   */
119  private PropertyChangeListener lineStyleListener;
120
121  /**
122   * Constructs a new instance of <code>MetalTreeUI</code>.
123   */
124  public MetalTreeUI()
125  {
126    super();
127  }
128
129  /**
130   * Returns a new instance of <code>MetalTreeUI</code>.
131   *
132   * @param component the component for which we return an UI instance
133   *
134   * @return A new instance of <code>MetalTreeUI</code>.
135   */
136  public static ComponentUI createUI(JComponent component)
137  {
138    return new MetalTreeUI();
139  }
140  
141  /**
142   * The horizontal element of legs between nodes starts at the right of the
143   * left-hand side of the child node by default. This method makes the
144   * leg end before that.
145   */
146  protected int getHorizontalLegBuffer()
147  {
148    return super.getHorizontalLegBuffer();
149  }
150
151  /**
152   * Configures the specified component appropriate for the look and feel.
153   * This method is invoked when the ComponentUI instance is being installed 
154   * as the UI delegate on the specified component. This method should completely 
155   * configure the component for the look and feel, including the following:
156   * 1. Install any default property values for color, fonts, borders, icons, 
157   *    opacity, etc. on the component. Whenever possible, property values
158   *    initialized by the client program should not be overridden.
159   * 2. Install a LayoutManager on the component if necessary.
160   * 3. Create/add any required sub-components to the component.
161   * 4. Create/install event listeners on the component.
162   * 5. Create/install a PropertyChangeListener on the component in order 
163   *    to detect and respond to component property changes appropriately.
164   * 6. Install keyboard UI (mnemonics, traversal, etc.) on the component.
165   * 7. Initialize any appropriate instance data. 
166   */
167  public void installUI(JComponent c)
168  {
169    super.installUI(c);
170
171    Object lineStyleProp = c.getClientProperty(LINE_STYLE_PROPERTY);
172    decodeLineStyle(lineStyleProp);
173    if (lineStyleListener == null)
174      lineStyleListener = new LineStyleListener();
175    c.addPropertyChangeListener(lineStyleListener);
176  }
177  
178  /**
179   * Reverses configuration which was done on the specified component during 
180   * installUI. This method is invoked when this UIComponent instance is being 
181   * removed as the UI delegate for the specified component. This method should 
182   * undo the configuration performed in installUI, being careful to leave the 
183   * JComponent instance in a clean state (no extraneous listeners, 
184   * look-and-feel-specific property objects, etc.). This should include 
185   * the following:
186   * 1. Remove any UI-set borders from the component.
187   * 2. Remove any UI-set layout managers on the component.
188   * 3. Remove any UI-added sub-components from the component.
189   * 4. Remove any UI-added event/property listeners from the component.
190   * 5. Remove any UI-installed keyboard UI from the component.
191   * 6. Nullify any allocated instance data objects to allow for GC. 
192   */
193  public void uninstallUI(JComponent c)
194  {
195    super.uninstallUI(c);
196    if (lineStyleListener != null)
197      c.removePropertyChangeListener(lineStyleListener);
198    lineStyleListener = null;
199  }
200  
201  /**
202   * This function converts between the string passed into the client
203   * property and the internal representation (currently an int).
204   * 
205   * @param lineStyleFlag - String representation
206   */     
207  protected void decodeLineStyle(Object lineStyleFlag)
208  {
209    if (lineStyleFlag == null || lineStyleFlag.equals(LINE_STYLE_VALUE_ANGLED))
210      lineStyle = LINE_STYLE_ANGLED;
211    else if (lineStyleFlag.equals(LINE_STYLE_VALUE_HORIZONTAL))
212      lineStyle = LINE_STYLE_HORIZONTAL;
213    else if (lineStyleFlag.equals(LINE_STYLE_VALUE_NONE))
214      lineStyle = LINE_STYLE_NONE;
215    else
216      lineStyle = LINE_STYLE_ANGLED;
217  }
218
219  /**
220   * Checks if the location is in expand control.
221   * 
222   * @param row - current row
223   * @param rowLevel - current level
224   * @param mouseX - current x location of the mouse click
225   * @param mouseY - current y location of the mouse click
226   */
227  protected boolean isLocationInExpandControl(int row, int rowLevel,
228                                          int mouseX, int mouseY)
229  {
230    return super.isLocationInExpandControl(tree.getPathForRow(row), 
231                                           mouseX, mouseY);
232  }
233  
234  /**
235   * Paints the specified component appropriate for the look and feel. 
236   * This method is invoked from the ComponentUI.update method when the 
237   * specified component is being painted. Subclasses should override this 
238   * method and use the specified Graphics object to render the content of 
239   * the component.
240   * 
241   * @param g - the current graphics configuration.
242   * @param c - the current component to draw
243   */
244  public void paint(Graphics g, JComponent c)
245  {
246    // Calls BasicTreeUI's paint since it takes care of painting all
247    // types of icons. 
248    super.paint(g, c);
249
250    if (lineStyle == LINE_STYLE_HORIZONTAL)
251      paintHorizontalSeparators(g, c);
252  }
253  
254  /**
255   * Paints the horizontal separators.
256   * 
257   * @param g - the current graphics configuration.
258   * @param c - the current component to draw
259   */
260  protected void paintHorizontalSeparators(Graphics g, JComponent c)
261  {
262    g.setColor(UIManager.getColor("Tree.line"));
263    Rectangle clip = g.getClipBounds();
264    int row0 = getRowForPath(tree, getClosestPathForLocation(tree, 0, clip.y));
265    int row1 =
266      getRowForPath(tree, getClosestPathForLocation(tree, 0,
267                                                    clip.y + clip.height - 1));
268    if (row0 >= 0 && row1 >= 0)
269      {
270        for (int i = row0; i <= row1; i++)
271          {
272            TreePath p = getPathForRow(tree, i);
273            if (p != null && p.getPathCount() == 2)
274              {
275                Rectangle r = getPathBounds(tree, getPathForRow(tree, i));
276                if (r != null)
277                  {
278                    g.drawLine(clip.x, r.y, clip.x + clip.width, r.y);
279                  }
280              }
281          }
282      }
283  }
284
285  
286  /**
287   * Paints the vertical part of the leg. The receiver should NOT modify 
288   * clipBounds, insets.
289   * 
290   * @param g - the current graphics configuration.
291   * @param clipBounds -
292   * @param insets - 
293   * @param path - the current path
294   */
295  protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds,
296                                    Insets insets, TreePath path)
297  {
298    if (lineStyle == LINE_STYLE_ANGLED)
299      super.paintVerticalPartOfLeg(g, clipBounds, insets, path);
300  }
301
302  /**
303   * Paints the horizontal part of the leg. The receiver should NOT \
304   * modify clipBounds, or insets.
305   * NOTE: parentRow can be -1 if the root is not visible.
306   */
307  protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds,
308                                        Insets insets, Rectangle bounds,
309                                        TreePath path, int row,
310                                        boolean isExpanded, boolean hasBeenExpanded,
311                                        boolean isLeaf)
312  {
313    if (lineStyle == LINE_STYLE_ANGLED)
314      super.paintHorizontalPartOfLeg(g, clipBounds, insets, bounds, path, row, 
315                                     isExpanded, hasBeenExpanded, isLeaf);
316  }
317}