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