1 /* 2 * Copyright (c) 2011, 2015, 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.javafx.webkit.theme; 27 28 import java.lang.ref.WeakReference; 29 import java.util.logging.Level; 30 import java.util.logging.Logger; 31 32 import com.sun.javafx.util.Utils; 33 import javafx.beans.Observable; 34 import javafx.geometry.Orientation; 35 import javafx.scene.Node; 36 import javafx.scene.control.Control; 37 import javafx.scene.control.ScrollBar; 38 39 import com.sun.javafx.scene.control.skin.ScrollBarSkin; 40 import com.sun.webkit.graphics.Ref; 41 import com.sun.webkit.graphics.ScrollBarTheme; 42 import com.sun.webkit.graphics.WCGraphicsContext; 43 import com.sun.javafx.webkit.Accessor; 44 import com.sun.javafx.webkit.theme.RenderThemeImpl.Pool; 45 import com.sun.javafx.webkit.theme.RenderThemeImpl.ViewListener; 46 import com.sun.javafx.webkit.theme.RenderThemeImpl.Widget; 47 import com.sun.javafx.webkit.theme.RenderThemeImpl.WidgetType; 48 49 public final class ScrollBarThemeImpl extends ScrollBarTheme { 50 51 private final static Logger log = Logger.getLogger(ScrollBarThemeImpl.class.getName()); 52 53 private WeakReference<ScrollBar> testSBRef = // used for hit testing 54 new WeakReference<ScrollBar>(null); 55 56 private boolean thicknessInitialized = false; 57 58 private final Accessor accessor; 59 60 private final Pool<ScrollBarWidget> pool; 61 62 private final class ScrollBarWidget extends ScrollBar implements Widget { 63 private ScrollBarWidget() { 64 setOrientation(Orientation.VERTICAL); 65 setMin(0); 66 setManaged(false); 67 } 68 69 @Override public void impl_updatePeer() { 70 super.impl_updatePeer(); 71 initializeThickness(); 72 } 73 74 @Override public WidgetType getType() { return WidgetType.SCROLLBAR; } 75 76 @Override protected void layoutChildren() { 77 super.layoutChildren(); 78 initializeThickness(); 79 } 80 } 81 82 private static final class ScrollBarRef extends Ref { 83 private final WeakReference<ScrollBarWidget> sbRef; 84 85 private ScrollBarRef(ScrollBarWidget sb) { 86 this.sbRef = new WeakReference<ScrollBarWidget>(sb); 87 } 88 89 private Control asControl() { 90 return sbRef.get(); 91 } 92 } 93 94 /* 95 * Note, the class should be instantiated no later than 96 * the appropriate page is created to ensure 'testSB' 97 * is added to the view before paiting starts. 98 */ 99 public ScrollBarThemeImpl(final Accessor accessor) { 100 this.accessor = accessor; 101 pool = new Pool<ScrollBarWidget>( 102 sb -> { 103 accessor.removeChild(sb); 104 }, ScrollBarWidget.class); 105 accessor.addViewListener(new ViewListener(pool, accessor) { 106 @Override public void invalidated(Observable ov) { 107 super.invalidated(ov); 108 ScrollBar testSB = new ScrollBarWidget(); 109 // testSB should be added to the new WebView (if any) 110 accessor.addChild(testSB); 111 testSBRef = new WeakReference<ScrollBar>(testSB); 112 } 113 }); 114 115 } 116 117 private static Orientation convertOrientation(int orientation) { 118 return orientation == VERTICAL_SCROLLBAR ? Orientation.VERTICAL : Orientation.HORIZONTAL; 119 } 120 121 private void adjustScrollBar(ScrollBar sb, int w, int h, int orientation) { 122 Orientation current = convertOrientation(orientation); 123 if (current != sb.getOrientation()) { 124 sb.setOrientation(current); 125 } 126 127 if (current == Orientation.VERTICAL) { 128 w = ScrollBarTheme.getThickness(); 129 } else { 130 h = ScrollBarTheme.getThickness(); 131 } 132 133 if ((w != sb.getWidth()) || (h != sb.getHeight())) { 134 sb.resize(w, h); 135 } 136 } 137 138 private void adjustScrollBar(ScrollBar sb, int w, int h, int orientation, 139 int value, int visibleSize, int totalSize) 140 { 141 adjustScrollBar(sb, w, h, orientation); 142 boolean disable = totalSize <= visibleSize; 143 sb.setDisable(disable); 144 if (disable) { 145 return; 146 } 147 if (value < 0) { 148 value = 0; 149 } else if(value > (totalSize - visibleSize)) { 150 value = totalSize - visibleSize; 151 } 152 153 if (sb.getMax() != totalSize || sb.getVisibleAmount() != visibleSize) { 154 sb.setValue(0); // reset 'value' to let 'max' & 'visibleAmount' be reinitialized 155 sb.setMax(totalSize); 156 sb.setVisibleAmount(visibleSize); 157 } 158 159 // For FX ScrollBar the following is true: 160 // [min <= value <= max] & [min <= visibleAmount <= max] 161 // But webkit assumes that: 162 // [0 <= value <= totalSize - visibleAmount] 163 // So, we calculate a factor from the following equation: 164 // (totalSize - visibleSize) * factor = totalSize 165 if (totalSize > visibleSize) { 166 float factor = ((float)totalSize) / (totalSize - visibleSize); 167 if (sb.getValue() != value * factor) { 168 sb.setValue(value * factor); // eventually set 'value' 169 } 170 } 171 } 172 173 @Override protected Ref createWidget(long id, int w, int h, int orientation, 174 int value, int visibleSize, 175 int totalSize) 176 { 177 ScrollBarWidget sb = pool.get(id); 178 if (sb == null) { 179 sb = new ScrollBarWidget(); 180 pool.put(id, sb); 181 accessor.addChild(sb); 182 } 183 adjustScrollBar(sb, w, h, orientation, value, visibleSize, totalSize); 184 185 return new ScrollBarRef(sb); 186 } 187 188 @Override public void paint(WCGraphicsContext g, Ref sbRef, 189 int x, int y, int pressedPart, int hoveredPart) 190 { 191 ScrollBar sb = (ScrollBar)((ScrollBarRef)sbRef).asControl(); 192 if (sb == null) { 193 return; 194 } 195 196 if (log.isLoggable(Level.FINEST)) { 197 log.log(Level.FINEST, "[{0}, {1} {2}x{3}], {4}", 198 new Object[] {x, y, sb.getWidth(), sb.getHeight(), 199 sb.getOrientation() == Orientation.VERTICAL ? "VERTICAL" : "HORIZONTAL"}); 200 } 201 g.saveState(); 202 g.translate(x, y); 203 Renderer.getRenderer().render(sb, g); 204 g.restoreState(); 205 } 206 207 @Override protected int hitTest(int w, int h, int orientation, int value, 208 int visibleSize, int totalSize, 209 int x, int y) 210 { 211 if (log.isLoggable(Level.FINEST)) { 212 log.log(Level.FINEST, "[{0}, {1} {2}x{3}], {4}", 213 new Object[] {x, y, w, h, orientation == VERTICAL_SCROLLBAR ? 214 "VERTICAL" : "HORIZONTAL"}); 215 } 216 217 ScrollBar testSB = testSBRef.get(); 218 if (testSB == null) { 219 return NO_PART; 220 } 221 Node thumb = getThumb(testSB); 222 Node track = getTrack(testSB); 223 Node decButton = getDecButton(testSB); 224 Node incButton = getIncButton(testSB); 225 226 adjustScrollBar(testSB, w, h, orientation, value, visibleSize, totalSize); 227 228 int trackX; 229 int trackY; 230 int incBtnX; 231 int incBtnY; 232 int thumbX; 233 int thumbY; 234 235 if (orientation == VERTICAL_SCROLLBAR) { 236 trackX = incBtnX = thumbX = x; 237 trackY = y - (int)decButton.getLayoutBounds().getHeight(); 238 thumbY = trackY - thumbPosition(); 239 incBtnY = trackY - (int)track.getLayoutBounds().getHeight(); 240 } else { 241 trackY = incBtnY = thumbY = y; 242 trackX = x - (int)decButton.getLayoutBounds().getWidth(); 243 thumbX = trackX - thumbPosition(); 244 incBtnX = trackX - (int)track.getLayoutBounds().getWidth(); 245 } 246 247 if (thumb != null && thumb.isVisible() && thumb.contains(thumbX, thumbY)) { 248 log.finer("thumb"); 249 return THUMB_PART; 250 251 } else if (track != null && track.isVisible() && track.contains(trackX, trackY)) { 252 253 if ((orientation == VERTICAL_SCROLLBAR && thumbPosition() >= trackY) || 254 (orientation == HORIZONTAL_SCROLLBAR && thumbPosition() >= trackX)) 255 { 256 log.finer("back track"); 257 return BACK_TRACK_PART; 258 259 } else if ((orientation == VERTICAL_SCROLLBAR && thumbPosition() < trackY) || 260 (orientation == HORIZONTAL_SCROLLBAR && thumbPosition() < trackX)) 261 { 262 log.finer("forward track"); 263 return FORWARD_TRACK_PART; 264 } 265 } else if (decButton != null && decButton.isVisible() && decButton.contains(x, y)) { 266 log.finer("back button"); 267 return BACK_BUTTON_START_PART; 268 269 } else if (incButton != null && incButton.isVisible() && incButton.contains(incBtnX, incBtnY)) { 270 log.finer("forward button"); 271 return FORWARD_BUTTON_START_PART; 272 } 273 274 log.finer("no part"); 275 return NO_PART; 276 } 277 278 private int thumbPosition() { 279 ScrollBar testSB = testSBRef.get(); 280 if (testSB == null) { 281 return 0; 282 } 283 // position calculated after ScrollBarSkin.positionThumb() 284 Node thumb = getThumb(testSB); 285 if (thumb == null) { 286 return 0; 287 } 288 double thumbLength = testSB.getOrientation() == Orientation.VERTICAL 289 ? thumb.getLayoutBounds().getHeight() 290 : thumb.getLayoutBounds().getWidth(); 291 292 Node track = getTrack(testSB); 293 double trackLength = testSB.getOrientation() == Orientation.VERTICAL 294 ? track.getLayoutBounds().getHeight() 295 : track.getLayoutBounds().getWidth(); 296 297 double clampedValue = Utils.clamp(testSB.getMin(), testSB.getValue(), testSB.getMax()); 298 double range = testSB.getMax() - testSB.getMin(); 299 return (int) Math.round((range > 0) 300 ? ((trackLength - thumbLength) * (clampedValue - testSB.getMin()) / range) 301 : 0); 302 } 303 304 @Override protected int getThumbLength(int w, int h, int orientation, 305 int value, 306 int visibleSize, int totalSize) 307 { 308 ScrollBar testSB = testSBRef.get(); 309 if (testSB == null) { 310 return 0; 311 } 312 Node thumb = getThumb(testSB); 313 if (thumb == null) { 314 return 0; 315 } 316 adjustScrollBar(testSB, w, h, orientation, value, visibleSize, totalSize); 317 318 double len = 0; 319 if (orientation == VERTICAL_SCROLLBAR) { 320 len = thumb.getLayoutBounds().getHeight(); 321 } else { 322 len = thumb.getLayoutBounds().getWidth(); 323 } 324 log.log(Level.FINEST, "thumb length: {0}", len); 325 return (int)len; 326 } 327 328 @Override protected int getTrackPosition(int w, int h, int orientation) { 329 ScrollBar testSB = testSBRef.get(); 330 if (testSB == null) { 331 return 0; 332 } 333 Node decButton = getDecButton(testSB); 334 if (decButton == null) { 335 return 0; 336 } 337 adjustScrollBar(testSB, w, h, orientation); 338 339 double pos = 0; 340 if (orientation == VERTICAL_SCROLLBAR) { 341 pos = decButton.getLayoutBounds().getHeight(); 342 } else { 343 pos = decButton.getLayoutBounds().getWidth(); 344 } 345 log.log(Level.FINEST, "track position: {0}", pos); 346 return (int)pos; 347 } 348 349 @Override protected int getTrackLength(int w, int h, int orientation) { 350 ScrollBar testSB = testSBRef.get(); 351 if (testSB == null) { 352 return 0; 353 } 354 Node track = getTrack(testSB); 355 if (track == null) { 356 return 0; 357 } 358 adjustScrollBar(testSB, w, h, orientation); 359 360 double len = 0; 361 if (orientation == VERTICAL_SCROLLBAR) { 362 len = track.getLayoutBounds().getHeight(); 363 } else { 364 len = track.getLayoutBounds().getWidth(); 365 } 366 log.log(Level.FINEST, "track length: {0}", len); 367 return (int)len; 368 } 369 370 @Override protected int getThumbPosition(int w, int h, int orientation, 371 int value, 372 int visibleSize, int totalSize) 373 { 374 ScrollBar testSB = testSBRef.get(); 375 if (testSB == null) { 376 return 0; 377 } 378 adjustScrollBar(testSB, w, h, orientation, value, visibleSize, totalSize); 379 380 int pos = thumbPosition(); 381 log.log(Level.FINEST, "thumb position: {0}", pos); 382 return pos; 383 } 384 385 private void initializeThickness() { 386 if (!thicknessInitialized) { 387 ScrollBar testSB = testSBRef.get(); 388 if (testSB == null) { 389 return; 390 } 391 int thickness = (int) testSB.prefWidth(-1); 392 if (thickness != 0 && ScrollBarTheme.getThickness() != thickness) { 393 ScrollBarTheme.setThickness(thickness); 394 } 395 thicknessInitialized = true; 396 } 397 } 398 399 private static Node getThumb(ScrollBar scrollBar) { 400 return ((ScrollBarSkin)scrollBar.getSkin()).getThumb(); 401 } 402 403 private static Node getTrack(ScrollBar scrollBar) { 404 return ((ScrollBarSkin)scrollBar.getSkin()).getTrack(); 405 } 406 407 private static Node getIncButton(ScrollBar scrollBar) { 408 return ((ScrollBarSkin)scrollBar.getSkin()).getIncButton(); 409 } 410 411 private static Node getDecButton(ScrollBar scrollBar) { 412 return ((ScrollBarSkin)scrollBar.getSkin()).getDecButton(); 413 } 414 }