modules/controls/src/main/java/javafx/scene/control/skin/AccordionSkin.java

Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization
   1 /*
   2  * Copyright (c) 2010, 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 com.sun.javafx.scene.control.skin;
  27 

  28 import javafx.beans.value.ChangeListener;
  29 import javafx.collections.ListChangeListener;
  30 import javafx.scene.Node;
  31 import javafx.scene.control.Accordion;


  32 import javafx.scene.control.Skin;

  33 import javafx.scene.control.TitledPane;
  34 import javafx.scene.shape.Rectangle;
  35 
  36 import com.sun.javafx.scene.control.behavior.AccordionBehavior;
  37 
  38 import java.util.HashMap;
  39 import java.util.List;
  40 import java.util.Map;
  41 
  42 public class AccordionSkin extends BehaviorSkinBase<Accordion, AccordionBehavior> {












  43 
  44     private TitledPane firstTitledPane;
  45     private Rectangle clipRect;
  46 
  47     // This is used when we definitely want to force a relayout, regardless of
  48     // whether the height has also changed
  49     private boolean forceRelayout = false;
  50 
  51     // this is used to request a layout, assuming the height has also changed
  52     private boolean relayout = false;
  53 
  54     // we record the previous height to know if the current height is different
  55     private double previousHeight = 0;
  56 
  57     public AccordionSkin(final Accordion accordion) {
  58         super(accordion, new AccordionBehavior(accordion));
  59         accordion.getPanes().addListener((ListChangeListener<TitledPane>) c -> {


























  60             if (firstTitledPane != null) {
  61                 firstTitledPane.getStyleClass().remove("first-titled-pane");
  62             }
  63             if (!accordion.getPanes().isEmpty()) {
  64                 firstTitledPane = accordion.getPanes().get(0);
  65                 firstTitledPane.getStyleClass().add("first-titled-pane");
  66             }
  67             // TODO there may be a more efficient way to keep these in sync
  68             getChildren().setAll(accordion.getPanes());
  69             while (c.next()) {
  70                 removeTitledPaneListeners(c.getRemoved());
  71                 initTitledPaneListeners(c.getAddedSubList());
  72             }
  73 
  74             // added to resolve RT-32787
  75             forceRelayout = true;
  76         });
  77 
  78         if (!accordion.getPanes().isEmpty()) {
  79             firstTitledPane = accordion.getPanes().get(0);
  80             firstTitledPane.getStyleClass().add("first-titled-pane");
  81         }
  82 
  83         clipRect = new Rectangle(accordion.getWidth(), accordion.getHeight());
  84         getSkinnable().setClip(clipRect);
  85 
  86         initTitledPaneListeners(accordion.getPanes());
  87         getChildren().setAll(accordion.getPanes());
  88         getSkinnable().requestLayout();
  89 
  90         registerChangeListener(getSkinnable().widthProperty(), "WIDTH");
  91         registerChangeListener(getSkinnable().heightProperty(), "HEIGHT");
  92     }
  93 
  94     @Override
  95     protected void handleControlPropertyChanged(String property) {
  96         super.handleControlPropertyChanged(property);
  97         if ("WIDTH".equals(property)) {
  98             clipRect.setWidth(getSkinnable().getWidth());
  99         } else if ("HEIGHT".equals(property)) {
 100             clipRect.setHeight(getSkinnable().getHeight());
 101             relayout = true;

 102         }
 103     }
 104 
 105 

















 106     @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 107         double h = 0;
 108 
 109         if (expandedPane != null) {
 110             h += expandedPane.minHeight(width);
 111         }
 112 
 113         if (previousPane != null && !previousPane.equals(expandedPane)) {
 114             h += previousPane.minHeight(width);
 115         }
 116 
 117         for (Node child: getChildren()) {
 118             TitledPane pane = (TitledPane)child;
 119             if (!pane.equals(expandedPane) && !pane.equals(previousPane)) {
 120                 final Skin<?> skin = ((TitledPane)child).getSkin();
 121                 if (skin instanceof TitledPaneSkin) {
 122                     TitledPaneSkin childSkin = (TitledPaneSkin) skin;
 123                     h += childSkin.getTitleRegionSize(width);
 124                 } else {
 125                     h += pane.minHeight(width);
 126                 }
 127             }
 128         }
 129 
 130         return h + topInset + bottomInset;
 131     }
 132 

 133     @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 134         double h = 0;
 135 
 136         if (expandedPane != null) {
 137             h += expandedPane.prefHeight(width);
 138         }
 139 
 140         if (previousPane != null && !previousPane.equals(expandedPane)) {
 141             h += previousPane.prefHeight(width);
 142         }
 143 
 144         for (Node child: getChildren()) {
 145             TitledPane pane = (TitledPane)child;
 146             if (!pane.equals(expandedPane) && !pane.equals(previousPane)) {
 147                 final Skin<?> skin = ((TitledPane)child).getSkin();
 148                 if (skin instanceof TitledPaneSkin) {
 149                     TitledPaneSkin childSkin = (TitledPaneSkin) skin;
 150                     h += childSkin.getTitleRegionSize(width);
 151                 } else {
 152                     h += pane.prefHeight(width);
 153                 }
 154             }
 155         }
 156 
 157         return h + topInset + bottomInset;
 158     }
 159 

 160     @Override protected void layoutChildren(final double x, double y,
 161             final double w, final double h) {
 162         final boolean rebuild = forceRelayout || (relayout && previousHeight != h);
 163         forceRelayout = false;
 164         previousHeight = h;
 165 
 166         // Compute height of all the collapsed panes
 167         double collapsedPanesHeight = 0;
 168         for (TitledPane tp : getSkinnable().getPanes()) {
 169             if (!tp.equals(expandedPane)) {
 170                 TitledPaneSkin childSkin = (TitledPaneSkin) ((TitledPane)tp).getSkin();
 171                 collapsedPanesHeight += snapSize(childSkin.getTitleRegionSize(w));
 172             }
 173         }
 174         final double maxTitledPaneHeight = h - collapsedPanesHeight;
 175 
 176         for (TitledPane tp : getSkinnable().getPanes()) {
 177             Skin<?> skin = tp.getSkin();
 178             double ph;
 179             if (skin instanceof TitledPaneSkin) {


 207                         y += ph;
 208                         needsRelocate = false;
 209                     }
 210                 } else {
 211                     // Previous and current expanded pane are the same.
 212                     // Since we are expanding and collapsing the same pane we will need to relocate
 213                     // all the panes.
 214                     tp.relocate(x, y);
 215                     y += ph;
 216                     needsRelocate = false;
 217                 }
 218             }
 219 
 220             if (needsRelocate) {
 221                 tp.relocate(x, y);
 222                 y += ph;
 223             }
 224         }
 225     }
 226 
 227     private TitledPane expandedPane = null;
 228     private TitledPane previousPane = null;
 229     private Map<TitledPane, ChangeListener<Boolean>>listeners = new HashMap<TitledPane, ChangeListener<Boolean>>();




 230 
 231     private void initTitledPaneListeners(List<? extends TitledPane> list) {
 232         for (final TitledPane tp: list) {
 233             tp.setExpanded(tp == getSkinnable().getExpandedPane());
 234             if (tp.isExpanded()) {
 235                 expandedPane = tp;
 236             }
 237             ChangeListener<Boolean> changeListener = expandedPropertyListener(tp);
 238             tp.expandedProperty().addListener(changeListener);
 239             listeners.put(tp, changeListener);
 240         }
 241     }
 242 
 243     private void removeTitledPaneListeners(List<? extends TitledPane> list) {
 244         for (final TitledPane tp: list) {
 245             if (listeners.containsKey(tp)) {
 246                 tp.expandedProperty().removeListener(listeners.get(tp));
 247                 listeners.remove(tp);
 248             }
 249         }


   1 /*
   2  * Copyright (c) 2010, 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 javafx.scene.control.skin;
  27 
  28 import com.sun.javafx.scene.control.behavior.BehaviorBase;
  29 import javafx.beans.value.ChangeListener;
  30 import javafx.collections.ListChangeListener;
  31 import javafx.scene.Node;
  32 import javafx.scene.control.Accordion;
  33 import javafx.scene.control.Button;
  34 import javafx.scene.control.Control;
  35 import javafx.scene.control.Skin;
  36 import javafx.scene.control.SkinBase;
  37 import javafx.scene.control.TitledPane;
  38 import javafx.scene.shape.Rectangle;
  39 
  40 import com.sun.javafx.scene.control.behavior.AccordionBehavior;
  41 
  42 import java.util.HashMap;
  43 import java.util.List;
  44 import java.util.Map;
  45 
  46 /**
  47  * Default skin implementation for the {@link Accordion} control.
  48  *
  49  * @see Accordion
  50  * @since 9
  51  */
  52 public class AccordionSkin extends SkinBase<Accordion> {
  53 
  54     /***************************************************************************
  55      *                                                                         *
  56      * Private fields                                                          *
  57      *                                                                         *
  58      **************************************************************************/
  59 
  60     private TitledPane firstTitledPane;
  61     private Rectangle clipRect;
  62 
  63     // This is used when we definitely want to force a relayout, regardless of
  64     // whether the height has also changed
  65     private boolean forceRelayout = false;
  66 
  67     // this is used to request a layout, assuming the height has also changed
  68     private boolean relayout = false;
  69 
  70     // we record the previous height to know if the current height is different
  71     private double previousHeight = 0;
  72 
  73     private TitledPane expandedPane = null;
  74     private TitledPane previousPane = null;
  75     private Map<TitledPane, ChangeListener<Boolean>>listeners = new HashMap<>();
  76 
  77     private final BehaviorBase<Accordion> behavior;
  78 
  79 
  80 
  81     /***************************************************************************
  82      *                                                                         *
  83      * Constructors                                                            *
  84      *                                                                         *
  85      **************************************************************************/
  86 
  87     /**
  88      * Creates a new AccordionSkin instance, installing the necessary child
  89      * nodes into the Control {@link Control#getChildren() children} list, as
  90      * well as the necessary input mappings for handling key, mouse, etc events.
  91      *
  92      * @param control The control that this skin should be installed onto.
  93      */
  94     public AccordionSkin(final Accordion control) {
  95         super(control);
  96 
  97         // install default input map for the accordion control
  98         behavior = new AccordionBehavior(control);
  99 //        control.setInputMap(behavior.getInputMap());
 100 
 101         control.getPanes().addListener((ListChangeListener<TitledPane>) c -> {
 102             if (firstTitledPane != null) {
 103                 firstTitledPane.getStyleClass().remove("first-titled-pane");
 104             }
 105             if (!control.getPanes().isEmpty()) {
 106                 firstTitledPane = control.getPanes().get(0);
 107                 firstTitledPane.getStyleClass().add("first-titled-pane");
 108             }
 109             // TODO there may be a more efficient way to keep these in sync
 110             getChildren().setAll(control.getPanes());
 111             while (c.next()) {
 112                 removeTitledPaneListeners(c.getRemoved());
 113                 initTitledPaneListeners(c.getAddedSubList());
 114             }
 115 
 116             // added to resolve RT-32787
 117             forceRelayout = true;
 118         });
 119 
 120         if (!control.getPanes().isEmpty()) {
 121             firstTitledPane = control.getPanes().get(0);
 122             firstTitledPane.getStyleClass().add("first-titled-pane");
 123         }
 124 
 125         clipRect = new Rectangle(control.getWidth(), control.getHeight());
 126         getSkinnable().setClip(clipRect);
 127 
 128         initTitledPaneListeners(control.getPanes());
 129         getChildren().setAll(control.getPanes());
 130         getSkinnable().requestLayout();
 131 
 132         registerChangeListener(getSkinnable().widthProperty(), e -> clipRect.setWidth(getSkinnable().getWidth()));
 133         registerChangeListener(getSkinnable().heightProperty(), e -> {








 134             clipRect.setHeight(getSkinnable().getHeight());
 135             relayout = true;
 136         });
 137     }

 138 
 139 
 140 
 141     /***************************************************************************
 142      *                                                                         *
 143      * Public API                                                              *
 144      *                                                                         *
 145      **************************************************************************/
 146 
 147     /** {@inheritDoc} */
 148     @Override public void dispose() {
 149         super.dispose();
 150 
 151         if (behavior != null) {
 152             behavior.dispose();
 153         }
 154     }
 155 
 156     /** {@inheritDoc} */
 157     @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 158         double h = 0;
 159 
 160         if (expandedPane != null) {
 161             h += expandedPane.minHeight(width);
 162         }
 163 
 164         if (previousPane != null && !previousPane.equals(expandedPane)) {
 165             h += previousPane.minHeight(width);
 166         }
 167 
 168         for (Node child: getChildren()) {
 169             TitledPane pane = (TitledPane)child;
 170             if (!pane.equals(expandedPane) && !pane.equals(previousPane)) {
 171                 final Skin<?> skin = ((TitledPane)child).getSkin();
 172                 if (skin instanceof TitledPaneSkin) {
 173                     TitledPaneSkin childSkin = (TitledPaneSkin) skin;
 174                     h += childSkin.getTitleRegionSize(width);
 175                 } else {
 176                     h += pane.minHeight(width);
 177                 }
 178             }
 179         }
 180 
 181         return h + topInset + bottomInset;
 182     }
 183 
 184     /** {@inheritDoc} */
 185     @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 186         double h = 0;
 187 
 188         if (expandedPane != null) {
 189             h += expandedPane.prefHeight(width);
 190         }
 191 
 192         if (previousPane != null && !previousPane.equals(expandedPane)) {
 193             h += previousPane.prefHeight(width);
 194         }
 195 
 196         for (Node child: getChildren()) {
 197             TitledPane pane = (TitledPane)child;
 198             if (!pane.equals(expandedPane) && !pane.equals(previousPane)) {
 199                 final Skin<?> skin = ((TitledPane)child).getSkin();
 200                 if (skin instanceof TitledPaneSkin) {
 201                     TitledPaneSkin childSkin = (TitledPaneSkin) skin;
 202                     h += childSkin.getTitleRegionSize(width);
 203                 } else {
 204                     h += pane.prefHeight(width);
 205                 }
 206             }
 207         }
 208 
 209         return h + topInset + bottomInset;
 210     }
 211 
 212     /** {@inheritDoc} */
 213     @Override protected void layoutChildren(final double x, double y,
 214             final double w, final double h) {
 215         final boolean rebuild = forceRelayout || (relayout && previousHeight != h);
 216         forceRelayout = false;
 217         previousHeight = h;
 218 
 219         // Compute height of all the collapsed panes
 220         double collapsedPanesHeight = 0;
 221         for (TitledPane tp : getSkinnable().getPanes()) {
 222             if (!tp.equals(expandedPane)) {
 223                 TitledPaneSkin childSkin = (TitledPaneSkin) ((TitledPane)tp).getSkin();
 224                 collapsedPanesHeight += snapSize(childSkin.getTitleRegionSize(w));
 225             }
 226         }
 227         final double maxTitledPaneHeight = h - collapsedPanesHeight;
 228 
 229         for (TitledPane tp : getSkinnable().getPanes()) {
 230             Skin<?> skin = tp.getSkin();
 231             double ph;
 232             if (skin instanceof TitledPaneSkin) {


 260                         y += ph;
 261                         needsRelocate = false;
 262                     }
 263                 } else {
 264                     // Previous and current expanded pane are the same.
 265                     // Since we are expanding and collapsing the same pane we will need to relocate
 266                     // all the panes.
 267                     tp.relocate(x, y);
 268                     y += ph;
 269                     needsRelocate = false;
 270                 }
 271             }
 272 
 273             if (needsRelocate) {
 274                 tp.relocate(x, y);
 275                 y += ph;
 276             }
 277         }
 278     }
 279 
 280 
 281 
 282     /***************************************************************************
 283      *                                                                         *
 284      * Private implementation                                                  *
 285      *                                                                         *
 286      **************************************************************************/
 287 
 288     private void initTitledPaneListeners(List<? extends TitledPane> list) {
 289         for (final TitledPane tp: list) {
 290             tp.setExpanded(tp == getSkinnable().getExpandedPane());
 291             if (tp.isExpanded()) {
 292                 expandedPane = tp;
 293             }
 294             ChangeListener<Boolean> changeListener = expandedPropertyListener(tp);
 295             tp.expandedProperty().addListener(changeListener);
 296             listeners.put(tp, changeListener);
 297         }
 298     }
 299 
 300     private void removeTitledPaneListeners(List<? extends TitledPane> list) {
 301         for (final TitledPane tp: list) {
 302             if (listeners.containsKey(tp)) {
 303                 tp.expandedProperty().removeListener(listeners.get(tp));
 304                 listeners.remove(tp);
 305             }
 306         }