Skip to content
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import javafx.event.EventType;
import javafx.scene.Node;
import javafx.scene.control.ButtonBar.ButtonData;
import javafx.scene.layout.HeaderBar;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
Expand Down Expand Up @@ -460,10 +461,10 @@ public final Modality getModality() {
}

/**
* Specifies the style for this dialog. This must be done prior to making
* the dialog visible. The style is one of: StageStyle.DECORATED,
* StageStyle.UNDECORATED, StageStyle.TRANSPARENT, StageStyle.UTILITY,
* or StageStyle.UNIFIED.
* Specifies the style for this dialog. This must be done prior to making the dialog visible.
* <p>
* Note that a dialog with the {@link StageStyle#EXTENDED} style should also specify a {@link HeaderBar} with
* the {@link DialogPane#setHeaderBar(HeaderBar)} method, as otherwise the dialog window will not be draggable.
*
* @param style the style for this dialog.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2025, 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
Expand Down Expand Up @@ -31,6 +31,8 @@
import java.util.Map;
import java.util.WeakHashMap;

import com.sun.javafx.PreviewFeature;
import com.sun.javafx.css.StyleManager;
import com.sun.javafx.scene.control.skin.Utils;
import com.sun.javafx.scene.control.skin.resources.ControlResources;
import javafx.beans.DefaultProperty;
Expand All @@ -51,22 +53,24 @@
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.css.StyleableStringProperty;
import javafx.css.converter.StringConverter;
import javafx.event.ActionEvent;
import javafx.geometry.HPos;
import javafx.geometry.Pos;
import javafx.geometry.VPos;
import javafx.scene.AccessibleRole;
import javafx.scene.Node;
import javafx.scene.control.ButtonBar.ButtonData;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HeaderBar;
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 javafx.css.converter.StringConverter;
import javafx.stage.StageStyle;

/**
* DialogPane should be considered to be the root node displayed within a
Expand Down Expand Up @@ -430,6 +434,79 @@ public CssMetaData<DialogPane,String> getCssMetaData() {
return imageUrl;
}

// --- header bar
private ObjectProperty<HeaderBar> headerBar;

/**
* Specifies the {@link HeaderBar} for the dialog. The {@code HeaderBar} will be placed at the
* top of the dialog window, and extend the entire width of the window. This property will only
* be used if the dialog window is configured with the {@link StageStyle#EXTENDED} style; it has
* no effect for other styles.
*
* @return the {@code headerBar} property
* @defaultValue {@code null}
* @since 26
* @deprecated This is a preview feature which may be changed or removed in a future release.
*/
@Deprecated(since = "26")
public final ObjectProperty<HeaderBar> headerBarProperty() {
if (headerBar == null) {
PreviewFeature.HEADER_BAR.checkEnabled();
headerBar = new SimpleObjectProperty<>(this, "headerBar") {
WeakReference<HeaderBar> wref = new WeakReference<>(null);

@Override
protected void invalidated() {
HeaderBar oldValue = wref.get();
if (oldValue != null) {
getChildren().remove(oldValue);
}

HeaderBar newValue = get();
if (newValue != null) {
getChildren().add(newValue);
}

wref = new WeakReference<>(newValue);
}
};
}

return headerBar;
}

/**
* Gets the value of the {@link #headerBarProperty() headerBar} property.
*
* @return the {@code HeaderBar}
* @since 26
* @deprecated This is a preview feature which may be changed or removed in a future release.
*/
@Deprecated(since = "26")
public final HeaderBar getHeaderBar() {
PreviewFeature.HEADER_BAR.checkEnabled();
return headerBar != null ? headerBar.get() : null;
}

/**
* Sets the value of the {@link #headerBarProperty() headerBar} property.
*
* @param value the new value
* @since 26
* @deprecated This is a preview feature which may be changed or removed in a future release.
*/
@Deprecated(since = "26")
public final void setHeaderBar(HeaderBar value) {
PreviewFeature.HEADER_BAR.checkEnabled();
if (headerBar != null || value != null) {
headerBarProperty().set(value);
}
}

