001/* MetalScrollBarUI.java
002   Copyright (C) 2005, 2006, 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.Color;
042import java.awt.Dimension;
043import java.awt.Graphics;
044import java.awt.Insets;
045import java.awt.Rectangle;
046import java.beans.PropertyChangeEvent;
047import java.beans.PropertyChangeListener;
048
049import javax.swing.JButton;
050import javax.swing.JComponent;
051import javax.swing.JScrollBar;
052import javax.swing.SwingConstants;
053import javax.swing.UIManager;
054import javax.swing.plaf.ComponentUI;
055import javax.swing.plaf.basic.BasicScrollBarUI;
056
057/**
058 * A UI delegate for the {@link JScrollBar} component.
059 */
060public class MetalScrollBarUI extends BasicScrollBarUI
061{
062  
063  /**
064   * A property change handler for the UI delegate that monitors for
065   * changes to the "JScrollBar.isFreeStanding" property, and updates
066   * the buttons and track rendering as appropriate.
067   */
068  class MetalScrollBarPropertyChangeHandler 
069    extends BasicScrollBarUI.PropertyChangeHandler
070  {
071    /**
072     * Creates a new handler.
073     * 
074     * @see #createPropertyChangeListener()
075     */
076    public MetalScrollBarPropertyChangeHandler()
077    {
078      // Nothing to do here.
079    }
080    
081    /**
082     * Handles a property change event.  If the event name is
083     * <code>JSlider.isFreeStanding</code>, this method updates the 
084     * delegate, otherwise the event is passed up to the super class.
085     * 
086     * @param e  the property change event.
087     */
088    public void propertyChange(PropertyChangeEvent e)
089    {
090      if (e.getPropertyName().equals(FREE_STANDING_PROP))
091        {
092          Boolean prop = (Boolean) e.getNewValue();
093          isFreeStanding = prop == null ? true : prop.booleanValue();
094          if (increaseButton != null)
095            increaseButton.setFreeStanding(isFreeStanding);
096          if (decreaseButton != null)
097            decreaseButton.setFreeStanding(isFreeStanding);
098        }
099      else
100        super.propertyChange(e);
101    }
102  }
103  
104  /** The name for the 'free standing' property. */
105  public static final String FREE_STANDING_PROP = "JScrollBar.isFreeStanding";
106
107  /** The minimum thumb size for a scroll bar that is not free standing. */
108  private static final Dimension MIN_THUMB_SIZE = new Dimension(15, 15);
109
110  /** The minimum thumb size for a scroll bar that is free standing. */
111  private static final Dimension MIN_THUMB_SIZE_FREE_STANDING 
112    = new Dimension(17, 17);
113  
114  /** The button that increases the value in the scroll bar. */
115  protected MetalScrollButton increaseButton;
116  
117  /** The button that decreases the value in the scroll bar. */
118  protected MetalScrollButton decreaseButton;
119  
120  /** 
121   * The scroll bar width. 
122   */
123  protected int scrollBarWidth;
124  
125  /** 
126   * A flag that indicates whether the scroll bar is "free standing", which 
127   * means it has complete borders and can be used anywhere in the UI.  A 
128   * scroll bar which is not free standing has borders missing from one
129   * side, and relies on being part of another container with its own borders
130   * to look right visually. */
131  protected boolean isFreeStanding = true;
132  
133  /** 
134   * The color for the scroll bar shadow (this is read from the UIDefaults in 
135   * the installDefaults() method).
136   */
137  Color scrollBarShadowColor;
138  
139  /**
140   * Constructs a new instance of <code>MetalScrollBarUI</code>, with no
141   * specific initialisation.
142   */
143  public MetalScrollBarUI()
144  {
145    super();
146  }
147
148  /**
149   * Returns a new instance of <code>MetalScrollBarUI</code>.
150   *
151   * @param component the component for which we return an UI instance
152   *
153   * @return An instance of MetalScrollBarUI
154   */
155  public static ComponentUI createUI(JComponent component)
156  {
157    return new MetalScrollBarUI();
158  }
159
160  /**
161   * Installs the defaults.
162   */
163  protected void installDefaults()
164  {    
165    // need to initialise isFreeStanding before calling the super class, 
166    // so that the value is set when createIncreaseButton() and 
167    // createDecreaseButton() are called (unless there is somewhere earlier
168    // that we can do this).
169    Boolean prop = (Boolean) scrollbar.getClientProperty(FREE_STANDING_PROP);
170    isFreeStanding = prop == null ? true : prop.booleanValue();
171    scrollBarShadowColor = UIManager.getColor("ScrollBar.shadow");
172    scrollBarWidth = UIManager.getInt("ScrollBar.width");
173    super.installDefaults();
174  }
175    
176  /**
177   * Creates a property change listener for the delegate to use.  This
178   * overrides the method to provide a custom listener for the 
179   * {@link MetalLookAndFeel} that can handle the 
180   * <code>JScrollBar.isFreeStanding</code> property.
181   * 
182   * @return A property change listener.
183   */
184  protected PropertyChangeListener createPropertyChangeListener()
185  {
186    return new MetalScrollBarPropertyChangeHandler();
187  }
188  
189  /**
190   * Creates a new button to use as the control at the lower end of the
191   * {@link JScrollBar}.  This method assigns the new button (an instance of
192   * {@link MetalScrollButton} to the {@link #decreaseButton} field, and also 
193   * returns the button.  The button width is determined by the 
194   * <code>ScrollBar.width</code> setting in the UI defaults.
195   * 
196   * @param orientation  the orientation of the button ({@link #NORTH},
197   *                     {@link #SOUTH}, {@link #EAST} or {@link #WEST}).
198   * 
199   * @return The button.
200   */
201  protected JButton createDecreaseButton(int orientation)
202  {
203    decreaseButton = new MetalScrollButton(orientation, scrollBarWidth, 
204            isFreeStanding);
205    return decreaseButton;
206  }
207
208  /**
209   * Creates a new button to use as the control at the upper end of the
210   * {@link JScrollBar}.  This method assigns the new button (an instance of
211   * {@link MetalScrollButton} to the {@link #increaseButton} field, and also 
212   * returns the button.  The button width is determined by the 
213   * <code>ScrollBar.width</code> setting in the UI defaults.
214   * 
215   * @param orientation  the orientation of the button ({@link #NORTH},
216   *                     {@link #SOUTH}, {@link #EAST} or {@link #WEST}).
217   * 
218   * @return The button.
219   */
220  protected JButton createIncreaseButton(int orientation)
221  {
222    increaseButton = new MetalScrollButton(orientation, scrollBarWidth, 
223            isFreeStanding);
224    return increaseButton;
225  }
226  
227  /**
228   * Paints the track for the scrollbar.
229   * 
230   * @param g  the graphics device.
231   * @param c  the component.
232   * @param trackBounds  the track bounds.
233   */
234  protected void paintTrack(Graphics g, JComponent c, Rectangle trackBounds)
235  {
236    g.setColor(MetalLookAndFeel.getControl());
237    g.fillRect(trackBounds.x, trackBounds.y, trackBounds.width, 
238            trackBounds.height);
239    if (scrollbar.getOrientation() == HORIZONTAL) 
240      paintTrackHorizontal(g, c, trackBounds.x, trackBounds.y, 
241          trackBounds.width, trackBounds.height);
242    else 
243      paintTrackVertical(g, c, trackBounds.x, trackBounds.y, 
244          trackBounds.width, trackBounds.height);
245    
246  }
247  
248  /**
249   * Paints the track for a horizontal scrollbar.
250   * 
251   * @param g  the graphics device.
252   * @param c  the component.
253   * @param x  the x-coordinate for the track bounds.
254   * @param y  the y-coordinate for the track bounds.
255   * @param w  the width for the track bounds.
256   * @param h  the height for the track bounds.
257   */
258  private void paintTrackHorizontal(Graphics g, JComponent c, 
259      int x, int y, int w, int h)
260  {
261    if (c.isEnabled())
262      {
263        g.setColor(MetalLookAndFeel.getControlDarkShadow());
264        g.drawLine(x, y, x, y + h - 1);
265        g.drawLine(x, y, x + w - 1, y);
266        g.drawLine(x + w - 1, y, x + w - 1, y + h - 1);
267        
268        g.setColor(scrollBarShadowColor);
269        g.drawLine(x + 1, y + 1, x + 1, y + h - 1);
270        g.drawLine(x + 1, y + 1, x + w - 2, y + 1);
271        
272        if (isFreeStanding) 
273          {
274            g.setColor(MetalLookAndFeel.getControlDarkShadow());
275            g.drawLine(x, y + h - 2, x + w - 1, y + h - 2);
276            g.setColor(scrollBarShadowColor);
277            g.drawLine(x, y + h - 1, x + w - 1, y + h - 1);
278          }
279      }
280    else
281      {
282        g.setColor(MetalLookAndFeel.getControlDisabled());
283        if (isFreeStanding)
284          g.drawRect(x, y, w - 1, h - 1);
285        else
286          {
287            g.drawLine(x, y, x + w - 1, y);
288            g.drawLine(x, y, x, y + h - 1);
289            g.drawLine(x + w - 1, y, x + w - 1, y + h - 1);
290          }
291      }
292  }
293    
294  /**
295   * Paints the track for a vertical scrollbar.
296   * 
297   * @param g  the graphics device.
298   * @param c  the component.
299   * @param x  the x-coordinate for the track bounds.
300   * @param y  the y-coordinate for the track bounds.
301   * @param w  the width for the track bounds.
302   * @param h  the height for the track bounds.
303   */
304  private void paintTrackVertical(Graphics g, JComponent c, 
305      int x, int y, int w, int h)
306  {
307    if (c.isEnabled())
308      {
309        g.setColor(MetalLookAndFeel.getControlDarkShadow());
310        g.drawLine(x, y, x, y + h - 1);
311        g.drawLine(x, y, x + w - 1, y);
312        g.drawLine(x, y + h - 1, x + w - 1, y + h - 1);
313        
314        g.setColor(scrollBarShadowColor);
315        g.drawLine(x + 1, y + 1, x + w - 1, y + 1);
316        g.drawLine(x + 1, y + 1, x + 1, y + h - 2);
317        
318        if (isFreeStanding) 
319          {
320            g.setColor(MetalLookAndFeel.getControlDarkShadow());
321            g.drawLine(x + w - 2, y, x + w - 2, y + h - 1);
322            g.setColor(MetalLookAndFeel.getControlHighlight());
323            g.drawLine(x + w - 1, y, x + w - 1, y + h - 1);
324          }
325      }
326    else
327      {
328        g.setColor(MetalLookAndFeel.getControlDisabled());
329        if (isFreeStanding)
330          g.drawRect(x, y, w - 1, h - 1);
331        else
332          {
333            g.drawLine(x, y, x + w - 1, y);
334            g.drawLine(x, y, x, y + h - 1);
335            g.drawLine(x, y + h - 1, x + w - 1, y + h - 1);
336          }
337      }
338  }
339
340  /**
341   * Paints the slider button of the ScrollBar.
342   *
343   * @param g the Graphics context to use
344   * @param c the JComponent on which we paint
345   * @param thumbBounds the rectangle that is the slider button
346   */
347  protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds)
348  {
349    // a disabled scrollbar has no thumb in the metal look and feel
350    if (!c.isEnabled())
351      return;
352    if (scrollbar.getOrientation() == HORIZONTAL)
353      paintThumbHorizontal(g, c, thumbBounds);
354    else 
355      paintThumbVertical(g, c, thumbBounds);
356
357    // Draw the pattern when the theme is not Ocean.
358    if (! (MetalLookAndFeel.getCurrentTheme() instanceof OceanTheme))
359      {
360        MetalUtils.fillMetalPattern(c, g, thumbBounds.x + 3, thumbBounds.y + 3,
361                                    thumbBounds.width - 6,
362                                    thumbBounds.height - 6,
363                                    thumbHighlightColor,
364                                    thumbLightShadowColor);
365      }
366  }
367
368  /**
369   * Paints the thumb for a horizontal scroll bar.
370   * 
371   * @param g  the graphics device.
372   * @param c  the scroll bar component.
373   * @param thumbBounds  the thumb bounds.
374   */
375  private void paintThumbHorizontal(Graphics g, JComponent c, 
376          Rectangle thumbBounds) 
377  {
378    int x = thumbBounds.x;
379    int y = thumbBounds.y;
380    int w = thumbBounds.width;
381    int h = thumbBounds.height;
382    
383    // First we fill the background.
384    MetalTheme theme = MetalLookAndFeel.getCurrentTheme();
385    if (theme instanceof OceanTheme
386        && UIManager.get("ScrollBar.gradient") != null)
387      {
388        MetalUtils.paintGradient(g, x + 2, y + 2, w - 4, h - 2,
389                                 SwingConstants.VERTICAL,
390                                 "ScrollBar.gradient");
391      }
392    else
393      {
394        g.setColor(thumbColor);
395        if (isFreeStanding)
396          g.fillRect(x, y, w, h - 1);
397        else
398          g.fillRect(x, y, w, h);
399      }
400
401    // then draw the dark box
402    g.setColor(thumbLightShadowColor);
403    if (isFreeStanding)
404      g.drawRect(x, y, w - 1, h - 2);
405    else
406      {
407        g.drawLine(x, y, x + w - 1, y);
408        g.drawLine(x, y, x, y + h - 1);
409        g.drawLine(x + w - 1, y, x + w - 1, y + h - 1);
410      }
411    
412    // then the highlight
413    g.setColor(thumbHighlightColor);
414    if (isFreeStanding)
415      {
416        g.drawLine(x + 1, y + 1, x + w - 3, y + 1);
417        g.drawLine(x + 1, y + 1, x + 1, y + h - 3);
418      }
419    else
420      {
421        g.drawLine(x + 1, y + 1, x + w - 3, y + 1);
422        g.drawLine(x + 1, y + 1, x + 1, y + h - 1);
423      }
424    
425    // draw the shadow line
426    g.setColor(UIManager.getColor("ScrollBar.shadow"));
427    g.drawLine(x + w, y + 1, x + w, y + h - 1);
428
429    // For the OceanTheme, draw the 3 lines in the middle.
430    if (theme instanceof OceanTheme)
431      {
432        g.setColor(thumbLightShadowColor);
433        int middle = x + w / 2;
434        g.drawLine(middle - 2, y + 4, middle - 2, y + h - 5);
435        g.drawLine(middle, y + 4, middle, y + h - 5);
436        g.drawLine(middle + 2, y + 4, middle + 2, y + h - 5);
437        g.setColor(UIManager.getColor("ScrollBar.highlight"));
438        g.drawLine(middle - 1, y + 5, middle - 1, y + h - 4);
439        g.drawLine(middle + 1, y + 5, middle + 1, y + h - 4);
440        g.drawLine(middle + 3, y + 5, middle + 3, y + h - 4);
441      }
442  }
443  
444  /**
445   * Paints the thumb for a vertical scroll bar.
446   * 
447   * @param g  the graphics device.
448   * @param c  the scroll bar component.
449   * @param thumbBounds  the thumb bounds.
450   */
451  private void paintThumbVertical(Graphics g, JComponent c, 
452          Rectangle thumbBounds)
453  {
454    int x = thumbBounds.x;
455    int y = thumbBounds.y;
456    int w = thumbBounds.width;
457    int h = thumbBounds.height;
458    
459    // First we fill the background.
460    MetalTheme theme = MetalLookAndFeel.getCurrentTheme();
461    if (theme instanceof OceanTheme
462        && UIManager.get("ScrollBar.gradient") != null)
463      {
464        MetalUtils.paintGradient(g, x + 2, y + 2, w - 2, h - 4,
465                                 SwingConstants.HORIZONTAL,
466                                 "ScrollBar.gradient");
467      }
468    else
469      {
470        g.setColor(thumbColor);
471        if (isFreeStanding)
472          g.fillRect(x, y, w - 1, h);
473        else
474          g.fillRect(x, y, w, h);
475      }
476
477    // then draw the dark box
478    g.setColor(thumbLightShadowColor);
479    if (isFreeStanding)
480      g.drawRect(x, y, w - 2, h - 1);
481    else
482      {
483        g.drawLine(x, y, x + w - 1, y);
484        g.drawLine(x, y, x, y + h - 1);
485        g.drawLine(x, y + h - 1, x + w - 1, y + h - 1);
486      }
487    
488    // then the highlight
489    g.setColor(thumbHighlightColor);
490    if (isFreeStanding)
491      {
492        g.drawLine(x + 1, y + 1, x + w - 3, y + 1);
493        g.drawLine(x + 1, y + 1, x + 1, y + h - 3);
494      }
495    else
496      {
497        g.drawLine(x + 1, y + 1, x + w - 1, y + 1);
498        g.drawLine(x + 1, y + 1, x + 1, y + h - 3);
499      }
500    
501    // draw the shadow line
502    g.setColor(UIManager.getColor("ScrollBar.shadow"));
503    g.drawLine(x + 1, y + h, x + w - 2, y + h);
504
505    // For the OceanTheme, draw the 3 lines in the middle.
506    if (theme instanceof OceanTheme)
507      {
508        g.setColor(thumbLightShadowColor);
509        int middle = y + h / 2;
510        g.drawLine(x + 4, middle - 2, x + w - 5, middle - 2);
511        g.drawLine(x + 4, middle, x + w - 5, middle);
512        g.drawLine(x + 4, middle + 2, x + w - 5, middle + 2);
513        g.setColor(UIManager.getColor("ScrollBar.highlight"));
514        g.drawLine(x + 5, middle - 1, x + w - 4, middle - 1);
515        g.drawLine(x + 5, middle + 1, x + w - 4, middle + 1);
516        g.drawLine(x + 5, middle + 3, x + w - 4, middle + 3);
517      }
518  }
519  
520  /**
521   * Returns the minimum thumb size.  For a free standing scroll bar the 
522   * minimum size is <code>17 x 17</code> pixels, whereas for a non free 
523   * standing scroll bar the minimum size is <code>15 x 15</code> pixels.
524   *
525   * @return The minimum thumb size.
526   */
527  protected Dimension getMinimumThumbSize()
528  {
529    Dimension retVal;
530    if (scrollbar != null)
531      {
532        if (isFreeStanding)
533          retVal = MIN_THUMB_SIZE_FREE_STANDING;
534        else
535          retVal = MIN_THUMB_SIZE;
536      }
537    else
538      retVal = new Dimension(0, 0);
539    return retVal;
540  }
541
542  /**
543   * Returns the <code>preferredSize</code> for the specified scroll bar.
544   * For a vertical scrollbar the height is the sum of the preferred heights
545   * of the buttons plus <code>30</code>. The width is fetched from the
546   * <code>UIManager</code> property <code>ScrollBar.width</code>.
547   *
548   * For horizontal scrollbars the width is the sum of the preferred widths
549   * of the buttons plus <code>30</code>. The height is fetched from the
550   * <code>UIManager</code> property <code>ScrollBar.height</code>.
551   *
552   * @param c the scrollbar for which to calculate the preferred size
553   *
554   * @return the <code>preferredSize</code> for the specified scroll bar
555   */
556  public Dimension getPreferredSize(JComponent c)
557  {
558    int height;
559    int width;
560    height = width = 0;
561
562    if (scrollbar.getOrientation() == SwingConstants.HORIZONTAL)
563      {
564        width += incrButton.getPreferredSize().getWidth();
565        width += decrButton.getPreferredSize().getWidth();
566        width += 30;
567        height = UIManager.getInt("ScrollBar.width");
568      }
569    else
570      {
571        height += incrButton.getPreferredSize().getHeight();
572        height += decrButton.getPreferredSize().getHeight();
573        height += 30;
574        width = UIManager.getInt("ScrollBar.width");
575      }
576
577    Insets insets = scrollbar.getInsets();
578
579    height += insets.top + insets.bottom;
580    width += insets.left + insets.right;
581
582    return new Dimension(width, height);
583  } 
584}
585