/* Panel.java
Purpose:
Description:
History:
Jun 10, 2008 11:39:33 AM , Created by jumperchen
Copyright (C) 2008 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 org.zkoss.lang.Objects;
import org.zkoss.zk.au.out.AuSetAttribute;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.IdSpace;
import org.zkoss.zk.ui.UiException;
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.XulElement;
/**
* Panel is a container that has specific functionality and structural components
* that make it the perfect building block for application-oriented user interfaces.
* The Panel contains bottom, top, and foot toolbars, along with separate header,
* footer and body sections. It also provides built-in collapsible, closable,
* maximizable, and minimizable behavior, along with a variety of pre-built tool
* buttons that can be wired up to provide other customized behavior. Panels can
* be easily embedded into any kind of ZUL component that is allowed to have children
* or layout component. Panels also provide specific features like float and move.
* Unlike {@link Window}, Panels can only be floated and moved inside its parent
* node, which is not using zk.setVParent() function at client side. In other words,
* if Panel's parent node is an relative position, the floated panel is only inside
* its parent, not the whole page.
* The second difference of {@link Window} is that Panel is not an independent ID
* space (by implementing {@link IdSpace}), so the ID of each child can be used
* throughout the panel.
*
* <p>Events:<br/>
* onMove, onOpen, onZIndex, onMaximize, onMinimize, and onClose.<br/>
*
* <p>Default {@link #getZclass}: z-panel.
*
* @author jumperchen
* @since 3.5.0
*/
public class Panel extends XulElement implements Framable {
private Toolbar _tbar, _bbar, _fbar;
private Panelchildren _panelchildren;
private Caption _caption;
private String _border = "none";
private String _title = "";
private int _minheight = 100, _minwidth = 200;
private boolean _closable, _collapsible, _floatable, _movable, _maximizable, _minimizable, _maximized, _minimized,
_sizable, _open = true, _framableBC/*backward compatible*/;
static {
addClientEvent(Panel.class, Events.ON_CLOSE, 0);
addClientEvent(Panel.class, Events.ON_MOVE, CE_DUPLICATE_IGNORE | CE_IMPORTANT);
addClientEvent(Panel.class, Events.ON_SIZE, CE_DUPLICATE_IGNORE | CE_IMPORTANT);
addClientEvent(Panel.class, Events.ON_OPEN, CE_IMPORTANT);
addClientEvent(Panel.class, Events.ON_Z_INDEX, CE_DUPLICATE_IGNORE | CE_IMPORTANT);
addClientEvent(Panel.class, Events.ON_MAXIMIZE, CE_DUPLICATE_IGNORE | CE_IMPORTANT);
addClientEvent(Panel.class, Events.ON_MINIMIZE, CE_DUPLICATE_IGNORE | CE_IMPORTANT);
}
/**
* Returns whether this Panel is open.
* <p>Default: true.
*/
public boolean isOpen() {
return _open;
}
/**
* Opens or closes this Panel.
*/
public void setOpen(boolean open) {
if (_open != open) {
_open = open;
smartUpdate("open", _open);
}
}
/**
* @deprecated As of release 5.0.6, replaced with {@link #getBorder}.
* Returns whether to render the panel with custom rounded borders.
* <p>Default: false.
*/
public boolean isFramable() {
return _border.startsWith("rounded"); //rounded or rounded+
}
/**
* @deprecated As of release 5.0.6, replaced with {@link #setBorder}.
* Sets whether to render the panel with custom rounded borders.
*
* <p>Default: false.
*/
public void setFramable(boolean framable) {
_framableBC = true;
boolean bordered = "normal".equals(_border) || "rounded+".equals(_border);
setBorder0(framable ? bordered ? "rounded+" : "rounded" : bordered ? "normal" : "none");
}
/**
* Sets whether to move the panel to display it inline where it is rendered.
*
* <p>Default: false;
* <p>Note that this method only applied when {@link #isFloatable()} is true.
*/
public void setMovable(boolean movable) {
if (_movable != movable) {
_movable = movable;
smartUpdate("movable", _movable);
}
}
/**
* Returns whether to move the panel to display it inline where it is rendered.
* <p>Default: false.
*/
public boolean isMovable() {
return _movable;
}
/**
* Returns whether to float the panel to display it inline where it is rendered.
* <p>Default: false.
*/
public boolean isFloatable() {
return _floatable;
}
public boolean setVisible(boolean visible) {
if (visible == isVisible())
return visible;
_maximized = _minimized = false;
return setVisible0(visible);
}
private boolean setVisible0(boolean visible) {
return super.setVisible(visible);
}
/**
* Sets whether to float the panel to display it inline where it is rendered.
*
* <p>Note that by default, setting floatable to true will cause the
* panel to display at default offsets, which depend on the offsets of
* the embedded panel from its element to <i>document.body</i> -- because the panel
* is absolute positioned, the position must be set explicitly by {@link #setTop(String)}
* and {@link #setLeft(String)}. Also, when floatable a panel you should always
* assign a fixed width, otherwise it will be auto width and will expand to fill
* to the right edge of the viewport.
*/
public void setFloatable(boolean floatable) {
if (_floatable != floatable) {
_floatable = floatable;
smartUpdate("floatable", _floatable);
}
}
/**
* Returns whether the panel is maximized.
*/
public boolean isMaximized() {
return _maximized;
}
/**
* Sets whether the panel is maximized, and then the size of the panel will depend
* on it to show a appropriate size. In other words, if true, the size of the
* panel will count on the size of its offset parent node whose position is
* absolute (by {@link #isFloatable()}) 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.
*
* <p>Default: false.
* @exception UiException if {@link #isMaximizable} is false.
*/
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 panel.
* <p>Default: false.
*/
public boolean isMaximizable() {
return _maximizable;
}
/**
* Sets whether to display the maximizing button and allow the user to maximize
* the panel, when a panel is maximized, the button will automatically
* change to a restore button with the appropriate behavior already built-in
* that will restore the panel to its previous size.
* <p>Default: false.
*
* <p>Note: the maximize button won't be displayed if no title or caption at all.
*/
public void setMaximizable(boolean maximizable) {
if (_maximizable != maximizable) {
_maximizable = maximizable;
smartUpdate("maximizable", _maximizable);
}
}
/**
* Returns whether the panel is minimized.
* <p>Default: false.
*/
public boolean isMinimized() {
return _minimized;
}
/**
* Sets whether the panel is minimized.
* <p>Default: false.
* @exception UiException if {@link #isMinimizable} is false.
*/
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 panel.
* <p>Default: false.
*/
public boolean isMinimizable() {
return _minimizable;
}
/**
* Sets whether to display the minimizing button and allow the user to minimize
* the panel. Note that this button provides no implementation -- the behavior
* of minimizing a panel is implementation-specific, so the MinimizeEvent
* event must be handled and a custom minimize behavior implemented for this
* option to be useful.
*
* <p>Default: false.
* <p>Note: the maximize button won't be displayed if no title or caption at all.
* @see MinimizeEvent
*/
public void setMinimizable(boolean minimizable) {
if (_minimizable != minimizable) {
_minimizable = minimizable;
smartUpdate("minimizable", _minimizable);
}
}
/**
* Returns whether to show a toggle button on the title bar.
* <p>Default: false.
*/
public boolean isCollapsible() {
return _collapsible;
}
/**
* Sets whether to show a toggle button on the title bar.
* <p>Default: false.
* <p>Note: the toggle button won't be displayed if no title or caption at all.
* <p>Note: onOpen event will be sent when you click the toggle button
*/
public void setCollapsible(boolean collapsible) {
if (_collapsible != collapsible) {
_collapsible = collapsible;
smartUpdate("collapsible", _collapsible);
}
}
/** 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.
*
* <p>Default: false.
*
* <p>You can intercept the default behavior by either overriding
* {@link #onClose}, or listening the onClose event.
*
* <p>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);
}
}
/**
* Sets the minimum height in pixels allowed for this panel. If negative, 100 is assumed.
* <p>Default: 100.
* <p>Note: Only applies when {@link #isSizable()} = true.
* @since 5.0.0
*/
public void setMinheight(int minheight) {
if (minheight < 0)
minheight = 100;
if (_minheight != minheight) {
_minheight = minheight;
smartUpdate("minheight", _minheight);
}
}
/**
* Returns the minimum height.
* <p>Default: 100.
* @since 5.0.0
*/
public int getMinheight() {
return _minheight;
}
/**
* Sets the minimum width in pixels allowed for this panel. If negative, 200 is assumed.
* <p>Default: 200.
* <p>Note: Only applies when {@link #isSizable()} = true.
* @since 5.0.0
*/
public void setMinwidth(int minwidth) {
if (minwidth < 0)
minwidth = 200;
if (_minwidth != minwidth) {
_minwidth = minwidth;
smartUpdate("minwidth", _minwidth);
}
}
/**
* Returns the minimum width.
* <p>Default: 200.
* @since 5.0.0
*/
public int getMinwidth() {
return _minwidth;
}
public void setHflex(String flex) {
super.setHflex(flex);
Panelchildren pc = getPanelchildren();
if (pc != null)
pc.smartUpdate("hflex", flex);
}
public void setVflex(String flex) {
super.setVflex(flex);
Panelchildren pc = getPanelchildren();
if (pc != null)
pc.smartUpdate("vflex", flex);
}
/** Returns whether the panel is sizable.
* @since 5.0.0
*/
public boolean isSizable() {
return _sizable;
}
/** Sets whether the panel is sizable.
* If true, an user can drag the border to change the panel width.
* <p>Default: false.
* @since 5.0.0
*/
public void setSizable(boolean sizable) {
if (_sizable != sizable) {
_sizable = sizable;
smartUpdate("sizable", sizable);
}
}
/** Returns the caption of this panel.
*/
public Caption getCaption() {
return _caption;
}
/** Returns the border.
*
* <p>Default: "none".
*/
public String getBorder() {
if (_framableBC && _border.startsWith("rounded")) //backward compatible
return "rounded".equals(_border) ? "none" : "normal";
return _border;
}
/** Sets the border.
* Allowed values include <code>none</code> (default), <code>normal</code>,
* <code>rounded</code> and <code>rounded+</code>.
* For more information, please refer to
* <a href="http://books.zkoss.org/wiki/ZK_Component_Reference/Containers/Panel#Border">ZK Component Reference: Panel</a>.
* @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 (_framableBC) {
if (border.startsWith("rounded")) {
_framableBC = false;
} else if ("normal".equals(border)) {
if (_border.startsWith("rounded"))
border = "rounded+";
} else {
if (_border.startsWith("rounded"))
border = "rounded";
}
}
setBorder0(border);
}
/** Enables or disables the border.
* @param border whether to have a border. If true is specified,
* it is the same as <code>setBorder("normal")</code>.
* @since 5.0.8
*/
public void setBorder(boolean border) {
setBorder(border ? "normal" : "none");
}
private void setBorder0(String border) {
if (!Objects.equals(_border, border)) {
_border = border;
smartUpdate("border", _border);
}
}
/**
* Returns the title.
* Besides this attribute, you could use {@link Caption} to define
* a more sophisticated caption (a.k.a., title).
* <p>If a panel has a caption whose label ({@link Caption#getLabel})
* is not empty, then this attribute is ignored.
* <p>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);
}
}
/**
* Adds the toolbar of the panel by these names, "tbar", "bbar", and "fbar".
* "tbar" is the name of top toolbar, and "bbar" the name of bottom toolbar,
* and "fbar" the name of foot toolbar.
*
* @param name "tbar", "bbar", and "fbar".
*/
public boolean addToolbar(String name, Toolbar toolbar) {
Component refChild = null;
if ("tbar".equals(name)) {
if (_tbar != null)
throw new UiException("Only one top toolbar child is allowed: " + this);
refChild = this.getFirstChild();
} else if ("bbar".equals(name)) {
if (_bbar != null)
throw new UiException("Only one bottom toolbar child is allowed: " + this);
refChild = _fbar;
} else if ("fbar".equals(name)) {
if (_fbar != null)
throw new UiException("Only one foot toolbar child is allowed: " + this);
} else {
throw new UiException("Unknown toolbar: " + name);
}
if (super.insertBefore(toolbar, refChild)) {
if ("tbar".equals(name)) {
_tbar = toolbar;
response(new AuSetAttribute(this, "tbar", toolbar.getUuid()));
} else if ("bbar".equals(name)) {
_bbar = toolbar;
response(new AuSetAttribute(this, "bbar", toolbar.getUuid()));
} else if ("fbar".equals(name)) {
_fbar = toolbar;
response(new AuSetAttribute(this, "fbar", toolbar.getUuid()));
}
return true;
}
return false;
}
/** Process the onClose event sent when the close button is pressed.
* <p>Default: detach itself.
*/
public void onClose() {
detach();
}
/**
* Returns the top toolbar of this panel.
*/
public Toolbar getTopToolbar() {
return _tbar;
}
/**
* Returns the bottom toolbar of this panel.
*/
public Toolbar getBottomToolbar() {
return _bbar;
}
/**
* Returns the foot toolbar of this panel.
*/
public Toolbar getFootToolbar() {
return _fbar;
}
/**
* Returns the panelchildren of this panel.
*/
public Panelchildren getPanelchildren() {
return _panelchildren;
}
public String getZclass() {
return _zclass == null ? "z-panel" : _zclass;
}
//-- Component --//
public void beforeChildAdded(Component newChild, Component refChild) {
if (newChild instanceof Caption) {
if (_caption != null && _caption != newChild)
throw new UiException("Only one caption is allowed: " + this);
} else if (refChild instanceof Caption) {
throw new UiException("caption must be the first child");
} else if (newChild instanceof Panelchildren) {
if (_panelchildren != null && _panelchildren != newChild)
throw new UiException("Only one panelchildren child is allowed: " + this);
} else if (newChild instanceof Toolbar) {
if (refChild instanceof Panelchildren || (refChild == null && (getChildren().isEmpty()))) {
if (_tbar != null && _tbar != newChild)
throw new UiException("Only one top toolbar child is allowed: " + this);
} else if (refChild == null || refChild == _fbar) {
if (_bbar != null && _bbar != newChild) {
if (refChild != null && refChild == _fbar)
throw new UiException("Only one bottom toolbar child is allowed: " + this);
if (_fbar != null && _fbar != newChild)
throw new UiException("Only one foot toolbar child is allowed: " + this);
}
} else {
throw new UiException("Only three toolbars child is allowed: " + this);
}
} else {
throw new UiException("Unsupported child for Panel: " + newChild);
}
super.beforeChildAdded(newChild, refChild);
}
public boolean insertBefore(Component newChild, Component refChild) {
if (newChild instanceof Caption) {
refChild = getFirstChild();
//always makes caption as the first child
if (super.insertBefore(newChild, refChild)) {
_caption = (Caption) newChild;
return true;
}
} else if (newChild instanceof Panelchildren) {
if (super.insertBefore(newChild, refChild)) {
_panelchildren = (Panelchildren) newChild;
return true;
}
} else if (newChild instanceof Toolbar) {
if (super.insertBefore(newChild, refChild)) {
if (refChild instanceof Panelchildren
|| (refChild == null && (getChildren().size() == (_caption != null ? 2 : 1)))) {
_tbar = (Toolbar) newChild;
} else if (refChild == null || refChild == _fbar) {
if (_bbar != null && _bbar != newChild) {
_fbar = (Toolbar) newChild;
} else {
_bbar = (Toolbar) newChild;
}
}
return true;
}
} else {
return super.insertBefore(newChild, refChild);
//impossible but to make it more extensible
}
return false;
}
public void onChildRemoved(Component child) {
super.onChildRemoved(child);
if (_caption == child)
_caption = null;
else if (_tbar == child)
_tbar = null;
else if (_bbar == child)
_bbar = null;
else if (_panelchildren == child)
_panelchildren = null;
else if (_fbar == child)
_fbar = null;
}
//Cloneable//
public Object clone() {
final Panel clone = (Panel) super.clone();
clone.afterUnmarshal();
return clone;
}
private void afterUnmarshal() {
if (_caption != null)
_caption = (Caption) getChildren().get(_caption.getParent().getChildren().indexOf(_caption));
if (_tbar != null)
_tbar = (Toolbar) getChildren().get(_tbar.getParent().getChildren().indexOf(_tbar));
if (_panelchildren != null)
_panelchildren = (Panelchildren) getChildren()
.get(_panelchildren.getParent().getChildren().indexOf(_panelchildren));
if (_bbar != null)
_bbar = (Toolbar) getChildren().get(_bbar.getParent().getChildren().indexOf(_bbar));
if (_fbar != null)
_fbar = (Toolbar) getChildren().get(_fbar.getParent().getChildren().indexOf(_fbar));
}
//-- Serializable --//
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
//afterUnmarshal(); // B50-ZK-261: no afterUnmarshal() as now the fields are non-transient
}
// super
protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer) throws java.io.IOException {
super.renderProperties(renderer);
if (_title.length() > 0)
render(renderer, "title", _title);
render(renderer, "closable", _closable);
render(renderer, "floatable", _floatable);
render(renderer, "collapsible", _collapsible);
render(renderer, "movable", _movable);
render(renderer, "maximizable", _maximizable);
render(renderer, "minimizable", _minimizable);
render(renderer, "maximized", _maximized);
render(renderer, "minimized", _minimized);
render(renderer, "sizable", _sizable);
if (_minheight != 100)
renderer.render("minheight", _minheight);
if (_minwidth != 200)
renderer.render("minwidth", _minwidth);
if (!_open)
renderer.render("open", false);
if (!"none".equals(_border))
renderer.render("border", _border);
}
//-- ComponentCtrl --//
/** Processes an AU request.
*
* <p>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);
_open = 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);
Events.postEvent(evt);
} else
super.service(request, everError);
}
}