/*
* Copyright (c) 2012, Codename One 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. Codename One 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 Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codename1.components;
import com.codename1.ui.Command;
import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.Form;
import com.codename1.ui.Image;
import com.codename1.ui.Label;
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.geom.Rectangle;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.LayeredLayout;
import com.codename1.ui.layouts.Layout;
import com.codename1.ui.plaf.Border;
import com.codename1.ui.plaf.Style;
import com.codename1.ui.plaf.UIManager;
/**
* <p>Unlike a regular dialog the interaction dialog only looks like a dialog,
* it resides in the layered pane and can be used to implement features where
* interaction with the background form is still required.<br>
* Since this code is designed for interaction all "dialogs" created thru here are
* modless and never block.</p>
*
* <script src="https://gist.github.com/codenameone/d1db2033981c835fb925.js"></script>
* <img src="https://www.codenameone.com/img/developer-guide/components-interaction-dialog.png" alt="InteractionDialog Sample" />
*
* @author Shai Almog
*/
public class InteractionDialog extends Container {
private final Label title = new Label();
private final Container titleArea = new Container(new BorderLayout());
private final Container contentPane;
private boolean animateShow = true;
private boolean repositionAnimation = true;
/**
* Whether the interaction dialog uses the form layered pane of the regular layered pane
*/
private boolean formMode;
/**
* Default constructor with no title
*/
public InteractionDialog() {
super(new BorderLayout());
contentPane = new Container();
init();
}
/**
* Default constructor with layout
* @param l layout
*/
public InteractionDialog(Layout l) {
super(new BorderLayout());
contentPane = new Container(l);
init();
}
/**
* Constructor with dialog title
*
* @param title the title of the dialog
*/
public InteractionDialog(String title) {
super(new BorderLayout());
contentPane = new Container();
this.title.setText(title);
init();
}
/**
* Constructor with dialog title
*
* @param title the title of the dialog
* @param l the layout for the content pane
*/
public InteractionDialog(String title, Layout l) {
super(new BorderLayout());
contentPane = new Container(l);
this.title.setText(title);
init();
}
private void init() {
setUIID("Dialog");
title.setUIID("DialogTitle");
contentPane.setUIID("DialogContentPane");
super.addComponent(BorderLayout.NORTH, titleArea);
titleArea.addComponent(BorderLayout.CENTER, title);
super.addComponent(BorderLayout.CENTER, contentPane);
setGrabsPointerEvents(true);
}
/**
* Returns the body of the interaction dialog
* @return the container where the elements of the interaction dialog are added.
*/
public Container getContentPane() {
return contentPane;
}
/**
* {@inheritDoc}
*/
public void setScrollable(boolean scrollable) {
getContentPane().setScrollable(scrollable);
}
/**
* {@inheritDoc}
*/
public Layout getLayout() {
return contentPane.getLayout();
}
/**
* {@inheritDoc}
*/
public String getTitle() {
return title.getText();
}
/**
* {@inheritDoc}
*/
public void addComponent(Component cmp) {
contentPane.addComponent(cmp);
}
/**
* {@inheritDoc}
*/
public void addComponent(Object constraints, Component cmp) {
contentPane.addComponent(constraints, cmp);
}
/**
* {@inheritDoc}
*/
public void addComponent(int index, Object constraints, Component cmp) {
contentPane.addComponent(index, constraints, cmp);
}
/**
* {@inheritDoc}
*/
public void addComponent(int index, Component cmp) {
contentPane.addComponent(index, cmp);
}
/**
* {@inheritDoc}
*/
public void removeAll() {
contentPane.removeAll();
}
/**
* {@inheritDoc}
*/
public void removeComponent(Component cmp) {
contentPane.removeComponent(cmp);
}
/**
* {@inheritDoc}
*/
public Label getTitleComponent() {
return title;
}
/**
* {@inheritDoc}
*/
public void setLayout(Layout layout) {
contentPane.setLayout(layout);
}
/**
* {@inheritDoc}
*/
public void setTitle(String title) {
this.title.setText(title);
}
private Container getLayeredPane(Form f) {
//return f.getLayeredPane();
Container c;
if(formMode) {
c = (Container)f.getFormLayeredPane(InteractionDialog.class, false);
} else {
c = (Container)f.getLayeredPane(InteractionDialog.class, false);
}
if (!(c.getLayout() instanceof LayeredLayout)) {
c.setLayout(new LayeredLayout());
}
return c;
}
/**
* This method shows the form as a modal alert allowing us to produce a behavior
* of an alert/dialog box. This method will block the calling thread even if the
* calling thread is the EDT. Notice that this method will not release the block
* until dispose is called even if show() from another form is called!
* <p>Modal dialogs Allow the forms "content" to "hang in mid air" this is especially useful for
* dialogs where you would want the underlying form to "peek" from behind the
* form.
*
* @param top space in pixels between the top of the screen and the form
* @param bottom space in pixels between the bottom of the screen and the form
* @param left space in pixels between the left of the screen and the form
* @param right space in pixels between the right of the screen and the form
*/
public void show(int top, int bottom, int left, int right) {
Form f = Display.getInstance().getCurrent();
getUnselectedStyle().setMargin(TOP, top);
getUnselectedStyle().setMargin(BOTTOM, bottom);
getUnselectedStyle().setMargin(LEFT, left);
getUnselectedStyle().setMargin(RIGHT, right);
getUnselectedStyle().setMarginUnit(new byte[] {Style.UNIT_TYPE_PIXELS, Style.UNIT_TYPE_PIXELS, Style.UNIT_TYPE_PIXELS, Style.UNIT_TYPE_PIXELS});
// might occur when showing the dialog twice...
remove();
getLayeredPane(f).addComponent(BorderLayout.center(this));
if(animateShow) {
int x = left + (f.getWidth() - right - left) / 2;
int y = top + (f.getHeight() - bottom - top) / 2;
if(repositionAnimation) {
getParent().setX(x);
getParent().setY(y);
getParent().setWidth(1);
getParent().setHeight(1);
} else {
getParent().setX(getX());
getParent().setY(getY());
setX(0);
setY(0);
getParent().setWidth(getWidth());
getParent().setHeight(getHeight());
}
getLayeredPane(f).animateLayout(400);
} else {
getLayeredPane(f).revalidate();
}
/*
Form f = Display.getInstance().getCurrent();
f.getLayeredPane().setLayout(new BorderLayout());
getUnselectedStyle().setMargin(TOP, top);
getUnselectedStyle().setMargin(BOTTOM, bottom);
getUnselectedStyle().setMargin(LEFT, left);
getUnselectedStyle().setMargin(RIGHT, right);
getUnselectedStyle().setMarginUnit(new byte[] {Style.UNIT_TYPE_PIXELS, Style.UNIT_TYPE_PIXELS, Style.UNIT_TYPE_PIXELS, Style.UNIT_TYPE_PIXELS});
f.getLayeredPane().addComponent(BorderLayout.CENTER, this);
if(animateShow) {
int x = left + (f.getWidth() - right - left) / 2;
int y = top + (f.getHeight() - bottom - top) / 2;
setX(x);
setY(y);
setWidth(1);
setHeight(1);
f.getLayeredPane().animateLayout(400);
} else {
f.getLayeredPane().revalidate();
}
*/
}
/**
* Removes the interaction dialog from view
*/
public void dispose() {
Container p = getParent();
if(p != null) {
Form f = p.getComponentForm();
if(f != null) {
if(animateShow) {
if(repositionAnimation) {
setX(getX() + getWidth() / 2);
setY(getY() + getHeight()/ 2);
setWidth(1);
setHeight(1);
}
p.animateUnlayoutAndWait(400, 100);
}
Container pp = getLayeredPane(f);
remove();
p.remove();
pp.removeAll();
pp.revalidate();
} else {
p.remove();
}
}
}
/**
* Removes the interaction dialog from view with an animation to the left
*/
public void disposeToTheLeft() {
Container p = getParent();
if(p != null) {
Form f = p.getComponentForm();
if(f != null) {
setX(-getWidth());
if(animateShow) {
p.animateUnlayoutAndWait(400, 255);
} else {
p.revalidate();
}
Container pp = getLayeredPane(f);
remove();
p.remove();
pp.removeAll();
pp.revalidate();
} else {
remove();
}
}
}
/**
* Will return true if the dialog is currently showing
* @return true if showing
*/
public boolean isShowing() {
return getParent() != null;
}
/**
* Indicates whether show/dispose should be animated or not
* @return the animateShow
*/
public boolean isAnimateShow() {
return animateShow;
}
/**
* Indicates whether show/dispose should be animated or not
* @param animateShow the animateShow to set
*/
public void setAnimateShow(boolean animateShow) {
this.animateShow = animateShow;
}
/**
* A popup dialog is shown with the context of a component and its selection, it is disposed seamlessly if the back button is pressed
* or if the user touches outside its bounds. It can optionally provide an arrow in the theme to point at the context component. The popup
* dialog has the PopupDialog style by default.
*
* @param c the context component which is used to position the dialog and can also be pointed at
*/
public void showPopupDialog(Component c) {
Rectangle componentPos = c.getSelectedRect();
componentPos.setX(componentPos.getX() - c.getScrollX());
componentPos.setY(componentPos.getY() - c.getScrollY());
showPopupDialog(componentPos);
}
/**
* A popup dialog is shown with the context of a component and its selection, it is disposed seamlessly if the back button is pressed
* or if the user touches outside its bounds. It can optionally provide an arrow in the theme to point at the context component. The popup
* dialog has the PopupDialog style by default.
*
* @param rect the screen rectangle to which the popup should point
*/
public void showPopupDialog(Rectangle rect) {
if(getUIID().equals("Dialog")) {
setUIID("PopupDialog");
if(getTitleComponent().getUIID().equals("DialogTitle")) {
getTitleComponent().setUIID("PopupDialogTitle");
}
getContentPane().setUIID("PopupContentPane");
}
Component contentPane = getContentPane();
Label title = getTitleComponent();
UIManager manager = getUIManager();
String dialogTitle = title.getText();
// hide the title if no text is there to allow the styles of the dialog title to disappear, we need this code here since otherwise the
// preferred size logic of the dialog won't work with large title borders
if((dialogTitle != null || dialogTitle.length() == 0) && manager.isThemeConstant("hideEmptyTitleBool", false)) {
boolean b = getTitle().length() > 0;
titleArea.setVisible(b);
getTitleComponent().setVisible(b);
if(!b && manager.isThemeConstant("shrinkPopupTitleBool", true)) {
getTitleComponent().setPreferredSize(new Dimension(0,0));
getTitleComponent().getStyle().setBorder(null);
titleArea.setPreferredSize(new Dimension(0,0));
if(getContentPane().getClientProperty("$ENLARGED_POP") == null) {
getContentPane().putClientProperty("$ENLARGED_POP", Boolean.TRUE);
int cpPaddingTop = getContentPane().getStyle().getPaddingTop();
int titlePT = getTitleComponent().getStyle().getPaddingTop();
byte[] pu = getContentPane().getStyle().getPaddingUnit();
if(pu == null){
pu = new byte[4];
}
pu[0] = Style.UNIT_TYPE_PIXELS;
getContentPane().getStyle().setPaddingUnit(pu);
int pop = Display.getInstance().convertToPixels(manager.getThemeConstant("popupNoTitleAddPaddingInt", 1), false);
getContentPane().getStyle().setPadding(TOP, pop + cpPaddingTop + titlePT);
}
}
}
// allows a text area to recalculate its preferred size if embedded within a dialog
revalidate();
Style contentPaneStyle = getStyle();
boolean restoreArrow = false;
if(manager.isThemeConstant(getUIID()+ "ArrowBool", false)) {
Image t = manager.getThemeImageConstant(getUIID() + "ArrowTopImage");
Image b = manager.getThemeImageConstant(getUIID() + "ArrowBottomImage");
Image l = manager.getThemeImageConstant(getUIID() + "ArrowLeftImage");
Image r = manager.getThemeImageConstant(getUIID() + "ArrowRightImage");
Border border = contentPaneStyle.getBorder();
if(border != null) {
border.setImageBorderSpecialTile(t, b, l, r, rect);
restoreArrow = true;
}
}
calcPreferredSize();
int prefHeight = getPreferredH();
int prefWidth = getPreferredW();
if(contentPaneStyle.getBorder() != null) {
prefWidth = Math.max(contentPaneStyle.getBorder().getMinimumWidth(), prefWidth);
prefHeight = Math.max(contentPaneStyle.getBorder().getMinimumHeight(), prefHeight);
}
Form f = Display.getInstance().getCurrent();
int availableHeight = getLayeredPane(f).getParent().getHeight();
int availableWidth =getLayeredPane(f).getParent().getWidth();
int width = Math.min(availableWidth, prefWidth);
int x = 0;
int y = 0;
boolean showPortrait = Display.getInstance().isPortrait();
// if we don't have enough space then disregard device orientation
if(showPortrait) {
if(availableHeight < (availableWidth - rect.getWidth()) / 2) {
showPortrait = false;
}
} else {
if(availableHeight / 2 > availableWidth - rect.getWidth()) {
showPortrait = true;
}
}
if(showPortrait) {
if(width < availableWidth) {
int idealX = rect.getX() - width / 2 + rect.getSize().getWidth() / 2;
// if the ideal position is less than 0 just use 0
if(idealX > 0) {
// if the idealX is too far to the right just align to the right
if(idealX + width > availableWidth) {
x = availableWidth - width;
} else {
x = idealX;
}
}
}
if(rect.getY() < availableHeight / 2) {
// popup downwards
y = rect.getY();
int height = Math.min(prefHeight, availableHeight - y);
show(y, Math.max(0, availableHeight - height - y), x, Math.max(0, availableWidth - width - x));
} else {
// popup upwards
int height = Math.min(prefHeight, rect.getY() - getLayeredPane(f).getAbsoluteY());
y = rect.getY() - height - getLayeredPane(f).getAbsoluteY();
show(y, Math.max(0, getLayeredPane(f).getComponentForm().getHeight() - rect.getY()), x, Math.max(0, availableWidth - width - x));
}
} else {
int height = Math.min(prefHeight, availableHeight);
if(height < availableHeight) {
int idealY = rect.getY() - height / 2 + rect.getSize().getHeight() / 2;
// if the ideal position is less than 0 just use 0
if(idealY > 0) {
// if the idealY is too far up just align to the top
if(idealY + height > availableHeight) {
y = availableHeight - height;
} else {
y = idealY;
}
}
}
if(prefWidth > rect.getX()) {
// popup right
x = rect.getX() + rect.getSize().getWidth();
if(x + prefWidth > availableWidth){
x = availableWidth - prefWidth;
}
width = Math.min(prefWidth, availableWidth - x);
show(y, availableHeight - height - y, Math.max(0, x), Math.max(0, availableWidth - width - x));
} else {
// popup left
width = Math.min(prefWidth, availableWidth - (availableWidth - rect.getX()));
x = rect.getX() - width;
show(y, availableHeight - height - y, Math.max(0, x), Math.max(0, availableWidth - width - x));
}
}
/*if(restoreArrow) {
contentPaneStyle.getBorder().clearImageBorderSpecialTile();
}*/
}
/**
* Simple setter to set the Dialog uiid
*
* @param uiid the id for the dialog
*/
public void setDialogUIID(String uiid){
getContentPane().setUIID(uiid);
}
/**
* Returns the uiid of the dialog
*
* @return the uiid of the dialog
*/
public String getDialogUIID(){
return getContentPane().getUIID();
}
/**
* Simple getter to get the Dialog Style
*
* @return the style of the dialog
*/
public Style getDialogStyle(){
return getContentPane().getStyle();
}
/**
* Repositions the component so the animation will "grow/shrink" when showing/disposing
* @return the repositionAnimation
*/
public boolean isRepositionAnimation() {
return repositionAnimation;
}
/**
* Repositions the component so the animation will "grow/shrink" when showing/disposing
* @param repositionAnimation the repositionAnimation to set
*/
public void setRepositionAnimation(boolean repositionAnimation) {
this.repositionAnimation = repositionAnimation;
}
/**
* Whether the interaction dialog uses the form layered pane of the regular layered pane
* @return the formMode
*/
public boolean isFormMode() {
return formMode;
}
/**
* Whether the interaction dialog uses the form layered pane of the regular layered pane
* @param formMode the formMode to set
*/
public void setFormMode(boolean formMode) {
this.formMode = formMode;
}
}