1 /*
2  * Copyright (c) 1997, 2018, 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 com.sun.java.swing.plaf.windows;
27 
28 import java.awt.*;
29 import java.awt.event.*;
30 import java.awt.image.*;
31 import java.lang.ref.*;
32 import java.util.*;
33 import javax.swing.plaf.basic.*;
34 import javax.swing.*;
35 import javax.swing.plaf.ComponentUI;
36 
37 import static com.sun.java.swing.plaf.windows.TMSchema.*;
38 import static com.sun.java.swing.plaf.windows.XPStyle.Skin;
39 
40 
41 /**
42  * Windows rendition of the component.
43  * <p>
44  * <strong>Warning:</strong>
45  * Serialized objects of this class will not be compatible with
46  * future Swing releases.  The current serialization support is appropriate
47  * for short term storage or RMI between applications running the same
48  * version of Swing.  A future release of Swing will provide support for
49  * long term persistence.
50  */
51 public class WindowsScrollBarUI extends BasicScrollBarUI {
52     private Grid thumbGrid;
53     private Grid highlightGrid;
54     private Dimension horizontalThumbSize;
55     private Dimension verticalThumbSize;
56 
57     /**
58      * Creates a UI for a JScrollBar.
59      *
60      * @param c the text field
61      * @return the UI
62      */
createUI(JComponent c)63     public static ComponentUI createUI(JComponent c) {
64         return new WindowsScrollBarUI();
65     }
66 
installDefaults()67     protected void installDefaults() {
68         super.installDefaults();
69 
70         XPStyle xp = XPStyle.getXP();
71         if (xp != null) {
72             scrollbar.setBorder(null);
73             horizontalThumbSize = getSize(scrollbar, xp, Part.SBP_THUMBBTNHORZ);
74             verticalThumbSize = getSize(scrollbar, xp, Part.SBP_THUMBBTNVERT);
75         } else {
76             horizontalThumbSize = null;
77             verticalThumbSize = null;
78         }
79     }
80 
getSize(Component component, XPStyle xp, Part part)81     private static Dimension getSize(Component component, XPStyle xp, Part part) {
82         Skin skin = xp.getSkin(component, part);
83         return new Dimension(skin.getWidth(), skin.getHeight());
84     }
85 
86     @Override
getMinimumThumbSize()87     protected Dimension getMinimumThumbSize() {
88         if ((horizontalThumbSize == null) || (verticalThumbSize == null)) {
89             return super.getMinimumThumbSize();
90         }
91         return JScrollBar.HORIZONTAL == scrollbar.getOrientation()
92                 ? horizontalThumbSize
93                 : verticalThumbSize;
94     }
95 
uninstallUI(JComponent c)96     public void uninstallUI(JComponent c) {
97         super.uninstallUI(c);
98         thumbGrid = highlightGrid = null;
99     }
100 
configureScrollBarColors()101     protected void configureScrollBarColors() {
102         super.configureScrollBarColors();
103         Color color = UIManager.getColor("ScrollBar.trackForeground");
104         if (color != null && trackColor != null) {
105             thumbGrid = Grid.getGrid(color, trackColor);
106         }
107 
108         color = UIManager.getColor("ScrollBar.trackHighlightForeground");
109         if (color != null && trackHighlightColor != null) {
110             highlightGrid = Grid.getGrid(color, trackHighlightColor);
111         }
112     }
113 
createDecreaseButton(int orientation)114     protected JButton createDecreaseButton(int orientation)  {
115         return new WindowsArrowButton(orientation,
116                                     UIManager.getColor("ScrollBar.thumb"),
117                                     UIManager.getColor("ScrollBar.thumbShadow"),
118                                     UIManager.getColor("ScrollBar.thumbDarkShadow"),
119                                     UIManager.getColor("ScrollBar.thumbHighlight"));
120     }
121 
createIncreaseButton(int orientation)122     protected JButton createIncreaseButton(int orientation)  {
123         return new WindowsArrowButton(orientation,
124                                     UIManager.getColor("ScrollBar.thumb"),
125                                     UIManager.getColor("ScrollBar.thumbShadow"),
126                                     UIManager.getColor("ScrollBar.thumbDarkShadow"),
127                                     UIManager.getColor("ScrollBar.thumbHighlight"));
128     }
129 
130     /**
131      * {@inheritDoc}
132      * @since 1.6
133      */
134     @Override
createArrowButtonListener()135     protected ArrowButtonListener createArrowButtonListener(){
136         // we need to repaint the entire scrollbar because state change for each
137         // button causes a state change for the thumb and other button on Vista
138         if(XPStyle.isVista()) {
139             return new ArrowButtonListener() {
140                 public void mouseEntered(MouseEvent evt) {
141                     repaint();
142                     super.mouseEntered(evt);
143                 }
144                 public void mouseExited(MouseEvent evt) {
145                     repaint();
146                     super.mouseExited(evt);
147                 }
148                 private void repaint() {
149                     scrollbar.repaint();
150                 }
151             };
152         } else {
153             return super.createArrowButtonListener();
154         }
155     }
156 
157     protected void paintTrack(Graphics g, JComponent c, Rectangle trackBounds){
158         boolean v = (scrollbar.getOrientation() == JScrollBar.VERTICAL);
159 
160         XPStyle xp = XPStyle.getXP();
161         if (xp != null) {
162             JScrollBar sb = (JScrollBar)c;
163             State state = State.NORMAL;
164             // Pending: Implement rollover (hot) and pressed
165             if (!sb.isEnabled()) {
166                 state = State.DISABLED;
167             }
168             Part part = v ? Part.SBP_LOWERTRACKVERT : Part.SBP_LOWERTRACKHORZ;
169             xp.getSkin(sb, part).paintSkin(g, trackBounds, state);
170         } else if (thumbGrid == null) {
171             super.paintTrack(g, c, trackBounds);
172         }
173         else {
174             thumbGrid.paint(g, trackBounds.x, trackBounds.y, trackBounds.width,
175                             trackBounds.height);
176             if (trackHighlight == DECREASE_HIGHLIGHT) {
177                 paintDecreaseHighlight(g);
178             }
179             else if (trackHighlight == INCREASE_HIGHLIGHT) {
180                 paintIncreaseHighlight(g);
181             }
182         }
183     }
184 
185     protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) {
186         boolean v = (scrollbar.getOrientation() == JScrollBar.VERTICAL);
187 
188         XPStyle xp = XPStyle.getXP();
189         if (xp != null) {
190             JScrollBar sb = (JScrollBar)c;
191             State state = State.NORMAL;
192             if (!sb.isEnabled()) {
193                 state = State.DISABLED;
194             } else if (isDragging) {
195                 state = State.PRESSED;
196             } else if (isThumbRollover()) {
197                 state = State.HOT;
198             } else if (XPStyle.isVista()) {
199                 if ((incrButton != null && incrButton.getModel().isRollover()) ||
200                     (decrButton != null && decrButton.getModel().isRollover())) {
201                     state = State.HOVER;
202                 }
203             }
204             // Paint thumb
205             Part thumbPart = v ? Part.SBP_THUMBBTNVERT : Part.SBP_THUMBBTNHORZ;
206             xp.getSkin(sb, thumbPart).paintSkin(g, thumbBounds, state);
207             // Paint gripper
208             Part gripperPart = v ? Part.SBP_GRIPPERVERT : Part.SBP_GRIPPERHORZ;
209             Skin skin = xp.getSkin(sb, gripperPart);
210             Insets gripperInsets = xp.getMargin(c, thumbPart, null, Prop.CONTENTMARGINS);
211             if (gripperInsets == null ||
212                 (v && (thumbBounds.height - gripperInsets.top -
213                        gripperInsets.bottom >= skin.getHeight())) ||
214                 (!v && (thumbBounds.width - gripperInsets.left -
215                         gripperInsets.right >= skin.getWidth()))) {
216                 skin.paintSkin(g,
217                                thumbBounds.x + (thumbBounds.width  - skin.getWidth()) / 2,
218                                thumbBounds.y + (thumbBounds.height - skin.getHeight()) / 2,
219                                skin.getWidth(), skin.getHeight(), state);
220             }
221         } else {
222             super.paintThumb(g, c, thumbBounds);
223         }
224     }
225 
226 
227     protected void paintDecreaseHighlight(Graphics g) {
228         if (highlightGrid == null) {
229             super.paintDecreaseHighlight(g);
230         }
231         else {
232             Insets insets = scrollbar.getInsets();
233             Rectangle thumbR = getThumbBounds();
234             int x, y, w, h;
235 
236             if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
237                 x = insets.left;
238                 y = decrButton.getY() + decrButton.getHeight();
239                 w = scrollbar.getWidth() - (insets.left + insets.right);
240                 h = thumbR.y - y;
241             }
242             else {
243                 x = decrButton.getX() + decrButton.getHeight();
244                 y = insets.top;
245                 w = thumbR.x - x;
246                 h = scrollbar.getHeight() - (insets.top + insets.bottom);
247             }
248             highlightGrid.paint(g, x, y, w, h);
249         }
250     }
251 
252 
253     protected void paintIncreaseHighlight(Graphics g) {
254         if (highlightGrid == null) {
255             super.paintDecreaseHighlight(g);
256         }
257         else {
258             Insets insets = scrollbar.getInsets();
259             Rectangle thumbR = getThumbBounds();
260             int x, y, w, h;
261 
262             if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
263                 x = insets.left;
264                 y = thumbR.y + thumbR.height;
265                 w = scrollbar.getWidth() - (insets.left + insets.right);
266                 h = incrButton.getY() - y;
267             }
268             else {
269                 x = thumbR.x + thumbR.width;
270                 y = insets.top;
271                 w = incrButton.getX() - x;
272                 h = scrollbar.getHeight() - (insets.top + insets.bottom);
273             }
274             highlightGrid.paint(g, x, y, w, h);
275         }
276     }
277 
278 
279     /**
280      * {@inheritDoc}
281      * @since 1.6
282      */
283     @Override
284     protected void setThumbRollover(boolean active) {
285         boolean old = isThumbRollover();
286         super.setThumbRollover(active);
287         // we need to repaint the entire scrollbar because state change for thumb
288         // causes state change for incr and decr buttons on Vista
289         if(XPStyle.isVista() && active != old) {
290             scrollbar.repaint();
291         }
292     }
293 
294     /**
295      * WindowsArrowButton is used for the buttons to position the
296      * document up/down. It differs from BasicArrowButton in that the
297      * preferred size is always a square.
298      */
299     @SuppressWarnings("serial") // Superclass is not serializable across versions
300     private class WindowsArrowButton extends BasicArrowButton {
301 
302         public WindowsArrowButton(int direction, Color background, Color shadow,
303                          Color darkShadow, Color highlight) {
304             super(direction, background, shadow, darkShadow, highlight);
305         }
306 
307         public WindowsArrowButton(int direction) {
308             super(direction);
309         }
310 
311         public void paint(Graphics g) {
312             XPStyle xp = XPStyle.getXP();
313             if (xp != null) {
314                 ButtonModel model = getModel();
315                 Skin skin = xp.getSkin(this, Part.SBP_ARROWBTN);
316                 State state = null;
317 
318                 boolean jointRollover = XPStyle.isVista() && (isThumbRollover() ||
319                     (this == incrButton && decrButton.getModel().isRollover()) ||
320                     (this == decrButton && incrButton.getModel().isRollover()));
321 
322                 // normal, rollover, pressed, disabled
323                 if (model.isArmed() && model.isPressed()) {
324                     switch (direction) {
325                         case NORTH: state = State.UPPRESSED;    break;
326                         case SOUTH: state = State.DOWNPRESSED;  break;
327                         case WEST:  state = State.LEFTPRESSED;  break;
328                         case EAST:  state = State.RIGHTPRESSED; break;
329                     }
330                 } else if (!model.isEnabled()) {
331                     switch (direction) {
332                         case NORTH: state = State.UPDISABLED;    break;
333                         case SOUTH: state = State.DOWNDISABLED;  break;
334                         case WEST:  state = State.LEFTDISABLED;  break;
335                         case EAST:  state = State.RIGHTDISABLED; break;
336                     }
337                 } else if (model.isRollover() || model.isPressed()) {
338                     switch (direction) {
339                         case NORTH: state = State.UPHOT;    break;
340                         case SOUTH: state = State.DOWNHOT;  break;
341                         case WEST:  state = State.LEFTHOT;  break;
342                         case EAST:  state = State.RIGHTHOT; break;
343                     }
344                 } else if (jointRollover) {
345                     switch (direction) {
346                         case NORTH: state = State.UPHOVER;    break;
347                         case SOUTH: state = State.DOWNHOVER;  break;
348                         case WEST:  state = State.LEFTHOVER;  break;
349                         case EAST:  state = State.RIGHTHOVER; break;
350                     }
351                 } else {
352                     switch (direction) {
353                         case NORTH: state = State.UPNORMAL;    break;
354                         case SOUTH: state = State.DOWNNORMAL;  break;
355                         case WEST:  state = State.LEFTNORMAL;  break;
356                         case EAST:  state = State.RIGHTNORMAL; break;
357                     }
358                 }
359 
360                 skin.paintSkin(g, 0, 0, getWidth(), getHeight(), state);
361             } else {
362                 super.paint(g);
363             }
364         }
365 
366         public Dimension getPreferredSize() {
367             int size = 16;
368             if (scrollbar != null) {
369                 switch (scrollbar.getOrientation()) {
370                 case JScrollBar.VERTICAL:
371                     size = scrollbar.getWidth();
372                     break;
373                 case JScrollBar.HORIZONTAL:
374                     size = scrollbar.getHeight();
375                     break;
376                 }
377                 size = Math.max(size, 5);
378             }
379             return new Dimension(size, size);
380         }
381     }
382 
383 
384     /**
385      * This should be pulled out into its own class if more classes need to
386      * use it.
387      * <p>
388      * Grid is used to draw the track for windows scrollbars. Grids
389      * are cached in a HashMap, with the key being the rgb components
390      * of the foreground/background colors. Further the Grid is held through
391      * a WeakRef so that it can be freed when no longer needed. As the
392      * Grid is rather expensive to draw, it is drawn in a BufferedImage.
393      */
394     private static class Grid {
395         private static final int BUFFER_SIZE = 64;
396         private static HashMap<String, WeakReference<Grid>> map;
397 
398         private BufferedImage image;
399 
400         static {
401             map = new HashMap<String, WeakReference<Grid>>();
402         }
403 
404         public static Grid getGrid(Color fg, Color bg) {
405             String key = fg.getRGB() + " " + bg.getRGB();
406             WeakReference<Grid> ref = map.get(key);
407             Grid grid = (ref == null) ? null : ref.get();
408             if (grid == null) {
409                 grid = new Grid(fg, bg);
410                 map.put(key, new WeakReference<Grid>(grid));
411             }
412             return grid;
413         }
414 
415         public Grid(Color fg, Color bg) {
416             int[] cmap = { fg.getRGB(), bg.getRGB() };
417             IndexColorModel icm = new IndexColorModel(8, 2, cmap, 0, false, -1,
418                                                       DataBuffer.TYPE_BYTE);
419             image = new BufferedImage(BUFFER_SIZE, BUFFER_SIZE,
420                                       BufferedImage.TYPE_BYTE_INDEXED, icm);
421             Graphics g = image.getGraphics();
422             try {
423                 g.setClip(0, 0, BUFFER_SIZE, BUFFER_SIZE);
424                 paintGrid(g, fg, bg);
425             }
426             finally {
427                 g.dispose();
428             }
429         }
430 
431         /**
432          * Paints the grid into the specified Graphics at the specified
433          * location.
434          */
435         public void paint(Graphics g, int x, int y, int w, int h) {
436             Rectangle clipRect = g.getClipBounds();
437             int minX = Math.max(x, clipRect.x);
438             int minY = Math.max(y, clipRect.y);
439             int maxX = Math.min(clipRect.x + clipRect.width, x + w);
440             int maxY = Math.min(clipRect.y + clipRect.height, y + h);
441 
442             if (maxX <= minX || maxY <= minY) {
443                 return;
444             }
445             int xOffset = (minX - x) % 2;
446             for (int xCounter = minX; xCounter < maxX;
447                  xCounter += BUFFER_SIZE) {
448                 int yOffset = (minY - y) % 2;
449                 int width = Math.min(BUFFER_SIZE - xOffset,
450                                      maxX - xCounter);
451 
452                 for (int yCounter = minY; yCounter < maxY;
453                      yCounter += BUFFER_SIZE) {
454                     int height = Math.min(BUFFER_SIZE - yOffset,
455                                           maxY - yCounter);
456 
457                     g.drawImage(image, xCounter, yCounter,
458                                 xCounter + width, yCounter + height,
459                                 xOffset, yOffset,
460                                 xOffset + width, yOffset + height, null);
461                     if (yOffset != 0) {
462                         yCounter -= yOffset;
463                         yOffset = 0;
464                     }
465                 }
466                 if (xOffset != 0) {
467                     xCounter -= xOffset;
468                     xOffset = 0;
469                 }
470             }
471         }
472 
473         /**
474          * Actually renders the grid into the Graphics <code>g</code>.
475          */
476         private void paintGrid(Graphics g, Color fg, Color bg) {
477             Rectangle clipRect = g.getClipBounds();
478             g.setColor(bg);
479             g.fillRect(clipRect.x, clipRect.y, clipRect.width,
480                        clipRect.height);
481             g.setColor(fg);
482             g.translate(clipRect.x, clipRect.y);
483             int width = clipRect.width;
484             int height = clipRect.height;
485             int xCounter = clipRect.x % 2;
486             for (int end = width - height; xCounter < end; xCounter += 2) {
487                 g.drawLine(xCounter, 0, xCounter + height, height);
488             }
489             for (int end = width; xCounter < end; xCounter += 2) {
490                 g.drawLine(xCounter, 0, width, width - xCounter);
491             }
492 
493             int yCounter = ((clipRect.x % 2) == 0) ? 2 : 1;
494             for (int end = height - width; yCounter < end; yCounter += 2) {
495                 g.drawLine(0, yCounter, width, yCounter + width);
496             }
497             for (int end = height; yCounter < end; yCounter += 2) {
498                 g.drawLine(0, yCounter, height - yCounter, height);
499             }
500             g.translate(-clipRect.x, -clipRect.y);
501         }
502     }
503 }
504