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