// This method skips the preview feature check for internal use and can be removed when HeaderBar is finalized.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if a comment should be added to the JBS ticket listing the things that need to be undone, lest we forget.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this specific case it's quite hard to miss, since it's right next to the preview API. But go ahead if you want to add the comment.

private HeaderBar getHeaderBarInternal() {
return headerBar != null ? headerBar.get() : null;
}

// --- header
private final ObjectProperty<Node> header = new SimpleObjectProperty<>(null) {
Expand Down Expand Up @@ -878,11 +955,15 @@ protected Node createDetailsButton() {
final Node content = getActualContent();
final Node graphic = getActualGraphic();
final Node expandableContent = getExpandableContent();
final HeaderBar headerBar = getHeaderBarInternal();

final double graphicPrefWidth = hasHeader || graphic == null ? 0 : graphic.prefWidth(-1);
final double headerPrefHeight = hasHeader ? header.prefHeight(w) : 0;
final double buttonBarPrefHeight = buttonBar == null ? 0 : buttonBar.prefHeight(w);
final double graphicPrefHeight = hasHeader || graphic == null ? 0 : graphic.prefHeight(-1);
final double headerBarPrefHeight = headerBar != null
? Utils.boundedSize(headerBar.prefHeight(w), headerBar.minHeight(w), headerBar.maxHeight(w))
: 0;

final double expandableContentPrefHeight;
final double contentAreaHeight;
Expand All @@ -894,16 +975,20 @@ protected Node createDetailsButton() {
// precedence goes to content and then expandable content
contentAreaHeight = isExpanded() ? content.prefHeight(availableContentWidth) : 0;
contentAndGraphicHeight = hasHeader ? contentAreaHeight : Math.max(graphicPrefHeight, contentAreaHeight);
expandableContentPrefHeight = h - (headerPrefHeight + contentAndGraphicHeight + buttonBarPrefHeight);
expandableContentPrefHeight = h - (headerBarPrefHeight + headerPrefHeight + contentAndGraphicHeight + buttonBarPrefHeight);
} else {
// content gets the lowest precedence
expandableContentPrefHeight = isExpanded() ? expandableContent.prefHeight(w) : 0;
contentAreaHeight = h - (headerPrefHeight + expandableContentPrefHeight + buttonBarPrefHeight);
contentAreaHeight = h - (headerBarPrefHeight + headerPrefHeight + expandableContentPrefHeight + buttonBarPrefHeight);
contentAndGraphicHeight = hasHeader ? contentAreaHeight : Math.max(graphicPrefHeight, contentAreaHeight);
}

if (headerBar != null) {
layoutInArea(headerBar, 0, 0, w, headerBarPrefHeight, 0, HPos.LEFT, VPos.TOP);
}

double x = leftPadding;
double y = topPadding;
double y = topPadding + headerBarPrefHeight;

if (! hasHeader) {
if (graphic != null) {
Expand Down Expand Up @@ -933,6 +1018,7 @@ protected Node createDetailsButton() {

/** {@inheritDoc} */
@Override protected double computeMinWidth(double height) {
double headerBarMinWidth = getHeaderBarInternal() instanceof HeaderBar hb ? hb.minWidth(height) : 0;
double headerMinWidth = hasHeader() ? getActualHeader().minWidth(height) + 10 : 0;
double contentMinWidth = getActualContent().minWidth(height);
double buttonBarMinWidth = buttonBar == null ? 0 : buttonBar.minWidth(height);
Expand All @@ -949,13 +1035,14 @@ protected Node createDetailsButton() {
Math.max(Math.max(headerMinWidth, expandableContentMinWidth), Math.max(contentMinWidth, buttonBarMinWidth)) +
snappedRightInset();

return snapSizeX(minWidth);
return snapSizeX(Math.max(minWidth, headerBarMinWidth));
}

/** {@inheritDoc} */
@Override protected double computeMinHeight(double width) {
final boolean hasHeader = hasHeader();

double headerBarMinHeight = getHeaderBarInternal() instanceof HeaderBar hb ? hb.minHeight(width) : 0;
double headerMinHeight = hasHeader ? getActualHeader().minHeight(width) : 0;
double buttonBarMinHeight = buttonBar == null ? 0 : buttonBar.minHeight(width);

Expand All @@ -976,6 +1063,7 @@ protected Node createDetailsButton() {
}

double minHeight = snappedTopInset() +
headerBarMinHeight +
headerMinHeight +
Math.max(graphicMinHeight, contentMinHeight) +
expandableContentMinHeight +
Expand All @@ -991,6 +1079,8 @@ protected Node createDetailsButton() {
double contentPrefWidth = getActualContent().prefWidth(height);
double buttonBarPrefWidth = buttonBar == null ? 0 : buttonBar.prefWidth(height);
double graphicPrefWidth = getActualGraphic().prefWidth(height);
double headerBarPrefWidth = getHeaderBarInternal() instanceof HeaderBar hb
? Utils.boundedSize(hb.prefWidth(height), hb.minWidth(height), hb.maxWidth(height)) : 0;

double expandableContentPrefWidth = 0;
final Node expandableContent = getExpandableContent();
Expand All @@ -1003,7 +1093,7 @@ protected Node createDetailsButton() {
Math.max(Math.max(headerPrefWidth, expandableContentPrefWidth), Math.max(contentPrefWidth, buttonBarPrefWidth)) +
snappedRightInset();

return snapSizeX(prefWidth);
return snapSizeX(Math.max(prefWidth, headerBarPrefWidth));
}

/** {@inheritDoc} */
Expand All @@ -1012,6 +1102,8 @@ protected Node createDetailsButton() {

double headerPrefHeight = hasHeader ? getActualHeader().prefHeight(width) : 0;
double buttonBarPrefHeight = buttonBar == null ? 0 : buttonBar.prefHeight(width);
double headerBarPrefHeight = getHeaderBarInternal() instanceof HeaderBar hb
? Utils.boundedSize(hb.prefHeight(width), hb.minHeight(width), hb.maxHeight(width)) : 0;

Node graphic = getActualGraphic();
double graphicPrefWidth = hasHeader ? 0 : graphic.prefWidth(-1);
Expand All @@ -1029,6 +1121,7 @@ protected Node createDetailsButton() {
}

double prefHeight = snappedTopInset() +
headerBarPrefHeight +
headerPrefHeight +
Math.max(graphicPrefHeight, contentPrefHeight) +
expandableContentPrefHeight +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@
package test.javafx.scene.control;

import static org.junit.jupiter.api.Assertions.*;

import javafx.collections.ObservableList;
import javafx.css.PseudoClass;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.scene.Node;
Expand All @@ -37,6 +39,7 @@
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HeaderBar;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
Expand Down Expand Up @@ -161,4 +164,27 @@ public void layoutIsRequestedWhenButtonTypesChange() {
dialogPane.getButtonTypes().add(ButtonType.OK);
assertTrue(dialogPane.isNeedsLayout());
}

@Test
public void headerBarIsLocatedAtTopOfDialogPane() {
var headerBar = new HeaderBar();
headerBar.setMinHeight(20);
headerBar.setPrefHeight(20);
dialogPane.setHeaderBar(headerBar);
dialogPane.resize(1000, 1000);
dialogPane.applyCss();
dialogPane.layout();

assertEquals(
new BoundingBox(0, 0, 1000, 20),
dialogPane.lookup("HeaderBar").getLayoutBounds());

dialogPane.getChildren().stream()
.filter(Node::isVisible)
.filter(c -> c != headerBar)
.forEach(child ->
assertTrue(child.getLayoutY() >= headerBar.getHeight(), () ->
child.getClass().getSimpleName() + " must be located below HeaderBar, layoutY = " + child.getLayoutBounds())
);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2025, 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
Expand Down Expand Up @@ -102,6 +102,17 @@ public void testDialogPrefHeight() {
assertEquals(prefHeight, dialog.getDialogPane().getPrefHeight(), 0);
}

@Test
public void testInitialDialogPaneIsAttachedToScene() {
class TestDialog extends Dialog<ButtonType> {
TestDialog() {
assertNotNull(getDialogPane().getScene());
}
}

assertDoesNotThrow(TestDialog::new);
}

@Test
public void testAddAndRemoveEventHandler() {
var handler = new TestHandler();
Expand Down
Loading