Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.scene.control; 027 028import javafx.css.CssMetaData; 029import javafx.css.PseudoClass; 030import java.util.Collections; 031import java.util.List; 032import javafx.beans.InvalidationListener; 033import javafx.beans.Observable; 034import javafx.collections.ObservableList; 035import javafx.css.Styleable; 036import javafx.event.EventHandler; 037import javafx.geometry.HPos; 038import javafx.geometry.Insets; 039import javafx.geometry.VPos; 040import javafx.scene.Node; 041import javafx.scene.input.MouseEvent; 042import javafx.scene.layout.Region; 043 044/** 045 * 046 */ 047public abstract class SkinBase<C extends Control> implements Skin<C> { 048 049 /*************************************************************************** 050 * * 051 * Private fields * 052 * * 053 **************************************************************************/ 054 055 /** 056 * The {@code Control} that is referencing this Skin. There is a 057 * one-to-one relationship between a {@code Skin} and a {@code Control}. 058 * When a {@code Skin} is set on a {@code Control}, this variable is 059 * automatically updated. 060 */ 061 private C control; 062 063 /** 064 * A local field that directly refers to the children list inside the Control. 065 */ 066 private ObservableList<Node> children; 067 068 069 070 /*************************************************************************** 071 * * 072 * Event Handlers / Listeners * 073 * * 074 **************************************************************************/ 075 076 /** 077 * Mouse handler used for consuming all mouse events (preventing them 078 * from bubbling up to parent) 079 */ 080 private static final EventHandler<MouseEvent> mouseEventConsumer = new EventHandler<MouseEvent>() { 081 @Override public void handle(MouseEvent event) { 082 /* 083 ** we used to consume mouse wheel rotations here, 084 ** be we've switched to ScrollEvents, and only consume those which we use. 085 ** See RT-13995 & RT-14480 086 */ 087 event.consume(); 088 } 089 }; 090 091 092 093 /*************************************************************************** 094 * * 095 * Constructor * 096 * * 097 **************************************************************************/ 098 099 /** 100 * Constructor for all SkinBase instances. 101 * 102 * @param control The control for which this Skin should attach to. 103 */ 104 protected SkinBase(final C control) { 105 if (control == null) { 106 throw new IllegalArgumentException("Cannot pass null for control"); 107 } 108 109 // Update the control and behavior 110 this.control = control; 111 this.children = control.getControlChildren(); 112 113 // Default behavior for controls is to consume all mouse events 114 consumeMouseEvents(true); 115 116 // RT-28337: request layout on prefWidth / prefHeight changes 117 InvalidationListener prefSizeListener = new InvalidationListener() { 118 @Override public void invalidated(Observable o) { 119 control.requestLayout(); 120 } 121 }; 122 this.control.prefWidthProperty().addListener(prefSizeListener); 123 this.control.prefHeightProperty().addListener(prefSizeListener); 124 } 125 126 127 128 /*************************************************************************** 129 * * 130 * Public API (from Skin) * 131 * * 132 **************************************************************************/ 133 134 /** {@inheritDoc} */ 135 @Override public final C getSkinnable() { 136 return control; 137 } 138 139 /** {@inheritDoc} */ 140 @Override public final Node getNode() { 141 return control; 142 } 143 144 /** {@inheritDoc} */ 145 @Override public void dispose() { 146// control.removeEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, contextMenuHandler); 147 148 this.control = null; 149 } 150 151 152 153 /*************************************************************************** 154 * * 155 * Public API * 156 * * 157 **************************************************************************/ 158 159 /** 160 * Returns the children of the skin. 161 */ 162 public final ObservableList<Node> getChildren() { 163 return children; 164 } 165 166 /** 167 * Called during the layout pass of the scenegraph. 168 */ 169 protected void layoutChildren(final double contentX, final double contentY, 170 final double contentWidth, final double contentHeight) { 171 // By default simply sizes all children to fit within the space provided 172 for (int i=0, max=children.size(); i<max; i++) { 173 Node child = children.get(i); 174 layoutInArea(child, contentX, contentY, contentWidth, contentHeight, -1, HPos.CENTER, VPos.CENTER); 175 } 176 } 177 178 /** 179 * Determines whether all mouse events should be automatically consumed. 180 */ 181 protected final void consumeMouseEvents(boolean value) { 182 if (value) { 183 control.addEventHandler(MouseEvent.ANY, mouseEventConsumer); 184 } else { 185 control.removeEventHandler(MouseEvent.ANY, mouseEventConsumer); 186 } 187 } 188 189 190 191 /*************************************************************************** 192 * * 193 * Public Layout-related API * 194 * * 195 **************************************************************************/ 196 197 /** 198 * Computes the minimum allowable width of the Skin, based on the provided 199 * height. 200 * 201 * @param height The height of the Skin, in case this value might dictate 202 * the minimum width. 203 * @param topInset the pixel snapped top inset 204 * @param rightInset the pixel snapped right inset 205 * @param bottomInset the pixel snapped bottom inset 206 * @param leftInset the pixel snapped left inset 207 * @return A double representing the minimum width of this Skin. 208 */ 209 protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { 210 double minX = 0; 211 double maxX = 0; 212 for (int i = 0; i < children.size(); i++) { 213 Node node = children.get(i); 214 if (node.isManaged()) { 215 final double x = node.getLayoutBounds().getMinX() + node.getLayoutX(); 216 minX = Math.min(minX, x); 217 maxX = Math.max(maxX, x + node.minWidth(-1)); 218 } 219 } 220 double minWidth = maxX - minX; 221 return leftInset + minWidth + rightInset; 222 } 223 224 /** 225 * Computes the minimum allowable height of the Skin, based on the provided 226 * width. 227 * 228 * @param width The width of the Skin, in case this value might dictate 229 * the minimum height. 230 * @param topInset the pixel snapped top inset 231 * @param rightInset the pixel snapped right inset 232 * @param bottomInset the pixel snapped bottom inset 233 * @param leftInset the pixel snapped left inset 234 * @return A double representing the minimum height of this Skin. 235 */ 236 protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { 237 double minY = 0; 238 double maxY = 0; 239 for (int i = 0; i < children.size(); i++) { 240 Node node = children.get(i); 241 if (node.isManaged()) { 242 final double y = node.getLayoutBounds().getMinY() + node.getLayoutY(); 243 minY = Math.min(minY, y); 244 maxY = Math.max(maxY, y + node.minHeight(-1)); 245 } 246 } 247 double minHeight = maxY - minY; 248 return topInset + minHeight + bottomInset; 249 } 250 251 /** 252 * Computes the maximum allowable width of the Skin, based on the provided 253 * height. 254 * 255 * @param height The height of the Skin, in case this value might dictate 256 * the maximum width. 257 * @param topInset the pixel snapped top inset 258 * @param rightInset the pixel snapped right inset 259 * @param bottomInset the pixel snapped bottom inset 260 * @param leftInset the pixel snapped left inset 261 * @return A double representing the maximum width of this Skin. 262 */ 263 protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { 264 return Double.MAX_VALUE; 265 } 266 267 /** 268 * Computes the maximum allowable height of the Skin, based on the provided 269 * width. 270 * 271 * @param width The width of the Skin, in case this value might dictate 272 * the maximum height. 273 * @param topInset the pixel snapped top inset 274 * @param rightInset the pixel snapped right inset 275 * @param bottomInset the pixel snapped bottom inset 276 * @param leftInset the pixel snapped left inset 277 * @return A double representing the maximum height of this Skin. 278 */ 279 protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { 280 return Double.MAX_VALUE; 281 } 282 283 // PENDING_DOC_REVIEW 284 /** 285 * Calculates the preferred width of this {@code SkinBase}. The default 286 * implementation calculates this width as the width of the area occupied 287 * by its managed children when they are positioned at their 288 * current positions at their preferred widths. 289 * 290 * @param height the height that should be used if preferred width depends on it 291 * @param topInset the pixel snapped top inset 292 * @param rightInset the pixel snapped right inset 293 * @param bottomInset the pixel snapped bottom inset 294 * @param leftInset the pixel snapped left inset 295 * @return the calculated preferred width 296 */ 297 protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { 298 double minX = 0; 299 double maxX = 0; 300 for (int i = 0; i < children.size(); i++) { 301 Node node = children.get(i); 302 if (node.isManaged()) { 303 final double x = node.getLayoutBounds().getMinX() + node.getLayoutX(); 304 minX = Math.min(minX, x); 305 maxX = Math.max(maxX, x + node.prefWidth(-1)); 306 } 307 } 308 return maxX - minX; 309 } 310 311 // PENDING_DOC_REVIEW 312 /** 313 * Calculates the preferred height of this {@code SkinBase}. The default 314 * implementation calculates this height as the height of the area occupied 315 * by its managed children when they are positioned at their current 316 * positions at their preferred heights. 317 * 318 * @param width the width that should be used if preferred height depends on it 319 * @param topInset the pixel snapped top inset 320 * @param rightInset the pixel snapped right inset 321 * @param bottomInset the pixel snapped bottom inset 322 * @param leftInset the pixel snapped left inset 323 * @return the calculated preferred height 324 */ 325 protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { 326 double minY = 0; 327 double maxY = 0; 328 for (int i = 0; i < children.size(); i++) { 329 Node node = children.get(i); 330 if (node.isManaged()) { 331 final double y = node.getLayoutBounds().getMinY() + node.getLayoutY(); 332 minY = Math.min(minY, y); 333 maxY = Math.max(maxY, y + node.prefHeight(-1)); 334 } 335 } 336 return maxY - minY; 337 } 338 339 /** 340 * Calculates the baseline offset based on the first managed child. If there 341 * is no such child, returns {@link Node#getBaselineOffset()}. 342 * 343 * @param topInset the pixel snapped top inset 344 * @param rightInset the pixel snapped right inset 345 * @param bottomInset the pixel snapped bottom inset 346 * @param leftInset the pixel snapped left inset 347 * @return baseline offset 348 */ 349 protected double computeBaselineOffset(double topInset, double rightInset, double bottomInset, double leftInset) { 350 int size = children.size(); 351 for (int i = 0; i < size; ++i) { 352 Node child = children.get(i); 353 if (child.isManaged()) { 354 return child.getLayoutBounds().getMinY() + child.getLayoutY() + child.getBaselineOffset(); 355 } 356 } 357 return control.getLayoutBounds().getHeight(); 358 } 359 360 361 /*************************************************************************** 362 * * 363 * (Mostly ugly) Skin -> Control forwarding API * 364 * * 365 **************************************************************************/ 366 367 /** 368 * Utility method to get the top inset which includes padding and border 369 * inset. Then snapped to whole pixels if getSkinnable().isSnapToPixel() is true. 370 * 371 * @return Rounded up insets top 372 */ 373 protected double snappedTopInset() { 374 return control.snappedTopInset(); 375 } 376 377 /** 378 * Utility method to get the bottom inset which includes padding and border 379 * inset. Then snapped to whole pixels if getSkinnable().isSnapToPixel() is true. 380 * 381 * @return Rounded up insets bottom 382 */ 383 protected double snappedBottomInset() { 384 return control.snappedBottomInset(); 385 } 386 387 /** 388 * Utility method to get the left inset which includes padding and border 389 * inset. Then snapped to whole pixels if getSkinnable().isSnapToPixel() is true. 390 * 391 * @return Rounded up insets left 392 */ 393 protected double snappedLeftInset() { 394 return control.snappedLeftInset(); 395 } 396 397 /** 398 * Utility method to get the right inset which includes padding and border 399 * inset. Then snapped to whole pixels if getSkinnable().isSnapToPixel() is true. 400 * 401 * @return Rounded up insets right 402 */ 403 protected double snappedRightInset() { 404 return control.snappedRightInset(); 405 } 406 407 /** 408 * If this region's snapToPixel property is true, returns a value rounded 409 * to the nearest pixel, else returns the same value. 410 * @param value the space value to be snapped 411 * @return value rounded to nearest pixel 412 */ 413 protected double snapSpace(double value) { 414 return control.isSnapToPixel() ? Math.round(value) : value; 415 } 416 417 /** 418 * If this region's snapToPixel property is true, returns a value ceiled 419 * to the nearest pixel, else returns the same value. 420 * @param value the size value to be snapped 421 * @return value ceiled to nearest pixel 422 */ 423 protected double snapSize(double value) { 424 return control.isSnapToPixel() ? Math.ceil(value) : value; 425 } 426 427 /** 428 * If this region's snapToPixel property is true, returns a value rounded 429 * to the nearest pixel, else returns the same value. 430 * @param value the position value to be snapped 431 * @return value rounded to nearest pixel 432 */ 433 protected double snapPosition(double value) { 434 return control.isSnapToPixel() ? Math.round(value) : value; 435 } 436 437 protected void positionInArea(Node child, double areaX, double areaY, 438 double areaWidth, double areaHeight, double areaBaselineOffset, 439 HPos halignment, VPos valignment) { 440 positionInArea(child, areaX, areaY, areaWidth, areaHeight, 441 areaBaselineOffset, Insets.EMPTY, halignment, valignment); 442 } 443 444 protected void positionInArea(Node child, double areaX, double areaY, 445 double areaWidth, double areaHeight, double areaBaselineOffset, 446 Insets margin, HPos halignment, VPos valignment) { 447 Region.positionInArea(child, areaX, areaY, areaWidth, areaHeight, 448 areaBaselineOffset, margin, halignment, valignment, 449 control.isSnapToPixel()); 450 } 451 452 protected void layoutInArea(Node child, double areaX, double areaY, 453 double areaWidth, double areaHeight, 454 double areaBaselineOffset, 455 HPos halignment, VPos valignment) { 456 layoutInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset, 457 Insets.EMPTY, true, true, halignment, valignment); 458 } 459 460 protected void layoutInArea(Node child, double areaX, double areaY, 461 double areaWidth, double areaHeight, 462 double areaBaselineOffset, 463 Insets margin, 464 HPos halignment, VPos valignment) { 465 layoutInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset, 466 margin, true, true, halignment, valignment); 467 } 468 469 protected void layoutInArea(Node child, double areaX, double areaY, 470 double areaWidth, double areaHeight, 471 double areaBaselineOffset, 472 Insets margin, boolean fillWidth, boolean fillHeight, 473 HPos halignment, VPos valignment) { 474 Region.layoutInArea(child, areaX, areaY, areaWidth, areaHeight, 475 areaBaselineOffset, margin, fillWidth, fillHeight, halignment, 476 valignment, control.isSnapToPixel()); 477 } 478 479 480 481 /*************************************************************************** 482 * * 483 * Private Implementation * 484 * * 485 **************************************************************************/ 486 487 488 489 /************************************************************************** 490 * * 491 * Specialization of CSS handling code * 492 * * 493 **************************************************************************/ 494 495 private static class StyleableProperties { 496 497 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 498 499 static { 500 STYLEABLES = Collections.unmodifiableList(Control.getClassCssMetaData()); 501 } 502 } 503 504 /** 505 * @return The CssMetaData associated with this class, which may include the 506 * CssMetaData of its super classes. 507 */ public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 508 return SkinBase.StyleableProperties.STYLEABLES; 509 } 510 511 /** 512 * This method should delegate to {@link Node#getClassCssMetaData()} so that 513 * a Node's CssMetaData can be accessed without the need for reflection. 514 * @return The CssMetaData associated with this node, which may include the 515 * CssMetaData of its super classes. 516 */ 517 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 518 return getClassCssMetaData(); 519 } 520 521 /** @see Node#pseudoClassStateChanged */ 522 public final void pseudoClassStateChanged(PseudoClass pseudoClass, boolean active) { 523 Control ctl = getSkinnable(); 524 if (ctl != null) { 525 ctl.pseudoClassStateChanged(pseudoClass, active); 526 } 527 } 528 529 530 /*************************************************************************** 531 * * 532 * Testing-only API * 533 * * 534 **************************************************************************/ 535 536}