1 /* MetalScrollBarUI.java
2    Copyright (C) 2005, 2006, Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package javax.swing.plaf.metal;
40 
41 import java.awt.Color;
42 import java.awt.Dimension;
43 import java.awt.Graphics;
44 import java.awt.Insets;
45 import java.awt.Rectangle;
46 import java.beans.PropertyChangeEvent;
47 import java.beans.PropertyChangeListener;
48 
49 import javax.swing.JButton;
50 import javax.swing.JComponent;
51 import javax.swing.JScrollBar;
52 import javax.swing.SwingConstants;
53 import javax.swing.UIManager;
54 import javax.swing.plaf.ComponentUI;
55 import javax.swing.plaf.basic.BasicScrollBarUI;
56 
57 /**
58  * A UI delegate for the {@link JScrollBar} component.
59  */
60 public class MetalScrollBarUI extends BasicScrollBarUI
61 {
62 
63   /**
64    * A property change handler for the UI delegate that monitors for
65    * changes to the "JScrollBar.isFreeStanding" property, and updates
66    * the buttons and track rendering as appropriate.
67    */
68   class MetalScrollBarPropertyChangeHandler
69     extends BasicScrollBarUI.PropertyChangeHandler
70   {
71     /**
72      * Creates a new handler.
73      *
74      * @see #createPropertyChangeListener()
75      */
MetalScrollBarPropertyChangeHandler()76     public MetalScrollBarPropertyChangeHandler()
77     {
78       // Nothing to do here.
79     }
80 
81     /**
82      * Handles a property change event.  If the event name is
83      * <code>JSlider.isFreeStanding</code>, this method updates the
84      * delegate, otherwise the event is passed up to the super class.
85      *
86      * @param e  the property change event.
87      */
propertyChange(PropertyChangeEvent e)88     public void propertyChange(PropertyChangeEvent e)
89     {
90       if (e.getPropertyName().equals(FREE_STANDING_PROP))
91         {
92           Boolean prop = (Boolean) e.getNewValue();
93           isFreeStanding = prop == null ? true : prop.booleanValue();
94           if (increaseButton != null)
95             increaseButton.setFreeStanding(isFreeStanding);
96           if (decreaseButton != null)
97             decreaseButton.setFreeStanding(isFreeStanding);
98         }
99       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    */
MetalScrollBarUI()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    */
createUI(JComponent component)155   public static ComponentUI createUI(JComponent component)
156   {
157     return new MetalScrollBarUI();
158   }
159 
160   /**
161    * Installs the defaults.
162    */
installDefaults()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    */
createPropertyChangeListener()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    */
createDecreaseButton(int orientation)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    */
createIncreaseButton(int orientation)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    */
paintTrack(Graphics g, JComponent c, Rectangle trackBounds)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    */
paintTrackHorizontal(Graphics g, JComponent c, int x, int y, int w, int h)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    */
paintTrackVertical(Graphics g, JComponent c, int x, int y, int w, int h)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    */
paintThumb(Graphics g, JComponent c, Rectangle thumbBounds)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    */
paintThumbHorizontal(Graphics g, JComponent c, Rectangle thumbBounds)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    */
paintThumbVertical(Graphics g, JComponent c, Rectangle thumbBounds)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    */
getMinimumThumbSize()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    */
getPreferredSize(JComponent c)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