Skip to content

Commit 5b448b8

Browse files
author
Marius Hanl
committed
8296387: [Tooltip, CSS] -fx-show-delay is only applied to the first tooltip that is shown before it is displayed
Reviewed-by: angorya, kcr
1 parent 0ce4e6f commit 5b448b8

File tree

9 files changed

+606
-37
lines changed

9 files changed

+606
-37
lines changed

modules/javafx.controls/src/main/java/javafx/scene/control/Tooltip.java

+7-11
Original file line numberDiff line numberDiff line change
@@ -859,7 +859,6 @@ private static class TooltipBehavior {
859859
private double lastMouseY;
860860

861861
private boolean hideOnExit;
862-
private boolean cssForced = false;
863862

864863
TooltipBehavior(final boolean hideOnExit) {
865864
this.hideOnExit = hideOnExit;
@@ -996,16 +995,13 @@ private static class TooltipBehavior {
996995
}
997996
hideTimer.playFromStart();
998997
} else {
999-
// Force the CSS to be processed for the tooltip so that it uses the
1000-
// appropriate timings for showDelay, showDuration, and hideDelay.
1001-
if (!cssForced) {
1002-
double opacity = t.getOpacity();
1003-
t.setOpacity(0);
1004-
t.show(owner);
1005-
t.hide();
1006-
t.setOpacity(opacity);
1007-
cssForced = true;
1008-
}
998+
// We need to copy the stylesheet from the owner so that we get all the defined tooltip styles.
999+
// Note that this is normally done when showing the tooltip,
1000+
// which is too late for some properties.
1001+
PopupWindowHelper.applyStylesheetFromOwner(t, owner);
1002+
// Force the CSS to be processed for the tooltip so that it uses the appropriate timings for
1003+
// showDelay, showDuration, and hideDelay.
1004+
t.bridge.applyCss();
10091005

10101006
// Start / restart the timer and make sure the tooltip
10111007
// is marked as activated.

modules/javafx.controls/src/test/java/test/javafx/scene/control/TooltipTest.java

+193-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -26,8 +26,11 @@
2626
package test.javafx.scene.control;
2727

2828
import javafx.css.CssMetaData;
29+
2930
import static test.com.sun.javafx.scene.control.infrastructure.ControlTestUtils.*;
3031

32+
import javafx.scene.input.MouseEvent;
33+
import javafx.util.Duration;
3134
import test.com.sun.javafx.pgstub.StubToolkit;
3235
import com.sun.javafx.tk.Toolkit;
3336
import javafx.beans.property.BooleanProperty;
@@ -47,25 +50,41 @@
4750
import javafx.scene.shape.Rectangle;
4851
import javafx.scene.text.Font;
4952
import javafx.scene.text.TextAlignment;
50-
import static org.junit.Assert.*;
5153

54+
import static org.junit.Assert.*;
5255

5356
import org.junit.Before;
57+
import org.junit.After;
5458
import org.junit.Ignore;
5559
import org.junit.Test;
60+
import test.com.sun.javafx.scene.control.infrastructure.MouseEventGenerator;
61+
import test.com.sun.javafx.scene.control.infrastructure.StageLoader;
62+
63+
import java.nio.charset.StandardCharsets;
64+
import java.util.Base64;
5665

5766
public class TooltipTest {
5867
private TooltipShim toolTip;//Empty string
5968
private TooltipShim dummyToolTip;//Empty string
6069

61-
@Before public void setup() {
62-
assertTrue(Toolkit.getToolkit() instanceof StubToolkit); // Ensure StubToolkit is loaded
70+
private StageLoader stageLoader;
71+
private StubToolkit toolkit;
6372

73+
@Before
74+
public void setup() {
6475
toolTip = new TooltipShim();
6576
dummyToolTip = new TooltipShim("dummy");
66-
}
6777

78+
toolkit = (StubToolkit) Toolkit.getToolkit();
79+
toolkit.setAnimationTime(0);
80+
}
6881

82+
@After
83+
public void tearDown() {
84+
if (stageLoader != null) {
85+
stageLoader.dispose();
86+
}
87+
}
6988

7089
/*********************************************************************
7190
* Tests for the constructors *
@@ -318,7 +337,7 @@ public class TooltipTest {
318337

319338
@Test public void whenWrapTextIsSpecifiedViaCSSAndIsNotBound_CssMetaData_isSettable_ReturnsTrue() {
320339
CssMetaData styleable = ((StyleableProperty)toolTip.wrapTextProperty()).getCssMetaData();
321-
assertTrue(styleable.isSettable(toolTip.get_bridge()));
340+
assertTrue(styleable.isSettable(toolTip.get_bridge()));
322341
}
323342

324343
@Test public void canSpecifyWrapTextViaCSS() {
@@ -328,15 +347,15 @@ public class TooltipTest {
328347

329348
@Test public void whenFontIsBound_CssMetaData_isSettable_ReturnsFalse() {
330349
CssMetaData styleable = ((StyleableProperty)toolTip.fontProperty()).getCssMetaData();
331-
assertTrue(styleable.isSettable(toolTip.get_bridge()));
350+
assertTrue(styleable.isSettable(toolTip.get_bridge()));
332351
ObjectProperty<Font> other = new SimpleObjectProperty<>();
333352
toolTip.fontProperty().bind(other);
334-
assertFalse(styleable.isSettable(toolTip.get_bridge()));
353+
assertFalse(styleable.isSettable(toolTip.get_bridge()));
335354
}
336355

337356
@Test public void whenFontIsSpecifiedViaCSSAndIsNotBound_CssMetaData_isSettable_ReturnsTrue() {
338357
CssMetaData styleable = ((StyleableProperty)toolTip.fontProperty()).getCssMetaData();
339-
assertTrue(styleable.isSettable(toolTip.get_bridge()));
358+
assertTrue(styleable.isSettable(toolTip.get_bridge()));
340359
}
341360

342361
@Test public void canSpecifyFontViaCSS() {
@@ -349,12 +368,12 @@ public class TooltipTest {
349368
assertTrue(styleable.isSettable(toolTip.get_bridge()));
350369
ObjectProperty<Node> other = new SimpleObjectProperty<>();
351370
toolTip.graphicProperty().bind(other);
352-
assertFalse(styleable.isSettable(toolTip.get_bridge()));
371+
assertFalse(styleable.isSettable(toolTip.get_bridge()));
353372
}
354373

355374
@Test public void whenGraphicIsSpecifiedViaCSSAndIsNotBound_CssMetaData_isSettable_ReturnsTrue() {
356375
CssMetaData styleable = ((StyleableProperty)toolTip.graphicProperty()).getCssMetaData();
357-
assertTrue(styleable.isSettable(toolTip.get_bridge()));
376+
assertTrue(styleable.isSettable(toolTip.get_bridge()));
358377
}
359378

360379
@Ignore("CSS sets graphicProperty indirectly")
@@ -366,14 +385,14 @@ public class TooltipTest {
366385

367386
@Test public void whenContentDisplayIsBound_CssMetaData_isSettable_ReturnsFalse() {
368387
CssMetaData styleable = ((StyleableProperty)toolTip.contentDisplayProperty()).getCssMetaData();
369-
assertTrue(styleable.isSettable(toolTip.get_bridge()));
388+
assertTrue(styleable.isSettable(toolTip.get_bridge()));
370389
ObjectProperty<ContentDisplay> other = new SimpleObjectProperty<>();
371390
toolTip.contentDisplayProperty().bind(other);
372-
assertFalse(styleable.isSettable(toolTip.get_bridge()));
391+
assertFalse(styleable.isSettable(toolTip.get_bridge()));
373392
}
374-
@Test public void whenContentDisplayIsSpecifiedViaCSSAndIsNotBound_CssMetaData_isSettable_ReturnsTrue() {
393+
@Test public void whenContentDisplayIsSpecifiedViaCSSAndIsNotBound_CssMetaData_isSettable_ReturnsTrue() {
375394
CssMetaData styleable = ((StyleableProperty)toolTip.contentDisplayProperty()).getCssMetaData();
376-
assertTrue(styleable.isSettable(toolTip.get_bridge()));
395+
assertTrue(styleable.isSettable(toolTip.get_bridge()));
377396
}
378397

379398
@Test public void canSpecifyContentDisplayViaCSS() {
@@ -383,15 +402,15 @@ public class TooltipTest {
383402

384403
@Test public void whenGraphicTextGapIsBound_CssMetaData_isSettable_ReturnsFalse() {
385404
CssMetaData styleable = ((StyleableProperty)toolTip.graphicTextGapProperty()).getCssMetaData();
386-
assertTrue(styleable.isSettable(toolTip.get_bridge()));
405+
assertTrue(styleable.isSettable(toolTip.get_bridge()));
387406
DoubleProperty other = new SimpleDoubleProperty();
388407
toolTip.graphicTextGapProperty().bind(other);
389-
assertFalse(styleable.isSettable(toolTip.get_bridge()));
408+
assertFalse(styleable.isSettable(toolTip.get_bridge()));
390409
}
391410

392411
@Test public void whenGraphicTextGapIsSpecifiedViaCSSAndIsNotBound_CssMetaData_isSettable_ReturnsTrue() {
393412
CssMetaData styleable = ((StyleableProperty)toolTip.graphicTextGapProperty()).getCssMetaData();
394-
assertTrue(styleable.isSettable(toolTip.get_bridge()));
413+
assertTrue(styleable.isSettable(toolTip.get_bridge()));
395414
}
396415

397416
@Test public void canSpecifyGraphicTextGapViaCSS() {
@@ -531,5 +550,161 @@ public class TooltipTest {
531550
}
532551
}
533552

553+
/**
554+
* A {@link Tooltip} once was showing and quickly hiding itself in order to process the CSS.
555+
* This was changed in <a href="https://bugs.openjdk.org/browse/JDK-8296387">JDK-8296387</a>
556+
* and this test ensure that this is the case.
557+
*/
558+
@Test
559+
public void testTooltipShouldNotBeShownBeforeDelayIsUp() {
560+
toolTip.showingProperty().addListener(inv -> fail());
561+
Rectangle rect = new Rectangle(0, 0, 100, 100);
562+
563+
stageLoader = new StageLoader(rect);
564+
565+
Tooltip.install(rect, toolTip);
566+
567+
MouseEvent mouseEvent = MouseEventGenerator.generateMouseEvent(MouseEvent.MOUSE_MOVED, 1, 1);
568+
rect.fireEvent(mouseEvent);
569+
}
570+
571+
@Test
572+
public void testTooltipShouldNotBeShownBeforeDefaultDelay() {
573+
Rectangle rect = new Rectangle(0, 0, 100, 100);
574+
575+
stageLoader = new StageLoader(rect);
576+
577+
Tooltip.install(rect, toolTip);
578+
579+
MouseEvent mouseEvent = MouseEventGenerator.generateMouseEvent(MouseEvent.MOUSE_MOVED, 1, 1);
580+
rect.fireEvent(mouseEvent);
581+
582+
assertFalse(toolTip.isShowing());
583+
584+
toolkit.setAnimationTime(999);
585+
586+
assertFalse(toolTip.isShowing());
587+
}
588+
589+
@Test
590+
public void testTooltipShouldBeShownAfterDefaultDelay() {
591+
Rectangle rect = new Rectangle(0, 0, 100, 100);
592+
593+
stageLoader = new StageLoader(rect);
594+
595+
Tooltip.install(rect, toolTip);
596+
597+
assertFalse(toolTip.isShowing());
598+
599+
assertTooltipShownAfter(rect, 1000);
600+
assertTooltipHiddenAfter(rect, 200);
601+
}
602+
603+
@Test
604+
public void testTooltipShouldBeHiddenAfterHideDelay() {
605+
int delay = 50;
606+
toolTip.setHideDelay(Duration.millis(delay));
607+
608+
Rectangle rect = new Rectangle(0, 0, 100, 100);
609+
610+
stageLoader = new StageLoader(rect);
611+
612+
Tooltip.install(rect, toolTip);
613+
614+
assertFalse(toolTip.isShowing());
615+
616+
assertTooltipShownAfter(rect, 1000);
617+
assertTooltipHiddenAfter(rect, delay);
618+
}
619+
620+
@Test
621+
public void testTooltipShouldBeShownAfterSetShowDelay() {
622+
int delay = 200;
623+
toolTip.setShowDelay(Duration.millis(delay));
624+
625+
Rectangle rect = new Rectangle(0, 0, 100, 100);
626+
627+
stageLoader = new StageLoader(rect);
628+
629+
Tooltip.install(rect, toolTip);
630+
631+
assertFalse(toolTip.isShowing());
632+
633+
assertTooltipShownAfter(rect, delay);
634+
assertTooltipHiddenAfter(rect, 200);
635+
}
636+
637+
@Test
638+
public void testTooltipShouldBeShownAfterSetStyleShowDelay() {
639+
toolTip.setStyle("-fx-show-delay: 200ms;");
640+
641+
Rectangle rect = new Rectangle(0, 0, 100, 100);
642+
643+
stageLoader = new StageLoader(rect);
644+
645+
Tooltip.install(rect, toolTip);
646+
647+
assertFalse(toolTip.isShowing());
648+
649+
assertTooltipShownAfter(rect, 200);
650+
assertTooltipHiddenAfter(rect, 200);
651+
}
652+
653+
@Test
654+
public void testTooltipShouldBeShownAfterSetCssShowDelay() {
655+
Rectangle rect = new Rectangle(0, 0, 100, 100);
656+
657+
stageLoader = new StageLoader(rect);
658+
stageLoader.getStage().getScene().getStylesheets().setAll(toBase64(".tooltip { -fx-show-delay: 200ms; }"));
659+
660+
Tooltip.install(rect, toolTip);
661+
662+
assertFalse(toolTip.isShowing());
663+
664+
assertTooltipShownAfter(rect, 200);
665+
assertTooltipHiddenAfter(rect, 200);
666+
}
667+
668+
@Test
669+
public void testTooltipChangeShowDelayCss() {
670+
Rectangle rect = new Rectangle(0, 0, 100, 100);
671+
672+
stageLoader = new StageLoader(rect);
673+
stageLoader.getStage().getScene().getStylesheets().setAll(toBase64(".tooltip { -fx-show-delay: 200ms; }"));
674+
675+
Tooltip.install(rect, toolTip);
676+
677+
assertFalse(toolTip.isShowing());
678+
679+
assertTooltipShownAfter(rect, 200);
680+
assertTooltipHiddenAfter(rect, 200);
681+
682+
stageLoader.getStage().getScene().getStylesheets().setAll(toBase64(".tooltip { -fx-show-delay: 450ms; }"));
683+
684+
assertTooltipShownAfter(rect, 450);
685+
assertTooltipHiddenAfter(rect, 200);
686+
}
687+
688+
private void assertTooltipShownAfter(Rectangle rect, int millis) {
689+
MouseEvent mouseEvent = MouseEventGenerator.generateMouseEvent(MouseEvent.MOUSE_MOVED, 1, 1);
690+
rect.fireEvent(mouseEvent);
691+
692+
toolkit.setAnimationTime(toolkit.getCurrentTime() + millis);
693+
694+
assertTrue(toolTip.isShowing());
695+
}
696+
697+
private void assertTooltipHiddenAfter(Rectangle rect, int millis) {
698+
MouseEvent mouseEvent = MouseEventGenerator.generateMouseEvent(MouseEvent.MOUSE_EXITED, -1, -1);
699+
rect.fireEvent(mouseEvent);
700+
701+
toolkit.setAnimationTime(toolkit.getCurrentTime() + millis);
702+
703+
assertFalse(toolTip.isShowing());
704+
}
705+
706+
private String toBase64(String css) {
707+
return "data:base64," + Base64.getUrlEncoder().encodeToString(css.getBytes(StandardCharsets.UTF_8));
708+
}
534709

535710
}

modules/javafx.graphics/src/main/java/com/sun/javafx/stage/PopupWindowHelper.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -63,6 +63,10 @@ protected void visibleChangedImpl(Window window, boolean visible) {
6363
popupWindowAccessor.doVisibleChanged(window, visible);
6464
}
6565

66+
public static void applyStylesheetFromOwner(PopupWindow popupWindow, Window owner) {
67+
popupWindowAccessor.applyStylesheetFromOwner(popupWindow, owner);
68+
}
69+
6670
public static ObservableList<Node> getContent(PopupWindow popupWindow) {
6771
return popupWindowAccessor.getContent(popupWindow);
6872
}
@@ -79,5 +83,6 @@ public interface PopupWindowAccessor {
7983
ObservableList<Node> getContent(PopupWindow popupWindow);
8084
void doVisibleChanging(Window window, boolean visible);
8185
void doVisibleChanged(Window window, boolean visible);
86+
void applyStylesheetFromOwner(PopupWindow popupWindow, Window owner);
8287
}
8388
}

0 commit comments

Comments
 (0)