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