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}