1 /*
2  * Copyright (c) 1997, 2008, 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     private class WindowsArrowButton extends BasicArrowButton {
300 
301         public WindowsArrowButton(int direction, Color background, Color shadow,
302                          Color darkShadow, Color highlight) {
303             super(direction, background, shadow, darkShadow, highlight);
304         }
305 
306         public WindowsArrowButton(int direction) {
307             super(direction);
308         }
309 
310         public void paint(Graphics g) {
311             XPStyle xp = XPStyle.getXP();
312             if (xp != null) {
313                 ButtonModel model = getModel();
314                 Skin skin = xp.getSkin(this, Part.SBP_ARROWBTN);
315                 State state = null;
316 
317                 boolean jointRollover = XPStyle.isVista() && (isThumbRollover() ||
318                     (this == incrButton && decrButton.getModel().isRollover()) ||
319                     (this == decrButton && incrButton.getModel().isRollover()));
320 
321                 // normal, rollover, pressed, disabled
322                 if (model.isArmed() && model.isPressed()) {
323                     switch (direction) {
324                         case NORTH: state = State.UPPRESSED;    break;
325                         case SOUTH: state = State.DOWNPRESSED;  break;
326                         case WEST:  state = State.LEFTPRESSED;  break;
327                         case EAST:  state = State.RIGHTPRESSED; break;
328                     }
329                 } else if (!model.isEnabled()) {
330                     switch (direction) {
331                         case NORTH: state = State.UPDISABLED;    break;
332                         case SOUTH: state = State.DOWNDISABLED;  break;
333                         case WEST:  state = State.LEFTDISABLED;  break;
334                         case EAST:  state = State.RIGHTDISABLED; break;
335                     }
336                 } else if (model.isRollover() || model.isPressed()) {
337                     switch (direction) {
338                         case NORTH: state = State.UPHOT;    break;
339                         case SOUTH: state = State.DOWNHOT;  break;
340                         case WEST:  state = State.LEFTHOT;  break;
341                         case EAST:  state = State.RIGHTHOT; break;
342                     }
343                 } else if (jointRollover) {
344                     switch (direction) {
345                         case NORTH: state = State.UPHOVER;    break;
346                         case SOUTH: state = State.DOWNHOVER;  break;
347                         case WEST:  state = State.LEFTHOVER;  break;
348                         case EAST:  state = State.RIGHTHOVER; break;
349                     }
350                 } else {
351                     switch (direction) {
352                         case NORTH: state = State.UPNORMAL;    break;
353                         case SOUTH: state = State.DOWNNORMAL;  break;
354                         case WEST:  state = State.LEFTNORMAL;  break;
355                         case EAST:  state = State.RIGHTNORMAL; break;
356                     }
357                 }
358 
359                 skin.paintSkin(g, 0, 0, getWidth(), getHeight(), state);
360             } else {
361                 super.paint(g);
362             }
363         }
364 
365         public Dimension getPreferredSize() {
366             int size = 16;
367             if (scrollbar != null) {
368                 switch (scrollbar.getOrientation()) {
369                 case JScrollBar.VERTICAL:
370                     size = scrollbar.getWidth();
371                     break;
372                 case JScrollBar.HORIZONTAL:
373                     size = scrollbar.getHeight();
374                     break;
375                 }
376                 size = Math.max(size, 5);
377             }
378             return new Dimension(size, size);
379         }
380     }
381 
382 
383     /**
384      * This should be pulled out into its own class if more classes need to
385      * use it.
386      * <p>
387      * Grid is used to draw the track for windows scrollbars. Grids
388      * are cached in a HashMap, with the key being the rgb components
389      * of the foreground/background colors. Further the Grid is held through
390      * a WeakRef so that it can be freed when no longer needed. As the
391      * Grid is rather expensive to draw, it is drawn in a BufferedImage.
392      */
393     private static class Grid {
394         private static final int BUFFER_SIZE = 64;
395         private static HashMap<String, WeakReference<Grid>> map;
396 
397         private BufferedImage image;
398 
399         static {
400             map = new HashMap<String, WeakReference<Grid>>();
401         }
402 
403         public static Grid getGrid(Color fg, Color bg) {
404             String key = fg.getRGB() + " " + bg.getRGB();
405             WeakReference<Grid> ref = map.get(key);
406             Grid grid = (ref == null) ? null : ref.get();
407             if (grid == null) {
408                 grid = new Grid(fg, bg);
409                 map.put(key, new WeakReference<Grid>(grid));
410             }
411             return grid;
412         }
413 
414         public Grid(Color fg, Color bg) {
415             int cmap[] = { fg.getRGB(), bg.getRGB() };
416             IndexColorModel icm = new IndexColorModel(8, 2, cmap, 0, false, -1,
417                                                       DataBuffer.TYPE_BYTE);
418             image = new BufferedImage(BUFFER_SIZE, BUFFER_SIZE,
419                                       BufferedImage.TYPE_BYTE_INDEXED, icm);
420             Graphics g = image.getGraphics();
421             try {
422                 g.setClip(0, 0, BUFFER_SIZE, BUFFER_SIZE);
423                 paintGrid(g, fg, bg);
424             }
425             finally {
426                 g.dispose();
427             }
428         }
429 
430         /**
431          * Paints the grid into the specified Graphics at the specified
432          * location.
433          */
434         public void paint(Graphics g, int x, int y, int w, int h) {
435             Rectangle clipRect = g.getClipBounds();
436             int minX = Math.max(x, clipRect.x);
437             int minY = Math.max(y, clipRect.y);
438             int maxX = Math.min(clipRect.x + clipRect.width, x + w);
439             int maxY = Math.min(clipRect.y + clipRect.height, y + h);
440 
441             if (maxX <= minX || maxY <= minY) {
442                 return;
443             }
444             int xOffset = (minX - x) % 2;
445             for (int xCounter = minX; xCounter < maxX;
446                  xCounter += BUFFER_SIZE) {
447                 int yOffset = (minY - y) % 2;
448                 int width = Math.min(BUFFER_SIZE - xOffset,
449                                      maxX - xCounter);
450 
451                 for (int yCounter = minY; yCounter < maxY;
452                      yCounter += BUFFER_SIZE) {
453                     int height = Math.min(BUFFER_SIZE - yOffset,
454                                           maxY - yCounter);
455 
456                     g.drawImage(image, xCounter, yCounter,
457                                 xCounter + width, yCounter + height,
458                                 xOffset, yOffset,
459                                 xOffset + width, yOffset + height, null);
460                     if (yOffset != 0) {
461                         yCounter -= yOffset;
462                         yOffset = 0;
463                     }
464                 }
465                 if (xOffset != 0) {
466                     xCounter -= xOffset;
467                     xOffset = 0;
468                 }
469             }
470         }
471 
472         /**
473          * Actually renders the grid into the Graphics <code>g</code>.
474          */
475         private void paintGrid(Graphics g, Color fg, Color bg) {
476             Rectangle clipRect = g.getClipBounds();
477             g.setColor(bg);
478             g.fillRect(clipRect.x, clipRect.y, clipRect.width,
479                        clipRect.height);
480             g.setColor(fg);
481             g.translate(clipRect.x, clipRect.y);
482             int width = clipRect.width;
483             int height = clipRect.height;
484             int xCounter = clipRect.x % 2;
485             for (int end = width - height; xCounter < end; xCounter += 2) {
486                 g.drawLine(xCounter, 0, xCounter + height, height);
487             }
488             for (int end = width; xCounter < end; xCounter += 2) {
489                 g.drawLine(xCounter, 0, width, width - xCounter);
490             }
491 
492             int yCounter = ((clipRect.x % 2) == 0) ? 2 : 1;
493             for (int end = height - width; yCounter < end; yCounter += 2) {
494                 g.drawLine(0, yCounter, width, yCounter + width);
495             }
496             for (int end = height; yCounter < end; yCounter += 2) {
497                 g.drawLine(0, yCounter, height - yCounter, height);
498             }
499             g.translate(-clipRect.x, -clipRect.y);
500         }
501     }
502 }
503