1 /*
2  * Copyright (c) 2002, 2016, 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.synth;
27 
28 import java.awt.*;
29 import java.beans.*;
30 import javax.swing.*;
31 import javax.swing.plaf.*;
32 import javax.swing.plaf.basic.*;
33 
34 
35 /**
36  * Provides the Synth L&F UI delegate for
37  * {@link javax.swing.JScrollBar}.
38  *
39  * @author Scott Violet
40  * @since 1.7
41  */
42 public class SynthScrollBarUI extends BasicScrollBarUI
43                               implements PropertyChangeListener, SynthUI {
44 
45     private SynthStyle style;
46     private SynthStyle thumbStyle;
47     private SynthStyle trackStyle;
48 
49     private boolean validMinimumThumbSize;
50 
51     /**
52      * Returns a UI.
53      * @return a UI
54      * @param c a component
55      */
createUI(JComponent c)56     public static ComponentUI createUI(JComponent c)    {
57         return new SynthScrollBarUI();
58     }
59 
60     /**
61      * {@inheritDoc}
62      */
63     @Override
installDefaults()64     protected void installDefaults() {
65         super.installDefaults();
66         trackHighlight = NO_HIGHLIGHT;
67         if (scrollbar.getLayout() == null ||
68                      (scrollbar.getLayout() instanceof UIResource)) {
69             scrollbar.setLayout(this);
70         }
71         configureScrollBarColors();
72         updateStyle(scrollbar);
73     }
74 
75     /**
76      * {@inheritDoc}
77      */
78     @Override
configureScrollBarColors()79     protected void configureScrollBarColors() {
80     }
81 
updateStyle(JScrollBar c)82     private void updateStyle(JScrollBar c) {
83         SynthStyle oldStyle = style;
84         SynthContext context = getContext(c, ENABLED);
85         style = SynthLookAndFeel.updateStyle(context, this);
86         if (style != oldStyle) {
87             scrollBarWidth = style.getInt(context,"ScrollBar.thumbHeight", 14);
88             minimumThumbSize = (Dimension)style.get(context,
89                                                 "ScrollBar.minimumThumbSize");
90             if (minimumThumbSize == null) {
91                 minimumThumbSize = new Dimension();
92                 validMinimumThumbSize = false;
93             }
94             else {
95                 validMinimumThumbSize = true;
96             }
97             maximumThumbSize = (Dimension)style.get(context,
98                         "ScrollBar.maximumThumbSize");
99             if (maximumThumbSize == null) {
100                 maximumThumbSize = new Dimension(4096, 4097);
101             }
102 
103             incrGap = style.getInt(context, "ScrollBar.incrementButtonGap", 0);
104             decrGap = style.getInt(context, "ScrollBar.decrementButtonGap", 0);
105 
106             // handle scaling for sizeVarients for special case components. The
107             // key "JComponent.sizeVariant" scales for large/small/mini
108             // components are based on Apples LAF
109             String scaleKey = (String)scrollbar.getClientProperty(
110                     "JComponent.sizeVariant");
111             if (scaleKey != null){
112                 if ("large".equals(scaleKey)){
113                     scrollBarWidth *= 1.15;
114                     incrGap *= 1.15;
115                     decrGap *= 1.15;
116                 } else if ("small".equals(scaleKey)){
117                     scrollBarWidth *= 0.857;
118                     incrGap *= 0.857;
119                     decrGap *= 0.857;
120                 } else if ("mini".equals(scaleKey)){
121                     scrollBarWidth *= 0.714;
122                     incrGap *= 0.714;
123                     decrGap *= 0.714;
124                 }
125             }
126 
127             if (oldStyle != null) {
128                 uninstallKeyboardActions();
129                 installKeyboardActions();
130             }
131         }
132 
133         context = getContext(c, Region.SCROLL_BAR_TRACK, ENABLED);
134         trackStyle = SynthLookAndFeel.updateStyle(context, this);
135 
136         context = getContext(c, Region.SCROLL_BAR_THUMB, ENABLED);
137         thumbStyle = SynthLookAndFeel.updateStyle(context, this);
138     }
139 
140     /**
141      * {@inheritDoc}
142      */
143     @Override
installListeners()144     protected void installListeners() {
145         super.installListeners();
146         scrollbar.addPropertyChangeListener(this);
147     }
148 
149     /**
150      * {@inheritDoc}
151      */
152     @Override
uninstallListeners()153     protected void uninstallListeners() {
154         super.uninstallListeners();
155         scrollbar.removePropertyChangeListener(this);
156     }
157 
158     /**
159      * {@inheritDoc}
160      */
161     @Override
uninstallDefaults()162     protected void uninstallDefaults(){
163         SynthContext context = getContext(scrollbar, ENABLED);
164         style.uninstallDefaults(context);
165         style = null;
166 
167         context = getContext(scrollbar, Region.SCROLL_BAR_TRACK, ENABLED);
168         trackStyle.uninstallDefaults(context);
169         trackStyle = null;
170 
171         context = getContext(scrollbar, Region.SCROLL_BAR_THUMB, ENABLED);
172         thumbStyle.uninstallDefaults(context);
173         thumbStyle = null;
174 
175         super.uninstallDefaults();
176     }
177 
178     /**
179      * {@inheritDoc}
180      */
181     @Override
getContext(JComponent c)182     public SynthContext getContext(JComponent c) {
183         return getContext(c, SynthLookAndFeel.getComponentState(c));
184     }
185 
getContext(JComponent c, int state)186     private SynthContext getContext(JComponent c, int state) {
187         return SynthContext.getContext(c, style, state);
188     }
189 
getContext(JComponent c, Region region)190     private SynthContext getContext(JComponent c, Region region) {
191         return getContext(c, region, getComponentState(c, region));
192     }
193 
getContext(JComponent c, Region region, int state)194     private SynthContext getContext(JComponent c, Region region, int state) {
195         SynthStyle style = trackStyle;
196 
197         if (region == Region.SCROLL_BAR_THUMB) {
198             style = thumbStyle;
199         }
200         return SynthContext.getContext(c, region, style, state);
201     }
202 
getComponentState(JComponent c, Region region)203     private int getComponentState(JComponent c, Region region) {
204         if (region == Region.SCROLL_BAR_THUMB && c.isEnabled()) {
205             if (isDragging) {
206                 return PRESSED;
207             } else if (isThumbRollover()) {
208                 return MOUSE_OVER;
209             }
210         }
211         return SynthLookAndFeel.getComponentState(c);
212     }
213 
214     /**
215      * {@inheritDoc}
216      */
217     @Override
getSupportsAbsolutePositioning()218     public boolean getSupportsAbsolutePositioning() {
219         SynthContext context = getContext(scrollbar);
220         boolean value = style.getBoolean(context,
221                       "ScrollBar.allowsAbsolutePositioning", false);
222         return value;
223     }
224 
225     /**
226      * Notifies this UI delegate to repaint the specified component.
227      * This method paints the component background, then calls
228      * the {@link #paint(SynthContext,Graphics)} method.
229      *
230      * <p>In general, this method does not need to be overridden by subclasses.
231      * All Look and Feel rendering code should reside in the {@code paint} method.
232      *
233      * @param g the {@code Graphics} object used for painting
234      * @param c the component being painted
235      * @see #paint(SynthContext,Graphics)
236      */
237     @Override
update(Graphics g, JComponent c)238     public void update(Graphics g, JComponent c) {
239         SynthContext context = getContext(c);
240 
241         SynthLookAndFeel.update(context, g);
242         context.getPainter().paintScrollBarBackground(context,
243                           g, 0, 0, c.getWidth(), c.getHeight(),
244                           scrollbar.getOrientation());
245         paint(context, g);
246     }
247 
248     /**
249      * Paints the specified component according to the Look and Feel.
250      * <p>This method is not used by Synth Look and Feel.
251      * Painting is handled by the {@link #paint(SynthContext,Graphics)} method.
252      *
253      * @param g the {@code Graphics} object used for painting
254      * @param c the component being painted
255      * @see #paint(SynthContext,Graphics)
256      */
257     @Override
paint(Graphics g, JComponent c)258     public void paint(Graphics g, JComponent c) {
259         SynthContext context = getContext(c);
260 
261         paint(context, g);
262     }
263 
264     /**
265      * Paints the specified component.
266      *
267      * @param context context for the component being painted
268      * @param g the {@code Graphics} object used for painting
269      * @see #update(Graphics,JComponent)
270      */
paint(SynthContext context, Graphics g)271     protected void paint(SynthContext context, Graphics g) {
272         SynthContext subcontext = getContext(scrollbar,
273                                              Region.SCROLL_BAR_TRACK);
274         paintTrack(subcontext, g, getTrackBounds());
275 
276         subcontext = getContext(scrollbar, Region.SCROLL_BAR_THUMB);
277         paintThumb(subcontext, g, getThumbBounds());
278     }
279 
280     /**
281      * {@inheritDoc}
282      */
283     @Override
paintBorder(SynthContext context, Graphics g, int x, int y, int w, int h)284     public void paintBorder(SynthContext context, Graphics g, int x,
285                             int y, int w, int h) {
286         context.getPainter().paintScrollBarBorder(context, g, x, y, w, h,
287                                                   scrollbar.getOrientation());
288     }
289 
290     /**
291      * Paints the scrollbar track.
292      *
293      * @param context context for the component being painted
294      * @param g {@code Graphics} object used for painting
295      * @param trackBounds bounding box for the track
296      */
paintTrack(SynthContext context, Graphics g, Rectangle trackBounds)297     protected void paintTrack(SynthContext context, Graphics g,
298                               Rectangle trackBounds) {
299         SynthLookAndFeel.updateSubregion(context, g, trackBounds);
300         context.getPainter().paintScrollBarTrackBackground(context, g, trackBounds.x,
301                         trackBounds.y, trackBounds.width, trackBounds.height,
302                         scrollbar.getOrientation());
303         context.getPainter().paintScrollBarTrackBorder(context, g, trackBounds.x,
304                         trackBounds.y, trackBounds.width, trackBounds.height,
305                         scrollbar.getOrientation());
306     }
307 
308     /**
309      * Paints the scrollbar thumb.
310      *
311      * @param context context for the component being painted
312      * @param g {@code Graphics} object used for painting
313      * @param thumbBounds bounding box for the thumb
314      */
paintThumb(SynthContext context, Graphics g, Rectangle thumbBounds)315     protected void paintThumb(SynthContext context, Graphics g,
316                               Rectangle thumbBounds) {
317         SynthLookAndFeel.updateSubregion(context, g, thumbBounds);
318         int orientation = scrollbar.getOrientation();
319         context.getPainter().paintScrollBarThumbBackground(context, g, thumbBounds.x,
320                         thumbBounds.y, thumbBounds.width, thumbBounds.height,
321                         orientation);
322         context.getPainter().paintScrollBarThumbBorder(context, g, thumbBounds.x,
323                         thumbBounds.y, thumbBounds.width, thumbBounds.height,
324                         orientation);
325     }
326 
327     /**
328      * A vertical scrollbar's preferred width is the maximum of
329      * preferred widths of the (non <code>null</code>)
330      * increment/decrement buttons,
331      * and the minimum width of the thumb. The preferred height is the
332      * sum of the preferred heights of the same parts.  The basis for
333      * the preferred size of a horizontal scrollbar is similar.
334      * <p>
335      * The <code>preferredSize</code> is only computed once, subsequent
336      * calls to this method just return a cached size.
337      *
338      * @param c the <code>JScrollBar</code> that's delegating this method to us
339      * @return the preferred size of a Basic JScrollBar
340      * @see #getMaximumSize
341      * @see #getMinimumSize
342      */
343     @Override
getPreferredSize(JComponent c)344     public Dimension getPreferredSize(JComponent c) {
345         Insets insets = c.getInsets();
346         return (scrollbar.getOrientation() == JScrollBar.VERTICAL)
347             ? new Dimension(scrollBarWidth + insets.left + insets.right, 48)
348             : new Dimension(48, scrollBarWidth + insets.top + insets.bottom);
349     }
350 
351     /**
352      * {@inheritDoc}
353      */
354     @Override
getMinimumThumbSize()355     protected Dimension getMinimumThumbSize() {
356         if (!validMinimumThumbSize) {
357             if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
358                 minimumThumbSize.width = scrollBarWidth;
359                 minimumThumbSize.height = 7;
360             } else {
361                 minimumThumbSize.width = 7;
362                 minimumThumbSize.height = scrollBarWidth;
363             }
364         }
365         return minimumThumbSize;
366     }
367 
368     /**
369      * {@inheritDoc}
370      */
371     @Override
createDecreaseButton(int orientation)372     protected JButton createDecreaseButton(int orientation)  {
373         @SuppressWarnings("serial") // anonymous class
374         SynthArrowButton synthArrowButton = new SynthArrowButton(orientation) {
375             @Override
376             public boolean contains(int x, int y) {
377                 if (decrGap < 0) { //there is an overlap between the track and button
378                     int width = getWidth();
379                     int height = getHeight();
380                     if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
381                         //adjust the height by decrGap
382                         //Note: decrGap is negative!
383                         height += decrGap;
384                     } else {
385                         //adjust the width by decrGap
386                         //Note: decrGap is negative!
387                         width += decrGap;
388                     }
389                     return (x >= 0) && (x < width) && (y >= 0) && (y < height);
390                 }
391                 return super.contains(x, y);
392             }
393         };
394         synthArrowButton.setName("ScrollBar.button");
395         return synthArrowButton;
396     }
397 
398     /**
399      * {@inheritDoc}
400      */
401     @Override
createIncreaseButton(int orientation)402     protected JButton createIncreaseButton(int orientation)  {
403         @SuppressWarnings("serial") // anonymous class
404         SynthArrowButton synthArrowButton = new SynthArrowButton(orientation) {
405             @Override
406             public boolean contains(int x, int y) {
407                 if (incrGap < 0) { //there is an overlap between the track and button
408                     int width = getWidth();
409                     int height = getHeight();
410                     if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
411                         //adjust the height and y by incrGap
412                         //Note: incrGap is negative!
413                         height += incrGap;
414                         y += incrGap;
415                     } else {
416                         //adjust the width and x by incrGap
417                         //Note: incrGap is negative!
418                         width += incrGap;
419                         x += incrGap;
420                     }
421                     return (x >= 0) && (x < width) && (y >= 0) && (y < height);
422                 }
423                 return super.contains(x, y);
424             }
425         };
426         synthArrowButton.setName("ScrollBar.button");
427         return synthArrowButton;
428     }
429 
430     /**
431      * {@inheritDoc}
432      */
433     @Override
setThumbRollover(boolean active)434     protected void setThumbRollover(boolean active) {
435         if (isThumbRollover() != active) {
436             scrollbar.repaint(getThumbBounds());
437             super.setThumbRollover(active);
438         }
439     }
440 
updateButtonDirections()441     private void updateButtonDirections() {
442         int orient = scrollbar.getOrientation();
443         if (scrollbar.getComponentOrientation().isLeftToRight()) {
444             ((SynthArrowButton)incrButton).setDirection(
445                         orient == HORIZONTAL? EAST : SOUTH);
446             ((SynthArrowButton)decrButton).setDirection(
447                         orient == HORIZONTAL? WEST : NORTH);
448         }
449         else {
450             ((SynthArrowButton)incrButton).setDirection(
451                         orient == HORIZONTAL? WEST : SOUTH);
452             ((SynthArrowButton)decrButton).setDirection(
453                         orient == HORIZONTAL ? EAST : NORTH);
454         }
455     }
456 
457     //
458     // PropertyChangeListener
459     //
propertyChange(PropertyChangeEvent e)460     public void propertyChange(PropertyChangeEvent e) {
461         String propertyName = e.getPropertyName();
462 
463         if (SynthLookAndFeel.shouldUpdateStyle(e)) {
464             updateStyle((JScrollBar)e.getSource());
465         }
466 
467         if ("orientation" == propertyName) {
468             updateButtonDirections();
469         }
470         else if ("componentOrientation" == propertyName) {
471             updateButtonDirections();
472         }
473     }
474 }
475