001    /* MetalScrollBarUI.java
002       Copyright (C) 2005, 2006, Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.plaf.metal;
040    
041    import java.awt.Color;
042    import java.awt.Dimension;
043    import java.awt.Graphics;
044    import java.awt.Insets;
045    import java.awt.Rectangle;
046    import java.beans.PropertyChangeEvent;
047    import java.beans.PropertyChangeListener;
048    
049    import javax.swing.JButton;
050    import javax.swing.JComponent;
051    import javax.swing.JScrollBar;
052    import javax.swing.SwingConstants;
053    import javax.swing.UIManager;
054    import javax.swing.plaf.ComponentUI;
055    import javax.swing.plaf.basic.BasicScrollBarUI;
056    
057    /**
058     * A UI delegate for the {@link JScrollBar} component.
059     */
060    public 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    }