Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 108 additions & 7 deletions CodenameOne/src/com/codename1/components/InteractionDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,27 @@ public void run() {
/// between (e.g. by `#showPopupDialog(Component)`).
private boolean shownInFormMode;

/// The form the dialog should be hosted on, pinned by the popup entry points
/// (the form of the component passed to `#showPopupDialog(Component)`) and
/// consumed by `#show(int, int, int, int)` so the whole popup flow targets
/// one consistent form. When no form is pinned the show methods use
/// `Display#getCurrent()`, deferring themselves with `callSerially` while a
/// form transition is in flight: during a transition `getCurrent()` still
/// returns the outgoing form, so a dialog shown right after `Form.show()`
/// would silently attach to the form that is leaving the screen and only
/// materialize when that form is shown again -- the "dialog never appears /
/// appears later on another screen" symptom of #5193. The EDT doesn't
/// process serial calls while a transition is animating, so a deferred show
/// runs once the destination form has become the current form.
private Form popupHostForm;

/// True while a show was deferred with `callSerially` because a form
/// transition was in flight (see `#popupHostForm`). Counted by
/// `#isShowing()` so `#showDialog()` keeps blocking through the deferral
/// window; cleared by dispose so a deferred show is abandoned if the dialog
/// was disposed before it ran.
private boolean pendingShow;

private boolean pressedOutOfBounds;
private ActionListener pressedListener;
private ActionListener releasedListener;
Expand Down Expand Up @@ -289,10 +310,16 @@ private void cleanupLayer(Form f) {
// left it, so it neither nukes siblings nor lingers empty. Use the
// mode captured at show() time so we clean the pane the dialog was
// actually added to even if formMode changed in the meantime.
// getComponentCount() can't be used for the emptiness check: while
// an animation is in progress (e.g. this dialog's own dispose
// animation) a sibling's add is only queued on the change queue and
// isn't counted, so the layer would be detached with the pending
// insert flushing into it later -- losing that dialog forever.
// getChildrenAsList(true) reflects queued inserts/removals.
Container c = shownInFormMode
? f.getFormLayeredPane(InteractionDialog.class, true)
: f.getLayeredPane(InteractionDialog.class, true);
if (c.getComponentCount() == 0) {
if (c.getChildrenAsList(true).isEmpty()) {
c.remove();
}
return;
Expand All @@ -304,6 +331,17 @@ private void cleanupLayer(Form f) {
}
}

/// Resolves the form this dialog should be hosted on: the form pinned by the
/// popup entry points when available, otherwise the current form. Callers
/// that can't pin a form must defer while a transition is in flight (see
/// `#popupHostForm`) since the current form is then about to leave the screen.
private Form resolveHostForm() {
if (popupHostForm != null) {
return popupHostForm;
}
return Display.getInstance().getCurrent();
}

private Container getLayeredPane(Form f) {
//return f.getLayeredPane();
Container c;
Expand Down Expand Up @@ -347,7 +385,12 @@ protected void deinitialize() {

public void resize(final int top, final int bottom, final int left, final int right) {
if (!disposed) {
final Form f = Display.getInstance().getCurrent();
// prefer the form the dialog is actually showing on; during a form
// transition Display.getCurrent() may point at the outgoing form
Form f = getComponentForm();
if (f == null) {
f = resolveHostForm();
}

Style unselectedStyle = getUnselectedStyle();

Expand Down Expand Up @@ -386,10 +429,31 @@ public void resize(final int top, final int bottom, final int left, final int ri
/// - `left`: space in pixels between the left of the screen and the form
///
/// - `right`: space in pixels between the right of the screen and the form
public void show(int top, int bottom, int left, int right) {
public void show(final int top, final int bottom, final int left, final int right) {
getUnselectedStyle().setOpacity(255);
disposed = false;
Form f = Display.getInstance().getCurrent();
if (popupHostForm == null && Display.getInstance().isInTransition()) {
// Display.getCurrent() still returns the outgoing form while a
// transition is in flight, so attaching now would put the dialog
// on the form that is leaving the screen where it stays invisible
// until that form is shown again (#5193). The EDT doesn't process
// serial calls while a transition is animating, so this re-runs
// once the destination form has become the current form.
pendingShow = true;
Display.getInstance().callSerially(new Runnable() {
@Override
public void run() {
pendingShow = false;
if (!disposed) {
show(top, bottom, left, right);
}
}
});
return;
}
pendingShow = false;
Form f = resolveHostForm();
popupHostForm = null;
shownInFormMode = formMode;
Style unselectedStyle = getUnselectedStyle();

Expand Down Expand Up @@ -458,6 +522,7 @@ public void show(int top, int bottom, int left, int right) {
@Override
public void dispose() {
disposed = true;
pendingShow = false;
Container p = getParent();
if (p != null) {
Form f = p.getComponentForm();
Expand Down Expand Up @@ -562,6 +627,7 @@ private void disposeTo(int direction) {

private void disposeTo(int direction, final Runnable onFinish) {
disposed = true;
pendingShow = false;
final Container p = getParent();
if (p != null) {
final Form f = p.getComponentForm();
Expand Down Expand Up @@ -643,7 +709,9 @@ public void run() {
///
/// true if showing
public boolean isShowing() {
return getParent() != null;
// pendingShow covers the window where show was deferred to the end of
// an in-flight form transition, so showDialog() keeps blocking
return pendingShow || getParent() != null;
}

/// Indicates whether show/dispose should be animated or not. When true (the default)
Expand Down Expand Up @@ -858,7 +926,10 @@ public void showPopupDialog(Component c, boolean bias) {
componentPos.setX(componentPos.getX() - c.getScrollX());
componentPos.setY(componentPos.getY() - c.getScrollY());
setOwner(c);
showPopupDialog(componentPos);
// host the popup on the form the target component actually belongs to;
// Display.getCurrent() may still be the outgoing form while a transition
// is in flight, which would make the dialog appear to never show (#5193)
showPopupDialogImpl(componentPos, Display.getInstance().isPortrait(), f);
}

/// A popup dialog is shown with the context of a component and its selection. You should use `#setDisposeWhenPointerOutOfBounds(boolean)` to make it dispose
Expand Down Expand Up @@ -890,10 +961,40 @@ public void showPopupDialog(Rectangle rect, boolean bias) {
}

private void showPopupDialogImpl(Rectangle rect, boolean bias) {
showPopupDialogImpl(rect, bias, null);
}

private void showPopupDialogImpl(Rectangle rect, boolean bias, Form hostForm) {
if (rect == null) {
throw new IllegalArgumentException("rect cannot be null");
}
Form f = Display.getInstance().getCurrent();
if (hostForm == null && Display.getInstance().isInTransition()) {
// see show(int, int, int, int): the current form is about to be
// replaced so the positioning math below would run against the
// outgoing form; defer until the destination form is current
disposed = false;
pendingShow = true;
final Rectangle deferredRect = rect;
final boolean deferredBias = bias;
Display.getInstance().callSerially(new Runnable() {
@Override
public void run() {
pendingShow = false;
if (!disposed) {
showPopupDialogImpl(deferredRect, deferredBias, null);
}
}
});
return;
}
Form f = hostForm;
if (f == null) {
f = resolveHostForm();
}
// pin the host form so the show(int, int, int, int) call at the end of
// this method (and any subclass override of it) targets the same form
// used for all the positioning math below
popupHostForm = f;
Rectangle origRect = rect;
rect = new Rectangle(rect);
rect.setX(rect.getX() - getLayeredPane(f).getAbsoluteX());
Expand Down
Loading
Loading