org.zkoss.zul.Window Maven / Gradle / Ivy
/* Window.java
Purpose:
Description:
History:
Tue May 31 19:29:13 2005, Created by tomyeh
Copyright (C) 2005 Potix Corporation. All Rights Reserved.
{{IS_RIGHT
This program is distributed under LGPL Version 2.1 in the hope that
it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.zul;
import java.util.Iterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.lang.Objects;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Execution;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.IdSpace;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.SuspendNotAllowedException;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.MaximizeEvent;
import org.zkoss.zk.ui.event.MinimizeEvent;
import org.zkoss.zk.ui.event.OpenEvent;
import org.zkoss.zul.ext.Framable;
import org.zkoss.zul.impl.Utils;
import org.zkoss.zul.impl.XulElement;
/**
* A window.
*
* Unlike other elements, each {@link Window} is an independent ID space
* (by implementing {@link org.zkoss.zk.ui.IdSpace}).
* It means a window and all its descendants forms a ID space and
* the ID of each of them is unique in this space.
* You could retrieve any of them in this space by calling {@link #getFellow}.
*
*
If a window X is a descendant of another window Y, X's descendants
* are not visible in Y's space. To retrieve a descendant, say Z, of X,
* you have to invoke Y.getFellow('X').getFellow('Z').
*
*
Events:
* onMove, onOpen, onMaximize, onMinimize, and onClose.
* Note: to have better performance, onOpen is sent only if a
* non-deferrable event listener is registered
* (see {@link org.zkoss.zk.ui.event.Deferrable}).
*
*
onMaximize
and onMinimize
are supported. (since 3.5.0)
*
*
onClose
is sent when the close button is pressed
* (if {@link #isClosable} is true). The window has to detach or hide
* the window. By default, {@link #onClose} detaches the window. To prevent
* it from detached, you have to call {@link org.zkoss.zk.ui.event.Event#stopPropagation}
* to prevent {@link #onClose} is called.
*
*
On the other hand, onOpen
is sent when a popup
* window (i.e., {@link #getMode} is popup) is closed due to user's activity
* (such as press ESC). This event is only a notification.
* In other words, the popup is hidden before the event is sent to the server.
* The application cannot prevent the window from being hidden.
*
*
Default {@link #getZclass}: z-window.(since 3.5.0)
* @author tomyeh
*/
public class Window extends XulElement implements Framable, IdSpace {
private static final Logger log = LoggerFactory.getLogger(Window.class);
private static final long serialVersionUID = 20100721L;
private transient Caption _caption;
private String _border = "none";
private String _title = "";
/** One of MODAL, MODAL_EVENT_THREAD_DISABLED, EMBEDDED, OVERLAPPED, HIGHLIGHTED, POPUP. */
private int _mode = EMBEDDED;
/** Used for doModal. */
private Mutex _mutex = new Mutex();
/** The style used for the content block. */
private String _cntStyle;
/** The style class used for the content block. */
private String _cntSclass;
/** How to position the window. */
private String _pos;
/** Whether to show a close button. */
private boolean _closable;
/** Whether the window is sizable. */
private boolean _sizable;
/** Whether to show the shadow. */
private boolean _shadow = true;
private boolean _maximizable, _minimizable, _maximized, _minimized;
private int _minheight = 100, _minwidth = 200;
/** Embeds the window as normal component. */
public static final int EMBEDDED = 0;
/** Makes the window as a modal dialog. once {@link #doModal}
* is called, the execution of the event processing thread
* is suspended until one of the following occurs.
*
* - {@link #setMode} is called with a mode other than MODAL.
* - Either {@link #doOverlapped}, {@link #doPopup},
* {@link #doEmbedded}, or {@link #doHighlighted} is called.
* - {@link #setVisible} is called with false.
* - The window is detached from the window.
*
*
* Note: In the last two cases, the mode becomes {@link #OVERLAPPED}.
* In other words, one might say a modal window is a special overlapped window.
*
* @see #HIGHLIGHTED
*/
public static final int MODAL = 1;
//Represent a modal when the event thread is disabled (internal)
private static final int MODAL_EVENT_THREAD_DISABLED = -100;
/** Makes the window as overlapped other components.
*/
public static final int OVERLAPPED = 2;
/** Makes the window as popup.
* It is similar to {@link #OVERLAPPED}, except it is auto hidden
* when user clicks outside of the window.
*/
public static final int POPUP = 3;
/** Makes the window as highlighted.
* Its visual effect is the same as {@link #MODAL}.
* However, from the server side's viewpoint, it is similar to
* {@link #OVERLAPPED}. The execution won't be suspended when
* {@link #doHighlighted} is called.
*
* @see #MODAL
* @see #OVERLAPPED
*/
public static final int HIGHLIGHTED = 4;
static {
addClientEvent(Window.class, Events.ON_CLOSE, 0);
addClientEvent(Window.class, Events.ON_MOVE, CE_DUPLICATE_IGNORE | CE_IMPORTANT);
addClientEvent(Window.class, Events.ON_SIZE, CE_DUPLICATE_IGNORE | CE_IMPORTANT);
addClientEvent(Window.class, Events.ON_OPEN, CE_IMPORTANT);
addClientEvent(Window.class, Events.ON_Z_INDEX, CE_DUPLICATE_IGNORE | CE_IMPORTANT);
addClientEvent(Window.class, Events.ON_MAXIMIZE, CE_DUPLICATE_IGNORE | CE_IMPORTANT);
addClientEvent(Window.class, Events.ON_MINIMIZE, CE_DUPLICATE_IGNORE | CE_IMPORTANT);
}
public Window() {
setAttribute("z$is", Boolean.TRUE); //optional but optimized to mean no need to generate z$is since client handles it
}
/**
* @param title the window title (see {@link #setTitle}).
* @param border the border (see {@link #setBorder}).
* @param closable whether it is closable (see {@link #setClosable}).
*/
public Window(String title, String border, boolean closable) {
this();
setTitle(title);
setBorder(border);
setClosable(closable);
}
/**
* Returns whether the window is maximized.
* @since 3.5.0
*/
public boolean isMaximized() {
return _maximized;
}
/**
* Sets whether the window is maximized, and then the size of the window will depend
* on it to show a appropriate size. In other words, if true, the size of the
* window will count on the size of its offset parent node whose position is
* absolute (by not {@link #inEmbedded()}) or its parent node. Otherwise, its size
* will be original size. Note that the maximized effect will run at client's
* sizing phase not initial phase.
*
*
Default: false.
* @exception UiException if {@link #isMaximizable} is false.
* @since 3.5.0
*/
public void setMaximized(boolean maximized) {
if (_maximized != maximized) {
if (!_maximizable)
throw new UiException("Not maximizable, " + this);
_maximized = maximized;
if (_maximized) {
_minimized = false;
setVisible0(true); //avoid dead loop
}
smartUpdate("maximized", _maximized);
}
}
/**
* Returns whether to display the maximizing button and allow the user to maximize
* the window.
*
Default: false.
* @since 3.5.0
*/
public boolean isMaximizable() {
return _maximizable;
}
/**
* Sets whether to display the maximizing button and allow the user to maximize
* the window, when a window is maximized, the button will automatically
* change to a restore button with the appropriate behavior already built-in
* that will restore the window to its previous size.
*
Default: false.
*
*
Note: the maximize button won't be displayed if no title or caption at all.
* @since 3.5.0
*/
public void setMaximizable(boolean maximizable) {
if (_maximizable != maximizable) {
_maximizable = maximizable;
smartUpdate("maximizable", _maximizable);
}
}
/**
* Returns whether the window is minimized.
*
Default: false.
* @since 3.5.0
*/
public boolean isMinimized() {
return _minimized;
}
/**
* Sets whether the window is minimized.
*
Default: false.
* @exception UiException if {@link #isMinimizable} is false.
* @since 3.5.0
*/
public void setMinimized(boolean minimized) {
if (_minimized != minimized) {
if (!_minimizable)
throw new UiException("not minimizable, " + this);
_minimized = minimized;
if (_minimized) {
_maximized = false;
setVisible0(false); //avoid dead loop
} else
setVisible0(true);
smartUpdate("minimized", _minimized);
}
}
/**
* Returns whether to display the minimizing button and allow the user to minimize
* the window.
*
Default: false.
* @since 3.5.0
*/
public boolean isMinimizable() {
return _minimizable;
}
/**
* Sets whether to display the minimizing button and allow the user to minimize
* the window. Note that this button provides no implementation -- the behavior
* of minimizing a window is implementation-specific, so the MinimizeEvent
* event must be handled and a custom minimize behavior implemented for this
* option to be useful.
*
*
Default: false.
*
Note: the maximize button won't be displayed if no title or caption at all.
* @see MinimizeEvent
* @since 3.5.0
*/
public void setMinimizable(boolean minimizable) {
if (_minimizable != minimizable) {
_minimizable = minimizable;
smartUpdate("minimizable", _minimizable);
}
}
/**
* Sets the minimum height in pixels allowed for this window. If negative, 100 is assumed.
*
Default: 100.
*
Note: Only applies when {@link #isSizable()} = true.
* @since 3.5.0
*/
public void setMinheight(int minheight) {
if (minheight < 0)
minheight = 100;
if (_minheight != minheight) {
_minheight = minheight;
smartUpdate("minheight", _minheight);
}
}
/**
* Returns the minimum height.
*
Default: 100.
* @since 3.5.0
*/
public int getMinheight() {
return _minheight;
}
/**
* Sets the minimum width in pixels allowed for this window. If negative, 200 is assumed.
*
Default: 200.
*
Note: Only applies when {@link #isSizable()} = true.
* @since 3.5.0
*/
public void setMinwidth(int minwidth) {
if (minwidth < 0)
minwidth = 200;
if (_minwidth != minwidth) {
_minwidth = minwidth;
smartUpdate("minwidth", _minwidth);
}
}
/**
* Returns the minimum width.
*
Default: 200.
* @since 3.5.0
*/
public int getMinwidth() {
return _minwidth;
}
/**
* @deprecated As release of 5.0.0, replaced with {@link org.zkoss.zk.ui.HtmlBasedComponent#setAction}.
*/
public static void setDefaultActionOnShow(String onshow) {
}
/**
* @deprecated As release of 5.0.0, replaced with {@link org.zkoss.zk.ui.HtmlBasedComponent#setAction}.
*/
public static String getDefaultActionOnShow() {
return null;
}
/** Returns the caption of this window.
*/
public Caption getCaption() {
return _caption;
}
/** Returns the border.
*
*
Default: "none".
*/
public String getBorder() {
return _border;
}
/** Sets the border (either none or normal).
*
* @param border the border. If null, "0" or "false", "none" is assumed.
* If "true", "normal" is assumed (since 5.0.8).
*/
public void setBorder(String border) {
if (border == null || "0".equals(border) || "false".equals(border))
border = "none";
else if ("true".equals(border))
border = "normal";
if (!Objects.equals(_border, border)) {
_border = border;
smartUpdate("border", border);
}
}
/** Enables or disables the border.
* @param border whether to have a border. If true is specified,
* it is the same as setBorder("normal")
.
* @since 5.0.8
*/
public void setBorder(boolean border) {
setBorder(border ? "normal" : "none");
}
/** Returns the title.
* Besides this attribute, you could use {@link Caption} to define
* a more sophisticated caption (a.k.a., title).
*
If a window has a caption whose label ({@link Caption#getLabel})
* is not empty, then this attribute is ignored.
*
Default: empty.
*/
public String getTitle() {
return _title;
}
/** Sets the title.
*/
public void setTitle(String title) {
if (title == null)
title = "";
if (!Objects.equals(_title, title)) {
_title = title;
smartUpdate("title", title);
}
}
/** Returns the current mode.
* One of "modal", "embedded", "overlapped", "popup", and "highlighted".
* @see #getModeType
*/
public String getMode() {
return modeToString(_mode);
}
private static String modeToString(int mode) {
switch (mode) {
case MODAL:
case MODAL_EVENT_THREAD_DISABLED:
return "modal";
case POPUP:
return "popup";
case OVERLAPPED:
return "overlapped";
case HIGHLIGHTED:
return "highlighted";
default:
return "embedded";
}
}
/** Sets the mode.
* @since 6.0.0
*/
public void setMode(Mode mode) {
setMode(mode.id);
}
/** Sets the mode to overlapped, popup, modal, embedded or highlighted.
*
*
Notice: {@link Events#ON_MODAL} is posted if you specify
* "modal" to this method.
* Unlike {@link #doModal}, {@link Events#ON_MODAL} is posted, so
* the window will become modal later (since 3.0.4).
* In other words, setMode("modal") never suspends the execution
* of the current thread. On the other hand, {@link #doModal} will
* suspends the execution if executed in an event listener, or
* throws an exception if not executed in an event listener.
*
*
Refer to Overlapped, Popup, Modal, Highlighted and Embedded
* for more information.
*
* @param name the mode which could be one of
* "embedded", "overlapped", "popup", "modal", "highlighted".
* Note: it cannot be "modal". Use {@link #doModal} instead.
*/
public void setMode(String name) {
if ("popup".equals(name))
doPopup();
else if ("overlapped".equals(name))
doOverlapped();
else if ("embedded".equals(name))
doEmbedded();
else if ("modal".equals(name)) {
if (isEventThreadEnabled(false))
Events.postEvent(Events.ON_MODAL, this, null);
else
doModal();
} else if ("highlighted".equals(name))
doHighlighted();
else
throw new WrongValueException("Unknown mode: " + name);
}
/** Sets the mode to overlapped, popup, modal, embedded or highlighted.
*
* @see #setMode(String)
*/
public void setMode(int mode) {
switch (mode) {
case POPUP:
doPopup();
break;
case OVERLAPPED:
doOverlapped();
break;
case EMBEDDED:
doEmbedded();
break;
case MODAL:
if (isEventThreadEnabled(false))
Events.postEvent(Events.ON_MODAL, this, null);
else
doModal();
break;
case HIGHLIGHTED:
doHighlighted();
break;
default:
throw new WrongValueException("Unknown mode: " + mode);
}
}
/** Returns the current mode.
* @see #getMode
* @see #setMode(Mode)
* @since 6.0.0
*/
public Mode getModeType() {
return toModeType(_mode);
}
private static Mode toModeType(int mode) {
switch (mode) {
case MODAL:
case MODAL_EVENT_THREAD_DISABLED:
return Mode.MODAL;
case POPUP:
return Mode.POPUP;
case OVERLAPPED:
return Mode.OVERLAPPED;
case HIGHLIGHTED:
return Mode.HIGHLIGHTED;
default:
return Mode.EMBEDDED;
}
}
/** Returns whether this is a modal dialog.
*/
public boolean inModal() {
return _mode == MODAL || _mode == MODAL_EVENT_THREAD_DISABLED;
}
/** Returns whether this is embedded with other components (Default).
* @see #doEmbedded
*/
public boolean inEmbedded() {
return _mode == EMBEDDED;
}
/** Returns whether this is a overlapped window.
*/
public boolean inOverlapped() {
return _mode == OVERLAPPED;
}
/** Returns whether this is a popup window.
*/
public boolean inPopup() {
return _mode == POPUP;
}
/** Returns whether this is a highlighted window.
*/
public boolean inHighlighted() {
return _mode == HIGHLIGHTED;
}
/** Makes this window as a modal dialog.
* It will automatically center the window (ignoring {@link #getLeft} and
* {@link #getTop}).
*
*
Notice: though both setMode("modal") and doModal() both
* causes the window to become modal, they are a bit different.
* doModal causes the event listener to suspend immediately,
* while setMode("modal") posts an event ({@link Events#ON_MODAL}).
* That is, {@link #setMode} won't suspend the execution immediately,
* but {@link #doModal} will.
* {@link #doModal} can be called only in an event listener,
* while {@link #setMode} can be called anytime.
* @since 3.0.4
*/
public void doModal() {
if (!isEventThreadEnabled(true)) {
checkOverlappable(MODAL_EVENT_THREAD_DISABLED);
setNonModalMode(MODAL_EVENT_THREAD_DISABLED);
return;
}
checkOverlappable(MODAL);
if (_mode != MODAL) {
if (!Events.inEventListener())
throw new SuspendNotAllowedException("doModal must be called in an event listener");
int oldmode = _mode;
boolean oldvisi = isVisible();
setVisible(true); //if MODAL, it must be visible; vice versa
try {
enterModal();
} catch (SuspendNotAllowedException ex) {
handleFailedModal(oldmode, oldvisi);
throw ex;
}
}
}
private void handleFailedModal(int oldmode, boolean oldvisi) {
try {
if (Executions.getCurrent().getAttribute("jakarta.servlet.error.exception") != null) {
//handle it specially if it is used for displaying err
setMode(HIGHLIGHTED);
} else {
setMode(oldmode); //restore
setVisible(oldvisi);
}
} catch (Throwable ex) {
log.error("Causing another error", ex);
}
}
/** Makes this window as overlapped with other components.
*/
public void doOverlapped() {
checkOverlappable(OVERLAPPED);
setNonModalMode(OVERLAPPED);
}
/** Makes this window as popup, which is overlapped with other component
* and auto-hidden when user clicks outside of the window.
*/
public void doPopup() {
checkOverlappable(POPUP);
setNonModalMode(POPUP);
}
/** Makes this window as highlighted. The visual effect is
* the similar to the modal window, but, like overlapped,
* it doesn't suspend (block) the execution at the server.
* In other words, it is more like an overlapped window from the
* server side's viewpoint.
*/
public void doHighlighted() {
checkOverlappable(HIGHLIGHTED);
setNonModalMode(HIGHLIGHTED);
}
/** Makes this window as embedded with other components (Default).
*/
public void doEmbedded() {
setNonModalMode(EMBEDDED);
}
/* Set non-modal mode. */
private void setNonModalMode(int mode) {
if (_mode != mode) {
if (_mode == MODAL)
leaveModal(mode);
else {
_mode = mode;
smartUpdate("mode", modeToString(_mode));
}
}
setVisible(true);
}
/** Set mode to MODAL and suspend this thread. */
private void enterModal() {
_mode = MODAL;
smartUpdate("mode", modeToString(_mode));
//no need to synchronized (_mutex) because no racing is possible
try {
Executions.wait(_mutex);
} catch (InterruptedException ex) {
throw UiException.Aide.wrap(ex);
}
}
/** Resumes the suspended thread and set mode to OVERLAPPED. */
private void leaveModal(int mode) {
_mode = mode;
smartUpdate("mode", modeToString(_mode));
Executions.notifyAll(_mutex);
}
private boolean isEventThreadEnabled(boolean attachedRequired) {
Desktop desktop = getDesktop();
if (desktop == null) {
if (attachedRequired)
throw new SuspendNotAllowedException("Not attached, " + this);
final Execution exec = Executions.getCurrent();
if (exec == null || (desktop = exec.getDesktop()) == null)
return true; //assume enabled (safer)
}
return desktop.getWebApp().getConfiguration().isEventThreadEnabled();
}
/** Makes sure it is not draggable. */
private void checkOverlappable(int mode) {
if (!"false".equals(getDraggable()))
throw new UiException("Draggable window cannot be modal, overlapped, popup, or highlighted: " + this);
if (mode == MODAL)
for (Component comp = this; (comp = comp.getParent()) != null;)
if (!comp.isVisible())
throw new UiException(
"One of its ancestors, " + comp + ", is not visible, so unable to be modal or highlighted");
}
/** Returns whether to show a close button on the title bar.
*/
public boolean isClosable() {
return _closable;
}
/** Sets whether to show a close button on the title bar.
* If closable, a button is displayed and the onClose event is sent
* if an user clicks the button.
*
*
Default: false.
*
*
You can intercept the default behavior by either overriding
* {@link #onClose}, or listening the onClose event.
*
*
Note: the close button won't be displayed if no title or caption at all.
*/
public void setClosable(boolean closable) {
if (_closable != closable) {
_closable = closable;
smartUpdate("closable", closable); //re-init is required
}
}
/** Returns whether the window is sizable.
*/
public boolean isSizable() {
return _sizable;
}
/** Sets whether the window is sizable.
* If true, an user can drag the border to change the window width.
*
Default: false.
*/
public void setSizable(boolean sizable) {
if (_sizable != sizable) {
_sizable = sizable;
smartUpdate("sizable", sizable);
}
}
/** Returns whether to show the shadow of an overlapped/popup/modal
* window. It is meaningless if it is an embedded window.
* @since 3.6.0
*/
public boolean isShadow() {
return _shadow;
}
/** Sets whether to show the shadow of an overlapped/popup/modal
* window. It is meaningless if it is an embedded window.
*
Default: true.
* @since 3.6.0
*/
public void setShadow(boolean shadow) {
if (_shadow != shadow) {
_shadow = shadow;
smartUpdate("shadow", shadow);
}
}
/** Returns how to position the window at the client screen.
* It is meaningless if the embedded mode is used.
*
*
Default: null which depends on {@link #getMode}:
* If overlapped or popup, {@link #setLeft} and {@link #setTop} are
* assumed. If modal or highlighted, it is centered.
*/
public String getPosition() {
return _pos;
}
/** Sets how to position the window at the client screen.
* It is meaningless if the embedded mode is used.
*
* @param pos how to position. It can be null (the default), or
* a combination of the following values (by separating with comma).
*
* - center
* - Position the window at the center. {@link #setTop} and {@link #setLeft}
* are both ignored.
* - nocenter
* - Not to position the window at the center. A modal window, by default,
* will be position at the center. By specifying this value could
* prevent it and the real position depends on {@link #setTop} and {@link #setLeft} (since 5.0.4)
* - left
* - Position the window at the left edge. {@link #setLeft} is ignored.
* - right
* - Position the window at the right edge. {@link #setLeft} is ignored.
* - top
* - Position the window at the top edge. {@link #setTop} is ignored.
* - bottom
* - Position the window at the bottom edge. {@link #setTop} is ignored.
* - parent
* - Position the window relative to its parent.
* That is, the left and top ({@link #getTop} and {@link #getLeft})
* is an offset to his parent's let-top corner. (since 3.0.2)
*
* For example, "left,center" means to position it at the center of
* the left edge.
*/
public void setPosition(String pos) {
//Note: we always update since the window might be dragged by an user
_pos = pos;
smartUpdate("position", pos);
}
/** Process the onClose event sent when the close button is pressed.
*
Default: detach itself.
*/
public void onClose() {
detach();
}
/** Process the onModal event by making itself a modal window.
*/
public void onModal() {
doModal();
}
/** Returns the CSS style for the content block of the window.
*/
public String getContentStyle() {
return _cntStyle;
}
/** Sets the CSS style for the content block of the window.
*
*
Default: null.
*/
public void setContentStyle(String style) {
if (!Objects.equals(_cntStyle, style)) {
_cntStyle = style;
smartUpdate("contentStyle", _cntStyle);
}
}
/** Returns the style class used for the content block.
*
* @see #setContentSclass
*/
public String getContentSclass() {
return _cntSclass;
}
/** Sets the style class used for the content block.
*
* @see #getContentSclass
* @since 3.0.0
*/
public void setContentSclass(String scls) {
if (!Objects.equals(_cntSclass, scls)) {
_cntSclass = scls;
smartUpdate("contentSclass", scls);
}
}
/** Makes this window as topmost.
* It has no effect if this window is embedded.
* @since 5.0.0
*/
public void setTopmost() {
smartUpdate("topmost", true);
}
//ZK-3678: Provide a switch to enable/disable iscroll
/*package*/ boolean isNativeScrollbar() {
return Utils.testAttribute(this, "org.zkoss.zul.nativebar", true, true);
}
// super
protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer) throws java.io.IOException {
super.renderProperties(renderer);
render(renderer, "title", _title);
render(renderer, "maximized", _maximized);
render(renderer, "maximizable", _maximizable);
render(renderer, "minimized", _minimized);
render(renderer, "minimizable", _minimizable);
render(renderer, "closable", _closable);
render(renderer, "sizable", _sizable);
render(renderer, "position", _pos);
render(renderer, "contentStyle", _cntStyle);
render(renderer, "contentSclass", _cntSclass);
if (_minheight != 100)
renderer.render("minheight", _minheight);
if (_minwidth != 200)
renderer.render("minwidth", _minwidth);
if (!"none".equals(_border))
renderer.render("border", _border);
if (!isShadow())
renderer.render("shadow", false);
if (_mode != EMBEDDED)
renderer.render("mode", modeToString(_mode));
//render mode as the last property
//ZK-3678: Provide a switch to enable/disable iscroll
if (isNativeScrollbar())
renderer.render("_nativebar", true);
}
public String getZclass() {
return _zclass == null ? "z-window" : _zclass;
}
//-- Component --//
public void beforeChildAdded(Component child, Component refChild) {
if (child instanceof Caption) {
if (_caption != null && _caption != child)
throw new UiException("Only one caption is allowed: " + this);
} else if (refChild instanceof Caption) {
throw new UiException("caption must be the first child");
}
super.beforeChildAdded(child, refChild);
}
public boolean insertBefore(Component child, Component refChild) {
if (child instanceof Caption) {
refChild = getFirstChild();
//always makes caption as the first child
if (super.insertBefore(child, refChild)) {
_caption = (Caption) child;
return true;
}
return false;
}
return super.insertBefore(child, refChild);
}
public void onChildRemoved(Component child) {
if (child instanceof Caption)
_caption = null;
super.onChildRemoved(child);
}
public void onPageDetached(Page page) {
super.onPageDetached(page);
if (_mode == MODAL && getPage() == null)
leaveModal(OVERLAPPED);
}
/** Changes the visibility of the window.
*
*
Note if you turned on the event thread:
* If a modal dialog becomes invisible, the modal state
* will be ended automatically. In other words, the mode ({@link #getMode})
* will become {@link #OVERLAPPED} and the suspending thread is resumed.
* In other words, the modal window ({@link #MODAL}) can not be invisible
* (while a window in other modes could be invisible).
*
However, if the event thread is not enabled (default), there is no
* such limitation. In other words, it remains the same mode when becoming
* invisible.
*/
public boolean setVisible(boolean visible) {
if (visible == isVisible())
return visible;
_maximized = _minimized = false;
return setVisible0(visible);
}
private boolean setVisible0(boolean visible) {
if (!visible && _mode == MODAL) {
//Hide first to avoid unpleasant effect
super.setVisible(false);
leaveModal(OVERLAPPED);
return true;
}
return super.setVisible(visible);
}
//-- super --//
public void setDraggable(String draggable) {
if (_mode != EMBEDDED) {
if (draggable != null && (draggable.length() > 0 && !"false".equals(draggable)))
throw new UiException("Only embedded window could be draggable: " + this);
}
super.setDraggable(draggable);
}
//Cloneable//
public Object clone() {
final Window clone = (Window) super.clone();
clone._mutex = new Mutex();
if (clone._caption != null)
clone.afterUnmarshal();
return clone;
}
private void afterUnmarshal() {
for (Iterator it = getChildren().iterator(); it.hasNext();) {
final Object child = it.next();
if (child instanceof Caption) {
_caption = (Caption) child;
break;
}
}
}
//Serializable//
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
afterUnmarshal();
}
//-- ComponentCtrl --//
/** Processes an AU request.
*
* Default: in addition to what are handled by {@link XulElement#service},
* it also handles onOpen.
* @since 5.0.0
*/
public void service(org.zkoss.zk.au.AuRequest request, boolean everError) {
final String cmd = request.getCommand();
if (cmd.equals(Events.ON_OPEN)) {
OpenEvent evt = OpenEvent.getOpenEvent(request);
setVisible(evt.isOpen());
Events.postEvent(evt);
} else if (cmd.equals(Events.ON_MAXIMIZE)) {
MaximizeEvent evt = MaximizeEvent.getMaximizeEvent(request);
setLeftDirectly(evt.getLeft());
setTopDirectly(evt.getTop());
setWidthDirectly(evt.getWidth());
setHeightDirectly(evt.getHeight());
_maximized = evt.isMaximized();
if (_maximized)
setVisibleDirectly(true);
Events.postEvent(evt);
} else if (cmd.equals(Events.ON_MINIMIZE)) {
MinimizeEvent evt = MinimizeEvent.getMinimizeEvent(request);
setLeftDirectly(evt.getLeft());
setTopDirectly(evt.getTop());
setWidthDirectly(evt.getWidth());
setHeightDirectly(evt.getHeight());
_minimized = evt.isMinimized();
if (_minimized) {
setVisibleDirectly(false);
if (_mode == MODAL)
leaveModal(OVERLAPPED);
}
Events.postEvent(evt);
} else
super.service(request, everError);
}
/**
* Always return false.
* @since 3.6.2
*/
public boolean isCollapsible() {
return false;
}
/** The window's mode used with {@link Window#setMode(Mode)}.
* @since 6.0.0
*/
public static enum Mode {
/** Embeds the window as normal component. */
EMBEDDED(Window.EMBEDDED),
/** Makes the window as a modal dialog. once {@link #doModal}
* is called, the execution of the event processing thread
* is suspended until one of the following occurs.
*
* - {@link #setMode(Mode)} is called with a mode other than MODAL.
* - Either {@link #doOverlapped}, {@link #doPopup},
* {@link #doEmbedded}, or {@link #doHighlighted} is called.
* - {@link #setVisible} is called with false.
* - The window is detached from the window.
*
*
* Note: In the last two cases, the mode becomes {@link #OVERLAPPED}.
* In other words, one might say a modal window is a special overlapped window.
*
* @see #HIGHLIGHTED
*/
MODAL(Window.MODAL),
/** Makes the window as overlapped other components.
*/
OVERLAPPED(Window.OVERLAPPED),
/** Makes the window as popup.
* It is similar to {@link #OVERLAPPED}, except it is auto hidden
* when user clicks outside of the window.
*/
POPUP(Window.POPUP),
/** Makes the window as highlighted.
* Its visual effect is the same as {@link #MODAL}.
* However, from the server side's viewpoint, it is similar to
* {@link #OVERLAPPED}. The execution won't be suspended when
* {@link #doHighlighted} is called.
*
* @see #MODAL
* @see #OVERLAPPED
*/
HIGHLIGHTED(Window.HIGHLIGHTED);
private final int id;
private Mode(int v) {
this.id = v;
}
}
}