javafx.scene.control.DialogPane Maven / Gradle / Ivy
Show all versions of openjfx-dialogs Show documentation
/*
* Copyright (c) 2014, 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 javafx.scene.control;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import org.controlsfx.DialogResources;
import javafx.beans.DefaultProperty;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.WritableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.css.CssMetaData;
import javafx.css.StyleOrigin;
import javafx.css.Styleable;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.css.StyleableStringProperty;
import javafx.event.ActionEvent;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.ButtonBar.ButtonData;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import com.sun.javafx.css.StyleManager;
import com.sun.javafx.css.converters.StringConverter;
/**
* DialogPane should be considered to be the root node displayed within a
* {@link Dialog} instance. In this role, the DialogPane is responsible for the
* placement of {@link #headerProperty() headers}, {@link #graphicProperty() graphics},
* {@link #contentProperty() content}, and {@link #getButtonTypes() buttons}.
* The default implementation of DialogPane (that is, the DialogPane class itself)
* handles the layout via the normal {@link #layoutChildren()} method. This
* method may be overridden by subclasses wishing to handle the layout in an
* alternative fashion).
*
* In addition to the {@link #headerProperty() header} and
* {@link #contentProperty() content} properties, there exists
* {@link #headerTextProperty() header text} and
* {@link #contentTextProperty() content text} properties. The way the *Text
* properties work is that they are a lower precedence compared to the Node
* properties, but they are far more convenient for developers in the common case,
* as it is likely the case that a developer more often than not simply wants to
* set a string value into the header or content areas of the DialogPane.
*
*
DialogPane operates on the concept of {@link ButtonType}. A ButtonType is
* a descriptor of a single button that should be represented visually in the
* DialogPane. Developers who create a DialogPane therefore must specify the
* button types that they want to display, and this is done via the
* {@link #getButtonTypes()} method, which returns a modifiable
* {@link ObservableList}, which users can add to and remove from as desired.
*
*
The {@link ButtonType} class defines a number of pre-defined button types,
* such as {@link ButtonType#OK} and {@link ButtonType#CANCEL}. Many users of the
* JavaFX dialogs API will find that these pre-defined button types meet their
* needs, particularly due to their built-in support for
* {@link ButtonData#isDefaultButton() default} and
* {@link ButtonData#isCancelButton() cancel} buttons, as well as the benefit of
* the strings being translated into all languages which JavaFX is translated to.
* For users that want to define their own {@link ButtonType} (most commonly to
* define a button with custom text), they may do so via the constructors available
* on the {@link ButtonType} class.
*
*
Developers will quickly find that the amount of configurability offered
* via the {@link ButtonType} class is minimal. This is intentional, but does not
* mean that developers can not modify the buttons created by the {@link ButtonType}
* that have been specified. To do this, developers simply call the
* {@link #lookupButton(ButtonType)} method with the ButtonType (assuming it has
* already been set in the {@link #getButtonTypes()} list. The returned Node is
* typically of type {@link Button}, but this depends on if the
* {@link #createButton(ButtonType)} method has been overridden.
*
*
The DialogPane class offers a few methods that can be overridden by
* subclasses, to more easily enable custom functionality. These methods include
* the following:
*
*
* - {@link #createButton(ButtonType)}
*
- {@link #createDetailsButton()}
*
- {@link #createButtonBar()}
*
*
* These methods are documented, so please take note of the expectations
* placed on any developer who wishes to override these methods with their own
* functionality.
*
* @see Dialog
* @since JavaFX 8u40
*/
@DefaultProperty("buttonTypes")
public class DialogPane extends Pane {
/**************************************************************************
*
* Static fields
*
**************************************************************************/
/**
* Creates a Label node that works well within a Dialog.
* @param text The text to display
*/
static Label createContentLabel(String text) {
Label label = new Label(text);
label.setMaxWidth(Double.MAX_VALUE);
label.setMaxHeight(Double.MAX_VALUE);
label.getStyleClass().add("content");
label.setWrapText(true);
label.setPrefWidth(360);
return label;
}
/**************************************************************************
*
* Private fields
*
**************************************************************************/
private final GridPane headerTextPanel;
private final Label contentLabel;
private final StackPane graphicContainer;
private final Node buttonBar;
private final ObservableList buttons = FXCollections.observableArrayList();
private final Map buttonNodes = new WeakHashMap<>();
private Node detailsButton;
// this is not a property - we have a package-scope setDialog method that
// sets this field. It is set by Dialog if the DialogPane is set inside a Dialog.
private Dialog> dialog;
/**************************************************************************
*
* Constructors
*
**************************************************************************/
/**
* Creates a new DialogPane instance with a style class of 'dialog-pane'.
*/
public DialogPane() {
getStyleClass().add("dialog-pane");
headerTextPanel = new GridPane();
getChildren().add(headerTextPanel);
graphicContainer = new StackPane();
contentLabel = createContentLabel("");
getChildren().add(contentLabel);
buttonBar = createButtonBar();
if (buttonBar != null) {
getChildren().add(buttonBar);
}
buttons.addListener((ListChangeListener) c -> {
while (c.next()) {
if (c.wasRemoved()) {
for (ButtonType cmd : c.getRemoved()) {
buttonNodes.remove(cmd);
}
}
if (c.wasAdded()) {
for (ButtonType cmd : c.getAddedSubList()) {
if (! buttonNodes.containsKey(cmd)) {
buttonNodes.put(cmd, createButton(cmd));
}
}
}
}
});
}
/**************************************************************************
*
* Properties
*
**************************************************************************/
// --- graphic
private final ObjectProperty graphicProperty = new StyleableObjectProperty() {
// The graphic is styleable by css, but it is the
// imageUrlProperty that handles the style value.
@Override public CssMetaData getCssMetaData() {
return StyleableProperties.GRAPHIC;
}
@Override public Object getBean() {
return DialogPane.this;
}
@Override public String getName() {
return "graphic";
}
WeakReference graphicRef = new WeakReference<>(null);
protected void invalidated() {
Node oldGraphic = graphicRef.get();
if (oldGraphic != null) {
getChildren().remove(oldGraphic);
}
Node newGraphic = getGraphic();
graphicRef = new WeakReference<>(newGraphic);
updateHeaderArea();
}
};
/**
* The dialog graphic, presented either in the header, if one is showing, or
* to the left of the {@link #contentProperty() content}.
*
* @return An ObjectProperty wrapping the current graphic.
*/
public final ObjectProperty graphicProperty() {
return graphicProperty;
}
public final Node getGraphic() {
return graphicProperty.get();
}
/**
* Sets the dialog graphic, which will be displayed either in the header, if
* one is showing, or to the left of the {@link #contentProperty() content}.
*
* @param graphic
* The new dialog graphic, or null if no graphic should be shown.
*/
public final void setGraphic(Node graphic) {
this.graphicProperty.set(graphic);
}
// --- imageUrl (this is NOT public API, except via CSS)
// Note that this code is a copy/paste from Labeled
private StyleableStringProperty imageUrl = null;
/**
* The imageUrl property is set from CSS and then the graphic property is
* set from the invalidated method. This ensures that the same image isn't
* reloaded.
*/
private StyleableStringProperty imageUrlProperty() {
if (imageUrl == null) {
imageUrl = new StyleableStringProperty() {
//
// If imageUrlProperty is invalidated, this is the origin of the style that
// triggered the invalidation. This is used in the invalidated() method where the
// value of super.getStyleOrigin() is not valid until after the call to set(v) returns,
// by which time invalidated will have been called.
// This value is initialized to USER in case someone calls set on the imageUrlProperty, which
// is possible:
// CssMetaData metaData = ((StyleableProperty)dialogPane.graphicProperty()).getCssMetaData();
// StyleableProperty prop = metaData.getStyleableProperty(dialogPane);
// prop.set(someUrl);
//
// TODO: Note that prop != dialogPane, which violates the contract between StyleableProperty and CssMetaData.
//
StyleOrigin origin = StyleOrigin.USER;
@Override
public void applyStyle(StyleOrigin origin, String v) {
this.origin = origin;
// Don't want applyStyle to throw an exception which would leave this.origin set to the wrong value
if (graphicProperty == null || graphicProperty.isBound() == false) super.applyStyle(origin, v);
// Origin is only valid for this invocation of applyStyle, so reset it to USER in case someone calls set.
this.origin = StyleOrigin.USER;
}
@Override
protected void invalidated() {
// need to call super.get() here since get() is overridden to return the graphicProperty's value
final String url = super.get();
if (url == null) {
((StyleableProperty)(WritableValue)graphicProperty()).applyStyle(origin, null);
} else {
// RT-34466 - if graphic's url is the same as this property's value, then don't overwrite.
final Node graphicNode = DialogPane.this.getGraphic();
if (graphicNode instanceof ImageView) {
final ImageView imageView = (ImageView)graphicNode;
final Image image = imageView.getImage();
if (image != null) {
final String imageViewUrl = image.impl_getUrl();
if (url.equals(imageViewUrl)) return;
}
}
final Image img = StyleManager.getInstance().getCachedImage(url);
if (img != null) {
//
// Note that it is tempting to try to re-use existing ImageView simply by setting
// the image on the current ImageView, if there is one. This would effectively change
// the image, but not the ImageView which means that no graphicProperty listeners would
// be notified. This is probably not what we want.
//
//
// Have to call applyStyle on graphicProperty so that the graphicProperty's
// origin matches the imageUrlProperty's origin.
//
((StyleableProperty)(WritableValue)graphicProperty()).applyStyle(origin, new ImageView(img));
}
}
}
@Override
public String get() {
//
// The value of the imageUrlProperty is that of the graphicProperty.
// Return the value in a way that doesn't expand the graphicProperty.
//
final Node graphic = getGraphic();
if (graphic instanceof ImageView) {
final Image image = ((ImageView)graphic).getImage();
if (image != null) {
return image.impl_getUrl();
}
}
return null;
}
@Override
public StyleOrigin getStyleOrigin() {
//
// The origin of the imageUrlProperty is that of the graphicProperty.
// Return the origin in a way that doesn't expand the graphicProperty.
//
return graphicProperty != null ? ((StyleableProperty)(WritableValue)graphicProperty).getStyleOrigin() : null;
}
@Override
public Object getBean() {
return DialogPane.this;
}
@Override
public String getName() {
return "imageUrl";
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.GRAPHIC;
}
};
}
return imageUrl;
}
// --- header
private final ObjectProperty header = new SimpleObjectProperty(null) {
WeakReference headerRef = new WeakReference<>(null);
@Override protected void invalidated() {
Node oldHeader = headerRef.get();
if (oldHeader != null) {
getChildren().remove(oldHeader);
}
Node newHeader = getHeader();
headerRef = new WeakReference<>(newHeader);
updateHeaderArea();
}
};
/**
* Node which acts as dialog's header
*
* @return dialog's header
*/
public final Node getHeader() {
return header.get();
}
/**
* Assigns dialog's header. Any Node can be used
*
* @param header
* future header
*/
public final void setHeader(Node header) {
this.header.setValue(header);
}
/**
* Property representing the header area of the dialog.
*/
public final ObjectProperty headerProperty() {
return header;
}
// --- header text
private final StringProperty headerText = new SimpleStringProperty(this, "headerText") {
@Override protected void invalidated() {
updateHeaderArea();
requestLayout();
}
};
/**
* Sets the string to show in the dialog header area. Note that the header text
* is lower precedence than the {@link #headerProperty() header node}, meaning
* that if both the header node and the headerText properties are set, the
* header text will not be displayed in a default DialogPane instance.
*/
public final void setHeaderText(String headerText) {
this.headerText.set(headerText);
}
/**
* Returns the currently-set header text for this DialogPane.
*/
public final String getHeaderText() {
return headerText.get();
}
/**
* A property representing the header text for the dialog pane. The header text
* is lower precedence than the {@link #headerProperty() header node}, meaning
* that if both the header node and the headerText properties are set, the
* header text will not be displayed in a default DialogPane instance.
*/
public final StringProperty headerTextProperty() {
return headerText;
}
// --- content
private final ObjectProperty content = new SimpleObjectProperty(null) {
WeakReference contentRef = new WeakReference<>(null);
@Override protected void invalidated() {
Node oldContent = contentRef.get();
if (oldContent != null) {
getChildren().remove(oldContent);
}
Node newContent = getContent();
contentRef = new WeakReference(newContent);
if (newContent != null) {
updateContentArea();
}
}
};
/**
* Returns the dialog content as a Node (even if it was set as a String
* using {@link #setContentText(String)} - this was simply transformed into a
* {@link Node} (most probably a {@link Label}).
*
* @return dialog's content
*/
public final Node getContent() {
return content.get();
}
/**
* Assign dialog content. Any Node can be used
*
* @param content
* dialog's content
*/
public final void setContent(Node content) {
this.content.setValue(content);
}
/**
* Property representing the content area of the dialog.
*/
public final ObjectProperty contentProperty() {
return content;
}
// --- content text
private final StringProperty contentText = new SimpleStringProperty(this, "contentText") {
@Override protected void invalidated() {
updateContentArea();
requestLayout();
}
};
/**
* Sets the string to show in the dialog content area. Note that the content text
* is lower precedence than the {@link #contentProperty() content node}, meaning
* that if both the content node and the contentText properties are set, the
* content text will not be displayed in a default DialogPane instance.
*/
public final void setContentText(String contentText) {
this.contentText.set(contentText);
}
/**
* Returns the currently-set content text for this DialogPane.
*/
public final String getContentText() {
return contentText.get();
}
/**
* A property representing the content text for the dialog pane. The content text
* is lower precedence than the {@link #contentProperty() content node}, meaning
* that if both the content node and the contentText properties are set, the
* content text will not be displayed in a default DialogPane instance.
*/
public final StringProperty contentTextProperty() {
return contentText;
}
// --- expandable content
private final ObjectProperty expandableContentProperty = new SimpleObjectProperty(null) {
WeakReference expandableContentRef = new WeakReference<>(null);
@Override protected void invalidated() {
Node oldExpandableContent = expandableContentRef.get();
if (oldExpandableContent != null) {
getChildren().remove(oldExpandableContent);
}
Node newExpandableContent = getExpandableContent();
expandableContentRef = new WeakReference(newExpandableContent);
if (newExpandableContent != null) {
newExpandableContent.setVisible(isExpanded());
newExpandableContent.setManaged(isExpanded());
if (!newExpandableContent.getStyleClass().contains("expandable-content")) { //$NON-NLS-1$
newExpandableContent.getStyleClass().add("expandable-content"); //$NON-NLS-1$
}
getChildren().add(newExpandableContent);
}
}
};
/**
* A property that represents the dialog expandable content area. Any Node
* can be placed in this area, but it will only be shown when the user
* clicks the 'Show Details' expandable button. This button will be added
* automatically when the expandable content property is non-null.
*/
public final ObjectProperty expandableContentProperty() {
return expandableContentProperty;
}
/**
* Returns the dialog expandable content node, if one is set, or null
* otherwise.
*/
public final Node getExpandableContent() {
return expandableContentProperty.get();
}
/**
* Sets the dialog expandable content node, or null if no expandable content
* needs to be shown.
*/
public final void setExpandableContent(Node content) {
this.expandableContentProperty.set(content);
}
// --- expanded
private final BooleanProperty expandedProperty = new SimpleBooleanProperty(this, "expanded", false) {
protected void invalidated() {
final Node expandableContent = getExpandableContent();
if (expandableContent != null) {
expandableContent.setVisible(isExpanded());
}
requestLayout();
}
};
/**
* Represents whether the dialogPane is expanded.
*/
public final BooleanProperty expandedProperty() {
return expandedProperty;
}
/**
* Returns whether or not the dialogPane is expanded.
*
* @return true if dialogPane is expanded.
*/
public final boolean isExpanded() {
return expandedProperty().get();
}
/**
* Sets whether the dialogPane is expanded. This only makes sense when there
* is {@link #expandableContentProperty() expandable content} to show.
*
* @param value true if dialogPane should be expanded.
*/
public final void setExpanded(boolean value) {
expandedProperty().set(value);
}
/**************************************************************************
*
* Public API
*
**************************************************************************/
// --- button types
/**
* Observable list of button types used for the dialog button bar area
* (created via the {@link #createButtonBar()} method). Modifying the contents
* of this list will immediately change the buttons displayed to the user
* within the dialog pane.
*
* @return The {@link ObservableList} of {@link ButtonType button types}
* available to the user.
*/
public final ObservableList getButtonTypes() {
return buttons;
}
/**
* This method provides a way in which developers may retrieve the actual
* Node for a given {@link ButtonType} (assuming it is part of the
* {@link #getButtonTypes() button types} list).
*
* @param buttonType The {@link ButtonType} for which a Node representation is requested.
* @return The Node used to represent the button type, as created by
* {@link #createButton(ButtonType)}, and only if the button type
* is part of the {@link #getButtonTypes() button types} list, otherwise null.
*/
public final Node lookupButton(ButtonType buttonType) {
return buttonNodes.get(buttonType);
}
/**
* This method can be overridden by subclasses to provide the button bar.
* Note that by overriding this method, the developer must take on multiple
* responsibilities:
*
*
* - The developer must immediately iterate through all
* {@link #getButtonTypes() button types} and call
* {@link #createButton(ButtonType)} for each of them in turn.
*
- The developer must add a listener to the
* {@link #getButtonTypes() button types} list, and when this list changes
* update the button bar as appropriate.
*
- Similarly, the developer must watch for changes to the
* {@link #expandableContentProperty() expandable content} property,
* adding and removing the details button (created via
* {@link #createDetailsButton()} method).
*
*
* The default implementation of this method creates and returns a new
* {@link ButtonBar} instance.
*/
protected Node createButtonBar() {
ButtonBar buttonBar = new ButtonBar();
buttonBar.setMaxWidth(Double.MAX_VALUE);
updateButtons(buttonBar);
getButtonTypes().addListener((ListChangeListener super ButtonType>) c -> updateButtons(buttonBar));
expandableContentProperty().addListener(o -> updateButtons(buttonBar));
return buttonBar;
}
/**
* This method can be overridden by subclasses to create a custom button that
* will subsequently inserted into the DialogPane button area (created via
* the {@link #createButtonBar()} method, but mostly commonly it is an instance
* of {@link ButtonBar}.
*
* @param buttonType The {@link ButtonType} to create a button from.
* @return A JavaFX {@link Node} that represents the given {@link ButtonType},
* most commonly an instance of {@link Button}.
*/
protected Node createButton(ButtonType buttonType) {
final Button button = new Button(buttonType.getText());
final ButtonData buttonData = buttonType.getButtonData();
ButtonBar.setButtonData(button, buttonData);
button.setDefaultButton(buttonType != null && buttonData.isDefaultButton());
button.setCancelButton(buttonType != null && buttonData.isCancelButton());
button.addEventHandler(ActionEvent.ACTION, ae -> {
if (ae.isConsumed()) return;
dialog.impl_setResultAndClose(buttonType, true);
});
return button;
}
/**
* This method can be overridden by subclasses to create a custom details button.
*
*
To override this method you must do two things:
*
* - The button will need to have its own code set to handle mouse / keyboard
* interaction and to toggle the state of the
* {@link #expandedProperty() expanded} property.
*
- If your button changes its visuals based on whether the dialog pane
* is expanded or collapsed, you should add a listener to the
* {@link #expandedProperty() expanded} property, so that you may update
* the button visuals.
*
*/
protected Node createDetailsButton() {
final Hyperlink detailsButton = new Hyperlink();
detailsButton.getStyleClass().setAll("details-button", "more"); //$NON-NLS-1$ //$NON-NLS-2$
final String moreText = DialogResources.getString("Dialog.detail.button.more"); //$NON-NLS-1$
final String lessText = DialogResources.getString("Dialog.detail.button.less"); //$NON-NLS-1$
detailsButton.setText(moreText);
expandedProperty().addListener(o -> {
final boolean isExpanded = isExpanded();
detailsButton.setText(isExpanded ? lessText : moreText);
detailsButton.getStyleClass().setAll("details-button", (isExpanded ? "less" : "more")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
});
detailsButton.setOnAction(ae -> setExpanded(!isExpanded()));
return detailsButton;
}
/** {@inheritDoc} */
@Override protected void layoutChildren() {
final boolean hasHeader = hasHeader();
final double w = getWidth() - (snappedLeftInset() + snappedRightInset());
final double h = getHeight() - (snappedTopInset() + snappedBottomInset());
final double leftPadding = snappedLeftInset();
final double topPadding = snappedTopInset();
final double rightPadding = snappedRightInset();
final double bottomPadding = snappedBottomInset();
// create the nodes up front so we can work out sizing
final Node header = getActualHeader();
final Node content = getActualContent();
final Node graphic = getActualGraphic();
final Node expandableContent = getExpandableContent();
final double graphicPrefWidth = hasHeader || graphic == null ? 0 : graphic.prefWidth(h);
final double headerPrefHeight = hasHeader ? header.prefHeight(w) : 0;
final double buttonBarPrefHeight = buttonBar == null ? 0 : buttonBar.prefHeight(w);
final double expandableContentPrefHeight = isExpanded() ? expandableContent.prefHeight(w) : 0;
final double graphicPrefHeight = hasHeader || graphic == null ? 0 : graphic.prefHeight(w);
final double contentAreaHeight = h -
(topPadding + headerPrefHeight + expandableContentPrefHeight + buttonBarPrefHeight + bottomPadding);
double x = leftPadding;
double y = topPadding;
if (! hasHeader) {
if (graphic != null) {
graphic.resizeRelocate(x, y, graphicPrefWidth, graphicPrefHeight);
x += graphicPrefWidth;
}
} else {
header.resizeRelocate(leftPadding, topPadding, w - rightPadding, headerPrefHeight);
y += headerPrefHeight;
}
content.resizeRelocate(x, y, w - graphicPrefWidth, contentAreaHeight);
y += contentAreaHeight;
if (expandableContent != null) {
expandableContent.resizeRelocate(leftPadding, y, w - rightPadding, expandableContentPrefHeight);
y += expandableContentPrefHeight;
}
if (buttonBar != null) {
buttonBar.resizeRelocate(leftPadding, (h - buttonBarPrefHeight - bottomPadding), w - rightPadding, buttonBarPrefHeight);
}
}
/** {@inheritDoc} */
@Override protected double computePrefWidth(double height) {
double headerPrefWidth = hasHeader() ? getActualHeader().prefWidth(height) + 10 : 0;
double contentPrefWidth = getActualContent().prefWidth(height);
double buttonBarPrefWidth = buttonBar == null ? 0 : buttonBar.prefWidth(height);
double graphicPrefWidth = getActualGraphic().prefWidth(height);
double expandableContentPrefWidth = 0;
final Node expandableContent = getExpandableContent();
if (isExpanded() && expandableContent != null) {
expandableContentPrefWidth = expandableContent.prefWidth(height);
}
double prefWidth = snappedLeftInset() +
(hasHeader() ? 0 : graphicPrefWidth) +
Math.max(Math.max(headerPrefWidth, expandableContentPrefWidth), Math.max(contentPrefWidth, buttonBarPrefWidth)) +
snappedRightInset();
return prefWidth;
}
/** {@inheritDoc} */
@Override protected double computePrefHeight(double width) {
final boolean hasHeader = hasHeader();
double headerPrefHeight = hasHeader ? getActualHeader().prefHeight(width) : 0;
double buttonBarPrefHeight = buttonBar == null ? 0 : buttonBar.prefHeight(width);
Node graphic = getActualGraphic();
double graphicPrefWidth = graphic.prefWidth(-1);
double graphicPrefHeight = graphic.prefHeight(width);
double contentAvailableWidth = width == Region.USE_COMPUTED_SIZE ? Region.USE_COMPUTED_SIZE :
hasHeader ? width : (width - graphicPrefWidth);
double contentPrefHeight = getActualContent().prefHeight(contentAvailableWidth);
double expandableContentPrefHeight = 0;
final Node expandableContent = getExpandableContent();
if (isExpanded() && expandableContent != null) {
expandableContentPrefHeight = expandableContent.prefHeight(width);
}
double prefHeight = snappedTopInset() +
headerPrefHeight +
Math.max(graphicPrefHeight, contentPrefHeight) +
expandableContentPrefHeight +
buttonBarPrefHeight +
snappedBottomInset();
return prefHeight;
}
/**************************************************************************
*
* Private implementation
* @param buttonBar
*
**************************************************************************/
private void updateButtons(ButtonBar buttonBar) {
buttonBar.getButtons().clear();
// show details button if expandable content is present
if (hasExpandableContent()) {
if (detailsButton == null) {
detailsButton = createDetailsButton();
}
ButtonBar.setButtonData(detailsButton, ButtonData.HELP_2);
buttonBar.getButtons().add(detailsButton);
ButtonBar.setButtonUniformSize(detailsButton, false);
}
boolean hasDefault = false;
for (ButtonType cmd : getButtonTypes()) {
Node button = buttonNodes.computeIfAbsent(cmd, dialogButton -> createButton(cmd));
// keep only first default button
if (button instanceof Button) {
ButtonData buttonType = cmd.getButtonData();
((Button)button).setDefaultButton(!hasDefault && buttonType != null && buttonType.isDefaultButton());
((Button)button).setCancelButton(buttonType != null && buttonType.isCancelButton());
hasDefault |= buttonType != null && buttonType.isDefaultButton();
}
buttonBar.getButtons().add(button);
}
}
private Node getActualContent() {
Node content = getContent();
return content == null ? contentLabel : content;
}
private Node getActualHeader() {
Node header = getHeader();
return header == null ? headerTextPanel : header;
}
private Node getActualGraphic() {
return headerTextPanel;
}
private void updateHeaderArea() {
Node header = getHeader();
if (header != null) {
getChildren().add(header);
headerTextPanel.setVisible(false);
headerTextPanel.setManaged(false);
} else {
final String headerText = getHeaderText();
headerTextPanel.getChildren().clear();
headerTextPanel.getStyleClass().clear();
// recreate the headerTextNode and add it to the children list.
headerTextPanel.setMaxWidth(Double.MAX_VALUE);
if (headerText != null && ! headerText.isEmpty()) {
headerTextPanel.getStyleClass().add("header-panel"); //$NON-NLS-1$
}
// on left of header is the text
Label headerLabel = new Label(headerText);
headerLabel.setWrapText(true);
headerLabel.setAlignment(Pos.CENTER_LEFT);
headerLabel.setMaxWidth(Double.MAX_VALUE);
headerLabel.setMaxHeight(Double.MAX_VALUE);
headerTextPanel.add(headerLabel, 0, 0);
// on the right of the header is a graphic, if one is specified
graphicContainer.getChildren().clear();
if (! graphicContainer.getStyleClass().contains("graphic-container")) { //$NON-NLS-1$)
graphicContainer.getStyleClass().add("graphic-container"); //$NON-NLS-1$
}
final Node graphic = getGraphic();
if (graphic != null) {
graphicContainer.getChildren().add(graphic);
}
headerTextPanel.add(graphicContainer, 1, 0);
// column constraints
ColumnConstraints textColumn = new ColumnConstraints();
textColumn.setFillWidth(true);
textColumn.setHgrow(Priority.ALWAYS);
ColumnConstraints graphicColumn = new ColumnConstraints();
graphicColumn.setFillWidth(false);
graphicColumn.setHgrow(Priority.NEVER);
headerTextPanel.getColumnConstraints().setAll(textColumn , graphicColumn);
headerTextPanel.setVisible(true);
headerTextPanel.setManaged(true);
}
}
private void updateContentArea() {
Node content = getContent();
if (content != null) {
if (! getChildren().contains(content)) {
getChildren().add(content);
}
if (! content.getStyleClass().contains("content")) {
content.getStyleClass().add("content");
}
contentLabel.setVisible(false);
contentLabel.setManaged(false);
} else {
final String contentText = getContentText();
if (contentText != null && !contentText.isEmpty()) {
contentLabel.setText(contentText);
contentLabel.setVisible(true);
contentLabel.setManaged(true);
}
}
}
boolean hasHeader() {
return getHeader() != null || isTextHeader();
}
private boolean isTextHeader() {
String headerText = getHeaderText();
return headerText != null && !headerText.isEmpty();
}
boolean hasExpandableContent() {
return getExpandableContent() != null;
}
void setDialog(Dialog> dialog) {
this.dialog = dialog;
}
/***************************************************************************
* *
* Stylesheet Handling *
* *
**************************************************************************/
/**
* @treatAsPrivate implementation detail
*/
private static class StyleableProperties {
private static final CssMetaData GRAPHIC =
new CssMetaData("-fx-graphic",
StringConverter.getInstance()) {
@Override
public boolean isSettable(DialogPane n) {
// Note that we care about the graphic, not imageUrl
return n.graphicProperty == null || !n.graphicProperty.isBound();
}
@Override
public StyleableProperty getStyleableProperty(DialogPane n) {
return n.imageUrlProperty();
}
};
private static final List> STYLEABLES;
static {
final List> styleables = new ArrayList<>(Region.getClassCssMetaData());
Collections.addAll(styleables,
GRAPHIC
);
STYLEABLES = Collections.unmodifiableList(styleables);
}
}
/**
* @return The CssMetaData associated with this class, which may include the
* CssMetaData of its super classes.
*/
public static List> getClassCssMetaData() {
return StyleableProperties.STYLEABLES;
}
/** {@inheritDoc} */
@Override public List> getCssMetaData() {
return getClassCssMetaData();
}
}