gt:             if (graphic != null) {
context: /*
   * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   *
   * This code is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 2 only, as
   * published by the Free Software Foundation.  Oracle designates this
   * particular file as subject to the "Classpath" exception as provided
   * by Oracle in the LICENSE file that accompanied this code.
   *
   * This code is distributed in the hope that it will be useful, but WITHOUT
   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   * version 2 for more details (a copy is included in the LICENSE file that
   * accompanied this code).
   *
   * You should have received a copy of the GNU General Public License version
   * 2 along with this work; if not, write to the Free Software Foundation,
   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   *
   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   * or visit www.oracle.com if you need additional information or have any
   * questions.
   */
  
  package com.sun.javafx.scene.control.skin;
  
  import javafx.animation.Animation.Status;
  import javafx.animation.Interpolator;
  import javafx.animation.KeyFrame;
  import javafx.animation.KeyValue;
  import javafx.animation.Timeline;
  import javafx.beans.property.DoubleProperty;
  import javafx.beans.property.DoublePropertyBase;
  import javafx.beans.property.SimpleDoubleProperty;
  import javafx.event.ActionEvent;
  import javafx.event.EventHandler;
  import javafx.geometry.Bounds;
  import javafx.geometry.HPos;
  import javafx.geometry.Pos;
  import javafx.geometry.VPos;
  import javafx.scene.Cursor;
  import javafx.scene.Node;
  import javafx.scene.control.ContentDisplay;
  import javafx.scene.control.TitledPane;
  import javafx.scene.input.MouseEvent;
  import javafx.scene.layout.Region;
  import javafx.scene.layout.StackPane;
  import javafx.scene.shape.Rectangle;
  import javafx.util.Duration;
  
  import com.sun.javafx.scene.control.behavior.TitledPaneBehavior;
  import com.sun.javafx.scene.traversal.Direction;
  import com.sun.javafx.scene.traversal.TraversalEngine;
  import com.sun.javafx.scene.traversal.TraverseListener;
  import javafx.beans.binding.DoubleBinding;
  import javafx.geometry.Insets;
  import javafx.scene.control.Accordion;
  import javafx.scene.control.Labeled;
  import javafx.scene.control.ContextMenu;
  import javafx.scene.input.MouseButton;
  import javafx.scene.text.Font;
  
  public class TitledPaneSkin extends LabeledSkinBase<TitledPane, TitledPaneBehavior>  {
  
      public static final int MIN_HEADER_HEIGHT = 22;
      public static final Duration TRANSITION_DURATION = new Duration(350.0);
  
      private final TitleRegion titleRegion;
      private final StackPane contentContainer;
      private final Content contentRegion;
      private Timeline timeline;
      private double transitionStartValue;
      private Rectangle clipRect;
      private Pos pos;
      private HPos hpos;
      private VPos vpos;
  
      public TitledPaneSkin(final TitledPane titledPane) {
          super(titledPane, new TitledPaneBehavior(titledPane));
  
          clipRect = new Rectangle();
  
          transitionStartValue = 0;
          titleRegion = new TitleRegion();
  
          contentRegion = new Content(getSkinnable().getContent());
          contentContainer = new StackPane() {
              {
                  getStyleClass().setAll("content");
  
                  // RT-20266: We want to align the content container so the bottom of the content
                  // is at the bottom of the title region.  If we do not do this the
                  // content will be center aligned.
                  setAlignment(Pos.BOTTOM_CENTER);
  
                  getChildren().setAll(contentRegion);
              }
          };
          contentContainer.setClip(clipRect);
  
          if (titledPane.isExpanded()) {
              setTransition(1.0f);
              setExpanded(titledPane.isExpanded());
          } else {
              setTransition(0.0f);
              if (contentRegion.getContent() != null) {
                  contentRegion.getContent().setVisible(false);
              }
          }
  
          getChildren().setAll(contentContainer, titleRegion);
  
          registerChangeListener(titledPane.contentProperty(), "CONTENT");
          registerChangeListener(titledPane.expandedProperty(), "EXPANDED");
          registerChangeListener(titledPane.collapsibleProperty(), "COLLAPSIBLE");
          registerChangeListener(titledPane.alignmentProperty(), "ALIGNMENT");
          registerChangeListener(titledPane.widthProperty(), "WIDTH");
          registerChangeListener(titledPane.heightProperty(), "HEIGHT");
          registerChangeListener(titleRegion.alignmentProperty(), "TITLE_REGION_ALIGNMENT");
  
          pos = titledPane.getAlignment();
          hpos = pos == null ? HPos.LEFT   : pos.getHpos();
          vpos = pos == null ? VPos.CENTER : pos.getVpos();
      }
  
      public StackPane getContentRegion() {
          return contentRegion;
      }
  
      @Override
      protected void handleControlPropertyChanged(String property) {
          super.handleControlPropertyChanged(property);
          if ("CONTENT".equals(property)) {
              contentRegion.setContent(getSkinnable().getContent());
          } else if ("EXPANDED".equals(property)) {
              setExpanded(getSkinnable().isExpanded());
          } else if ("COLLAPSIBLE".equals(property)) {
              titleRegion.update();
          } else if ("ALIGNMENT".equals(property)) {
              pos = getSkinnable().getAlignment();
              hpos = pos.getHpos();
              vpos = pos.getVpos();
          } else if ("TITLE_REGION_ALIGNMENT".equals(property)) {
              pos = titleRegion.getAlignment();
              hpos = pos.getHpos();
              vpos = pos.getVpos();
          } else if ("WIDTH".equals(property)) {
              clipRect.setWidth(getSkinnable().getWidth());
          } else if ("HEIGHT".equals(property)) {
              clipRect.setHeight(contentContainer.getHeight());
          } else if ("GRAPHIC_TEXT_GAP".equals(property)) {
              titleRegion.requestLayout();            
          }
      }
  
      // Override LabeledSkinBase updateChildren because
      // it removes all the children.  The update() in TitleRegion
      // will replace this method.
      @Override protected void updateChildren() {
          if (titleRegion != null) {
              titleRegion.update();
          }
      }
  
      private void setExpanded(boolean expanded) {
          if (! getSkinnable().isCollapsible()) {
              setTransition(1.0f);
              return;
          }
  
          // we need to perform the transition between expanded / hidden
          if (getSkinnable().isAnimated()) {
              transitionStartValue = getTransition();
              doAnimationTransition();
          } else {
              if (expanded) {
                  setTransition(1.0f);
              } else {
                  setTransition(0.0f);
              }
              if (contentRegion.getContent() != null) {
                  contentRegion.getContent().setVisible(expanded);
               }
              getSkinnable().requestLayout();
          }
      }
  
      private DoubleProperty transition;
      private void setTransition(double value) { transitionProperty().set(value); }
      private double getTransition() { return transition == null ? 0.0 : transition.get(); }
      private DoubleProperty transitionProperty() {
          if (transition == null) {
              transition = new SimpleDoubleProperty(this, "transition", 0.0) {
                  @Override protected void invalidated() {
                      contentContainer.requestLayout();
                  }
              };
          }
          return transition;
      }
  
      private boolean isInsideAccordion() {
          return getSkinnable().getParent() != null && getSkinnable().getParent() instanceof Accordion;
      }
  
      @Override protected void layoutChildren(final double x, double y,
              final double w, final double h) {
          
          // header
          double headerHeight = Math.max(MIN_HEADER_HEIGHT, snapSize(titleRegion.prefHeight(-1)));
  
          titleRegion.resize(w, headerHeight);
          positionInArea(titleRegion, x, y,
              w, headerHeight, 0, HPos.LEFT, VPos.CENTER);
  
          // content
          double contentHeight = (h - headerHeight) * getTransition();
          if (isInsideAccordion()) {
              if (prefHeightFromAccordion != 0) {
                  contentHeight = (prefHeightFromAccordion - headerHeight) * getTransition();
              }
          }
          contentHeight = snapSize(contentHeight);
  
          y += snapSize(headerHeight);
          contentContainer.resize(w, contentHeight);
          clipRect.setHeight(contentHeight);
          positionInArea(contentContainer, x, y,
              w, contentHeight, /*baseline ignored*/0, HPos.CENTER, VPos.CENTER);
      }
  
      @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
          return computePrefWidth(height, topInset, rightInset, bottomInset, leftInset);
      }
  
      @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
          return Math.max(MIN_HEADER_HEIGHT, snapSize(titleRegion.prefHeight(-1)));
      }
  
      @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
          double titleWidth = snapSize(titleRegion.prefWidth(height));
          double contentWidth = snapSize(contentContainer.prefWidth(height));
          return Math.max(titleWidth, contentWidth) + leftInset + rightInset;
      }
  
      @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
          double headerHeight = Math.max(MIN_HEADER_HEIGHT, snapSize(titleRegion.prefHeight(-1)));
          double contentHeight = contentContainer.prefHeight(-1) * getTransition();
          return headerHeight + snapSize(contentHeight) + topInset + bottomInset;
      }
  
      @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
          return Double.MAX_VALUE;
      }
  
      private double prefHeightFromAccordion = 0;
      void setMaxTitledPaneHeightForAccordion(double height) {
          this.prefHeightFromAccordion = height;
      }
  
      double getTitledPaneHeightForAccordion() {
          double headerHeight = Math.max(MIN_HEADER_HEIGHT, snapSize(titleRegion.prefHeight(-1)));
          double contentHeight = (prefHeightFromAccordion - headerHeight) * getTransition();
          return headerHeight + snapSize(contentHeight) + snappedTopInset() + snappedBottomInset();
      }
  
      private void doAnimationTransition() {
          if (contentRegion.getContent() == null) {
              return;
          }
  
          Duration duration;
          if (timeline != null && (timeline.getStatus() != Status.STOPPED)) {
              duration = timeline.getCurrentTime();
              timeline.stop();
          } else {
              duration = TRANSITION_DURATION;
          }
  
          timeline = new Timeline();
          timeline.setCycleCount(1);
  
          KeyFrame k1, k2;
  
          if (getSkinnable().isExpanded()) {
              k1 = new KeyFrame(
                  Duration.ZERO,
                  new EventHandler<ActionEvent>() {
                      @Override public void handle(ActionEvent event) {
                          // start expand
                          contentRegion.getContent().setCache(true);
                          contentRegion.getContent().setVisible(true);
                      }
                  },
                  new KeyValue(transitionProperty(), transitionStartValue)
              );
  
              k2 = new KeyFrame(
                  duration,
                      new EventHandler<ActionEvent>() {
                      @Override public void handle(ActionEvent event) {
                          // end expand
                          contentRegion.getContent().setCache(false);
                      }
                  },
                  new KeyValue(transitionProperty(), 1, Interpolator.LINEAR)
  
              );
          } else {
              k1 = new KeyFrame(
                  Duration.ZERO,
                  new EventHandler<ActionEvent>() {
                      @Override public void handle(ActionEvent event) {
                          // Start collapse
                          contentRegion.getContent().setCache(true);
                      }
                  },
                  new KeyValue(transitionProperty(), transitionStartValue)
              );
  
              k2 = new KeyFrame(
                  duration,
                  new EventHandler<ActionEvent>() {
                      @Override public void handle(ActionEvent event) {
                          // end collapse
                          contentRegion.getContent().setVisible(false);
                          contentRegion.getContent().setCache(false);
                      }
                  },
                  new KeyValue(transitionProperty(), 0, Interpolator.LINEAR)
              );
          }
  
          timeline.getKeyFrames().setAll(k1, k2);
          timeline.play();
      }
  
      class TitleRegion extends StackPane {
          private final StackPane arrowRegion;
  
          public TitleRegion() {
              getStyleClass().setAll("title");
              arrowRegion = new StackPane();
              arrowRegion.setId("arrowRegion");
              arrowRegion.getStyleClass().setAll("arrow-button");
  
              StackPane arrow = new StackPane();
              arrow.setId("arrow");
              arrow.getStyleClass().setAll("arrow");
              arrowRegion.getChildren().setAll(arrow);
              
              // RT-13294: TitledPane : add animation to the title arrow
              arrow.rotateProperty().bind(new DoubleBinding() {
                  { bind(transitionProperty()); }
  
                  @Override protected double computeValue() {
                      return -90 * (1.0 - getTransition());
                  }
              });
  
              setAlignment(Pos.CENTER_LEFT);
  
              setOnMouseReleased(new EventHandler<MouseEvent>() {
                  @Override public void handle(MouseEvent e) {
                      if( e.getButton() != MouseButton.PRIMARY ) return;
                      ContextMenu contextMenu = getSkinnable().getContextMenu() ;
                      if (contextMenu != null) {
                          contextMenu.hide() ;
                      }
                      if (getSkinnable().isCollapsible() && getSkinnable().isFocused()) {
                          getBehavior().toggle();
                      }
                  }
              });
  
              // title region consists of the title and the arrow regions
              update();
          }
  
          private void update() {
              getChildren().clear();
              final TitledPane titledPane = getSkinnable();
  
              if (titledPane.isCollapsible()) {
                  getChildren().add(arrowRegion);
              }
  
              // Only in some situations do we want to have the graphicPropertyChangedListener
              // installed. Since updateChildren() is not called much, we'll just remove it always
              // and reinstall it later if it is necessary to do so.
  