0001: /* Window.java
0002:
0003: {{IS_NOTE
0004: Purpose:
0005:
0006: Description:
0007:
0008: History:
0009: Tue May 31 19:29:13 2005, Created by tomyeh
0010: }}IS_NOTE
0011:
0012: Copyright (C) 2005 Potix Corporation. All Rights Reserved.
0013:
0014: {{IS_RIGHT
0015: This program is distributed under GPL Version 2.0 in the hope that
0016: it will be useful, but WITHOUT ANY WARRANTY.
0017: }}IS_RIGHT
0018: */
0019: package org.zkoss.zul;
0020:
0021: import java.util.Collections;
0022: import java.util.Map;
0023: import java.util.HashMap;
0024: import java.util.Iterator;
0025: import java.util.Set;
0026: import java.util.LinkedHashSet;
0027:
0028: import org.zkoss.mesg.MCommon;
0029: import org.zkoss.lang.D;
0030: import org.zkoss.lang.Objects;
0031: import org.zkoss.util.logging.Log;
0032: import org.zkoss.xml.HTMLs;
0033:
0034: import org.zkoss.zk.ui.Desktop;
0035: import org.zkoss.zk.ui.Component;
0036: import org.zkoss.zk.ui.Page;
0037: import org.zkoss.zk.ui.Executions;
0038: import org.zkoss.zk.ui.UiException;
0039: import org.zkoss.zk.ui.WrongValueException;
0040: import org.zkoss.zk.ui.SuspendNotAllowedException;
0041: import org.zkoss.zk.ui.IdSpace;
0042: import org.zkoss.zk.ui.ext.render.MultiBranch;
0043: import org.zkoss.zk.ui.ext.client.Openable;
0044: import org.zkoss.zk.ui.ext.render.Floating;
0045: import org.zkoss.zk.ui.event.Events;
0046:
0047: import org.zkoss.zul.impl.XulElement;
0048:
0049: /**
0050: * A generic window.
0051: *
0052: * <p>Unlike other elements, each {@link Window} is an independent ID space
0053: * (by implementing {@link IdSpace}).
0054: * It means a window and all its descendants forms a ID space and
0055: * the ID of each of them is unique in this space.
0056: * You could retrieve any of them in this space by calling {@link #getFellow}.
0057: *
0058: * <p>If a window X is a descendant of another window Y, X's descendants
0059: * are not visible in Y's space. To retrieve a descendant, say Z, of X,
0060: * you have to invoke Y.getFellow('X').getFellow('Z').
0061: *
0062: * <p>Events:<br/>
0063: * onMove, onOpen, onClose, onOK, onCacnel and onCtrlKey.<br/>
0064: * Note: to have better performance, onOpen is sent only if a
0065: * non-deferrable event listener is registered
0066: * (see {@link org.zkoss.zk.ui.event.Deferrable}).
0067: *
0068: * <p><code>onClose</code> is sent when the close button is pressed
0069: * (if {@link #isClosable} is true). The window has to detach or hide
0070: * the window. By default, {@link #onClose} detaches the window. To prevent
0071: * it from detached, you have to call {@link org.zkoss.zk.ui.event.Event#stopPropagation}
0072: * to prevent {@link #onClose} is called.
0073: *
0074: * <p>On the other hand, <code>onOpen</code> is sent when a popup
0075: * window (i.e., {@link #getMode} is popup) is closed due to user's activity
0076: * (such as press ESC). This event is only a notification.
0077: * In other words, the popup is hidden before the event is sent to the server.
0078: * The application cannot prevent the window from being hidden.
0079: *
0080: * @author tomyeh
0081: */
0082: public class Window extends XulElement implements IdSpace {
0083: private static final Log log = Log.lookup(Window.class);
0084: private static String _onshow = null;
0085: private transient Caption _caption;
0086:
0087: private String _border = "none";
0088: private String _title = "";
0089: /** What control and function keys to intercepts. */
0090: private String _ctrlKeys;
0091: /** The value passed to the client; parsed from _ctrlKeys. */
0092: private String _ctkeys;
0093: /** One of MODAL, EMBEDDED, OVERLAPPED, HIGHLIGHTED, POPUP. */
0094: private int _mode = EMBEDDED;
0095: /** Used for doModal. */
0096: private transient Object _mutex;
0097: /** The style used for the content block. */
0098: private String _cntStyle;
0099: /** The style class used for the content block. */
0100: private String _cntscls;
0101: /** How to position the window. */
0102: private String _pos;
0103: /** Whether to show a close button. */
0104: private boolean _closable;
0105: /** Whether the window is sizable. */
0106: private boolean _sizable;
0107:
0108: /** Embeds the window as normal component. */
0109: private static final int EMBEDDED = 0;
0110: /** Makes the window as a modal dialog. once {@link #doModal}
0111: * is called, the execution of the event processing thread
0112: * is suspended until one of the following occurs.
0113: * <ol>
0114: * <li>{@link #setMode} is called with a mode other than MODAL.</li>
0115: * <li>Either {@link #doOverlapped}, {@link #doPopup},
0116: * {@link #doEmbedded}, or {@link #doHighlighted} is called.</li>
0117: * <li>{@link #setVisible} is called with false.</li>
0118: * <li>The window is detached from the window.</li>
0119: * </ol>
0120: *
0121: * <p>Note: In the last two cases, the mode becomes {@link #OVERLAPPED}.
0122: * In other words, one might say a modal window is a special overlapped window.
0123: *
0124: * @see #HIGHLIGHTED
0125: */
0126: private static final int MODAL = 1;
0127: /** Makes the window as overlapped other components.
0128: */
0129: private static final int OVERLAPPED = 2;
0130: /** Makes the window as popup.
0131: * It is similar to {@link #OVERLAPPED}, except it is auto hidden
0132: * when user clicks outside of the window.
0133: */
0134: private static final int POPUP = 3;
0135: /** Makes the window as hilighted.
0136: * Its visual effect is the same as {@link #MODAL}.
0137: * However, from the server side's viewpoint, it is similar to
0138: * {@link #OVERLAPPED}. The execution won't be suspended when
0139: * {@link #doHighlighted} is called.
0140: *
0141: * @see #MODAL
0142: * @see #OVERLAPPED
0143: */
0144: private static final int HIGHLIGHTED = 4;
0145:
0146: public Window() {
0147: init();
0148: }
0149:
0150: /**
0151: * @param title the window title (see {@link #setTitle}).
0152: * @param border the border (see {@link #setBorder}).
0153: * @param closable whether it is closable (see {@link #setClosable}).
0154: */
0155: public Window(String title, String border, boolean closable) {
0156: this ();
0157: setTitle(title);
0158: setBorder(border);
0159: setClosable(closable);
0160: }
0161:
0162: private void init() {
0163: _mutex = new Object();
0164: }
0165:
0166: /**
0167: * Sets the action of window component to show the animating effect by default.
0168: *
0169: * <p>Default: null. In other words, if the property is null, it will refer to
0170: * the configuration of zk.xml to find the preference with
0171: * "org.zkoss.zul.Window.defaultActionOnShow", if any. For example,
0172: * <pre><preference>
0173: * <name>org.zkoss.zul.Window.defaultActionOnShow</name>
0174: * <value>moveDown</value>
0175: * </preference></pre>
0176: * Otherwise, the animating
0177: * effect is depended on component itself.</p>
0178: * <p>In JavaScript, the property will match the same function name with the
0179: * prefix "anima.". For example, if the property is "moveDown", the function name
0180: * should be "anima.moveDown" accordingly.</p>
0181: * <p><strong>Node:</strong> The method is available in modal mode only. And if
0182: * the onshow command of client-side action has been assigned on the
0183: * component, its priority is higher than this method.<br/>
0184: * For example,
0185: * <pre>action="onshow:anima.appear(#{self});"</pre>
0186: * </p>
0187: *
0188: * @param onshow the function name in JavaScript. You could use the following
0189: * animations, e.g. "moveDown", "moveRight", "moveDiagonal", "appear",
0190: * "slideDown", and so forth.
0191: * @since 3.0.2
0192: */
0193: public static void setDefaultActionOnShow(String onshow) {
0194: if (!Objects.equals(_onshow, onshow))
0195: _onshow = onshow;
0196: }
0197:
0198: /**
0199: * Returns the animating name of function.
0200: * @since 3.0.2
0201: */
0202: public static String getDefaultActionOnShow() {
0203: return _onshow;
0204: }
0205:
0206: /** Returns the caption of this window.
0207: */
0208: public Caption getCaption() {
0209: return _caption;
0210: }
0211:
0212: /** Returns the border.
0213: * The border actually controls what the content style class is
0214: * is used. In fact, the name of the border (except "normal")
0215: * is generate as part of the style class used for the content block.
0216: * Refer to {@link #getContentSclass} for more details.
0217: *
0218: * <p>Default: "none".
0219: */
0220: public String getBorder() {
0221: return _border;
0222: }
0223:
0224: /** Sets the border (either none or normal).
0225: *
0226: * @param border the border. If null or "0", "none" is assumed.
0227: * Since 2.4.1, We assume "0" to be "none".
0228: */
0229: public void setBorder(String border) {
0230: if (border == null || "0".equals(border))
0231: border = "none";
0232: if (!Objects.equals(_border, border)) {
0233: _border = border;
0234: smartUpdate("class", getSclass());
0235: }
0236: }
0237:
0238: /** Returns the title.
0239: * Besides this attribute, you could use {@link Caption} to define
0240: * a more sophiscated caption (aka., title).
0241: * <p>If a window has a caption whose label ({@link Caption#getLabel})
0242: * is not empty, then this attribute is ignored.
0243: * <p>Default: empty.
0244: */
0245: public String getTitle() {
0246: return _title;
0247: }
0248:
0249: /** Sets the title.
0250: */
0251: public void setTitle(String title) {
0252: if (title == null)
0253: title = "";
0254: if (!Objects.equals(_title, title)) {
0255: _title = title;
0256: if (_caption != null)
0257: _caption.invalidate();
0258: else
0259: invalidate();
0260: }
0261: }
0262:
0263: /** Returns what keystrokes to intercept.
0264: * <p>Default: null.
0265: */
0266: public String getCtrlKeys() {
0267: return _ctrlKeys;
0268: }
0269:
0270: /** Sets what keystrokes to intercept.
0271: *
0272: * <p>The string could be a combination of the following:
0273: * <dl>
0274: * <dt>^k</dt>
0275: * <dd>A control key, i.e., Ctrl+k, where k could be a~z, 0~9, #n</dd>
0276: * <dt>@k</dt>
0277: * <dd>A alt key, i.e., Alt+k, where k could be a~z, 0~9, #n</dd>
0278: * <dt>$k</dt>
0279: * <dd>A shift key, i.e., Shift+k, where k could be #n</dd>
0280: * <dt>#home</dt>
0281: * <dd>Home</dd>
0282: * <dt>#end</dt>
0283: * <dd>End</dd>
0284: * <dt>#ins</dt>
0285: * <dd>Insert</dd>
0286: * <dt>#del</dt>
0287: * <dd>Delete</dd>
0288: * <dt>#left</dt>
0289: * <dd>Left arrow</dd>
0290: * <dt>#right</dt>
0291: * <dd>Right arrow</dd>
0292: * <dt>#up</dt>
0293: * <dd>Up arrow</dd>
0294: * <dt>#down</dt>
0295: * <dd>Down arrow</dd>
0296: * <dt>#pgup</dt>
0297: * <dd>PageUp</dd>
0298: * <dt>#pgdn</dt>
0299: * <dd>PageDn</dd>
0300: * <dt>#f1 #f2 ... #f12</dt>
0301: * <dd>Function keys representing F1, F2, ... F12</dd>
0302: * </dl>
0303: *
0304: * <p>For example,
0305: * <dl>
0306: * <dt>^a^d@c#f10#left#right</dt>
0307: * <dd>It means you want to intercept Ctrl+A, Ctrl+D, Alt+C, F10,
0308: * Left and Right.</dd>
0309: * <dt>^#left</dt>
0310: * <dd>It means Ctrl+Left.</dd>
0311: * <dt>^#f1</dt>
0312: * <dd>It means Ctrl+F1.</dd>
0313: * <dt>@#f3</dt>
0314: * <dd>It means Alt+F3.</dd>
0315: * </dl>
0316: *
0317: * <p>Note: it doesn't support Ctrl+Alt, Shift+Ctrl, Shift+Alt or Shift+Ctrl+Alt.
0318: */
0319: public void setCtrlKeys(String ctrlKeys) throws UiException {
0320: if (ctrlKeys != null && ctrlKeys.length() == 0)
0321: ctrlKeys = null;
0322: if (!Objects.equals(_ctrlKeys, ctrlKeys)) {
0323: parseCtrlKeys(ctrlKeys);
0324: smartUpdate("z.ctkeys", _ctkeys);
0325: }
0326: }
0327:
0328: private void parseCtrlKeys(String keys) throws UiException {
0329: if (keys == null || keys.length() == 0) {
0330: _ctrlKeys = _ctkeys = null;
0331: return;
0332: }
0333:
0334: final StringBuffer sbctl = new StringBuffer(), sbsft = new StringBuffer(), sbalt = new StringBuffer(), sbext = new StringBuffer();
0335: StringBuffer sbcur = null;
0336: for (int j = 0, len = keys.length(); j < len; ++j) {
0337: char cc = keys.charAt(j);
0338: switch (cc) {
0339: case '^':
0340: case '$':
0341: case '@':
0342: if (sbcur != null)
0343: throw new WrongValueException(
0344: "Combination of Shift, Alt and Ctrl not supported: "
0345: + keys);
0346: sbcur = cc == '^' ? sbctl : cc == '@' ? sbalt : sbsft;
0347: break;
0348: case '#': {
0349: int k = j + 1;
0350: for (; k < len; ++k) {
0351: final char c2 = (char) keys.charAt(k);
0352: if ((c2 > 'Z' || c2 < 'A')
0353: && (c2 > 'z' || c2 < 'a')
0354: && (c2 > '9' || c2 < '0'))
0355: break;
0356: }
0357: if (k == j + 1)
0358: throw new WrongValueException(
0359: MCommon.UNEXPECTED_CHARACTER, new Object[] {
0360: new Character(cc), keys });
0361:
0362: final String s = keys.substring(j + 1, k).toLowerCase();
0363: if ("pgup".equals(s))
0364: cc = 'A';
0365: else if ("pgdn".equals(s))
0366: cc = 'B';
0367: else if ("end".equals(s))
0368: cc = 'C';
0369: else if ("home".equals(s))
0370: cc = 'D';
0371: else if ("left".equals(s))
0372: cc = 'E';
0373: else if ("up".equals(s))
0374: cc = 'F';
0375: else if ("right".equals(s))
0376: cc = 'G';
0377: else if ("down".equals(s))
0378: cc = 'H';
0379: else if ("ins".equals(s))
0380: cc = 'I';
0381: else if ("del".equals(s))
0382: cc = 'J';
0383: else if (s.length() > 1 && s.charAt(0) == 'f') {
0384: final int v;
0385: try {
0386: v = Integer.parseInt(s.substring(1));
0387: } catch (Throwable ex) {
0388: throw new WrongValueException("Unknown #" + s
0389: + " in " + keys);
0390: }
0391: if (v == 0 || v > 12)
0392: throw new WrongValueException(
0393: "Unsupported function key: #f" + v);
0394: cc = (char) ('O' + v); //'P': F1, 'Q': F2... 'Z': F12
0395: } else
0396: throw new WrongValueException("Unknown #" + s
0397: + " in " + keys);
0398:
0399: if (sbcur == null)
0400: sbext.append(cc);
0401: else {
0402: sbcur.append(cc);
0403: sbcur = null;
0404: }
0405: j = k - 1;
0406: }
0407: break;
0408: default:
0409: if (sbcur == null
0410: || ((cc > 'Z' || cc < 'A')
0411: && (cc > 'z' || cc < 'a') && (cc > '9' || cc < '0')))
0412: throw new WrongValueException(
0413: MCommon.UNEXPECTED_CHARACTER, new Object[] {
0414: new Character(cc), keys });
0415: if (sbcur == sbsft)
0416: throw new WrongValueException("$" + cc
0417: + " not supported: " + keys);
0418:
0419: if (cc <= 'Z' && cc >= 'A')
0420: cc = (char) (cc + ('a' - 'A')); //to lower case
0421: sbcur.append(cc);
0422: sbcur = null;
0423: break;
0424: }
0425: }
0426:
0427: _ctkeys = new StringBuffer().append('^').append(sbctl).append(
0428: ';').append('@').append(sbalt).append(';').append('$')
0429: .append(sbsft).append(';').append('#').append(sbext)
0430: .append(';').toString();
0431: _ctrlKeys = keys;
0432: }
0433:
0434: /** Returns the current mode.
0435: * One of "modal", "embedded", "overlapped", "popup", and "highlighted".
0436: */
0437: public String getMode() {
0438: return modeToString(_mode);
0439: }
0440:
0441: private static String modeToString(int mode) {
0442: switch (mode) {
0443: case MODAL:
0444: return "modal";
0445: case POPUP:
0446: return "popup";
0447: case OVERLAPPED:
0448: return "overlapped";
0449: case HIGHLIGHTED:
0450: return "highlighted";
0451: default:
0452: return "embedded";
0453: }
0454: }
0455:
0456: /** Sets the mode to overlapped, popup, modal, embedded or highlighted.
0457: *
0458: * <p>Notice: {@link Events#ON_MODAL} is posted if you specify
0459: * "modal" to this method and in a thread other than an event
0460: * listener ({@link Events#inEventListener}).
0461: * In other words, if this method is called with modal and
0462: * <em>not</em> in any event listener, the mode won't be changed
0463: * immediately (until {@link Events#ON_MODAL} is processed later).
0464: *
0465: * @param name the mode which could be one of
0466: * "embedded", "overlapped", "popup", "modal", "highlighted".
0467: * Note: it cannot be "modal". Use {@link #doModal} instead.
0468: *
0469: * @exception InterruptedException thrown if "modal" is specified,
0470: * and one of the following conditions occurs:
0471: * 1) the desktop or the Web application is being destroyed, or
0472: * 2) {@link org.zkoss.zk.ui.sys.DesktopCtrl#ceaseSuspendedThread}.
0473: * To tell the difference, check the getMessage method of InterruptedException.
0474: */
0475: public void setMode(String name) throws InterruptedException {
0476: if ("popup".equals(name))
0477: doPopup();
0478: else if ("overlapped".equals(name))
0479: doOverlapped();
0480: else if ("embedded".equals(name))
0481: doEmbedded();
0482: else if ("modal".equals(name))
0483: doModal();
0484: else if ("highlighted".equals(name))
0485: doHighlighted();
0486: else
0487: throw new WrongValueException("Uknown mode: " + name);
0488: }
0489:
0490: /** Sets the mode to overlapped, popup, modal, embedded or highlighted.
0491: *
0492: * @see #setMode(String)
0493: */
0494: public void setMode(int mode) throws InterruptedException {
0495: switch (mode) {
0496: case POPUP:
0497: doPopup();
0498: break;
0499: case OVERLAPPED:
0500: doOverlapped();
0501: break;
0502: case EMBEDDED:
0503: doEmbedded();
0504: break;
0505: case MODAL:
0506: doModal();
0507: break;
0508: case HIGHLIGHTED:
0509: doHighlighted();
0510: break;
0511: default:
0512: throw new WrongValueException("Unknown mode: " + mode);
0513: }
0514: }
0515:
0516: /** Returns whether this is a modal dialog.
0517: */
0518: public boolean inModal() {
0519: return _mode == MODAL;
0520: }
0521:
0522: /** Returns whether this is embedded with other components (Default).
0523: * @see #doEmbedded
0524: */
0525: public boolean inEmbedded() {
0526: return _mode == EMBEDDED;
0527: }
0528:
0529: /** Returns whether this is a overlapped window.
0530: */
0531: public boolean inOverlapped() {
0532: return _mode == OVERLAPPED;
0533: }
0534:
0535: /** Returns whether this is a popup window.
0536: */
0537: public boolean inPopup() {
0538: return _mode == POPUP;
0539: }
0540:
0541: /** Returns whether this is a highlighted window.
0542: */
0543: public boolean inHighlighted() {
0544: return _mode == HIGHLIGHTED;
0545: }
0546:
0547: /** Makes this window as a modal dialog.
0548: * It will automatically center the window (ignoring {@link #getLeft} and
0549: * {@link #getTop}).
0550: *
0551: * <p>Notice: {@link Events#ON_MODAL} is posted if you specify
0552: * "modal" to this method and in a thread other than an event
0553: * listener ({@link Events#inEventListener}).
0554: * In other words, if this method is called with modal and
0555: * <em>not</em> in any event listener, the mode won't be changed
0556: * immediately (until {@link Events#ON_MODAL} is processed later).
0557: *
0558: * @exception SuspendNotAllowedException if there are too many suspended
0559: * processing thread than the deployer allows.
0560: * By default, there is no limit of # of suspended threads.
0561: * @exception InterruptedException thrown if the desktop or
0562: * the Web application is being destroyed, or
0563: * {@link org.zkoss.zk.ui.sys.DesktopCtrl#ceaseSuspendedThread}.
0564: * To tell the difference, check the getMessage method of InterruptedException.
0565: */
0566: public void doModal() throws InterruptedException,
0567: SuspendNotAllowedException {
0568: Desktop desktop = getDesktop();
0569: if (desktop == null)
0570: desktop = Executions.getCurrent().getDesktop();
0571: if (!desktop.getWebApp().getConfiguration()
0572: .isEventThreadEnabled()) {
0573: handleFailedModal(_mode, isVisible());
0574: throw new SuspendNotAllowedException(
0575: "Event processing thread is disabled");
0576: }
0577:
0578: checkOverlappable(MODAL);
0579:
0580: if (_mode != MODAL) {
0581: if (!Events.inEventListener()) {
0582: Events.postEvent(Events.ON_MODAL, this , null);
0583: return; //done
0584: }
0585:
0586: int oldmode = _mode;
0587: boolean oldvisi = isVisible();
0588:
0589: invalidate();
0590: setVisible(true); //if MODAL, it must be visible; vice versa
0591:
0592: try {
0593: enterModal();
0594: } catch (SuspendNotAllowedException ex) {
0595: handleFailedModal(oldmode, oldvisi);
0596: throw ex;
0597: }
0598: }
0599: }
0600:
0601: private void handleFailedModal(int oldmode, boolean oldvisi) {
0602: try {
0603: if (Executions.getCurrent().getAttribute(
0604: "javax.servlet.error.exception") != null) {
0605: //handle it specially if it is used for dispalying err
0606: setMode(HIGHLIGHTED);
0607: } else {
0608: setMode(oldmode); //restore
0609: setVisible(oldvisi);
0610: }
0611: } catch (Throwable ex) {
0612: log.realCauseBriefly("Causing another error", ex);
0613: }
0614: }
0615:
0616: /** Makes this window as overlapped with other components.
0617: */
0618: public void doOverlapped() {
0619: checkOverlappable(OVERLAPPED);
0620: setNonModalMode(OVERLAPPED);
0621: }
0622:
0623: /** Makes this window as popup, which is overlapped with other component
0624: * and auto-hiden when user clicks outside of the window.
0625: */
0626: public void doPopup() {
0627: checkOverlappable(POPUP);
0628: setNonModalMode(POPUP);
0629: }
0630:
0631: /** Makes this window as highlited. The visual effect is
0632: * the similar to the modal window, but, like overlapped,
0633: * it doesn't suspend (block) the execution at the server.
0634: * In other words, it is more like an overlapped window from the
0635: * server side's viewpoint.
0636: */
0637: public void doHighlighted() {
0638: checkOverlappable(HIGHLIGHTED);
0639: setNonModalMode(HIGHLIGHTED);
0640: }
0641:
0642: /** Makes this window as embeded with other components (Default).
0643: */
0644: public void doEmbedded() {
0645: setNonModalMode(EMBEDDED);
0646: }
0647:
0648: /* Set non-modal mode. */
0649: private void setNonModalMode(int mode) {
0650: if (_mode != mode) {
0651: if (_mode == MODAL)
0652: leaveModal();
0653: _mode = mode;
0654: invalidate();
0655: }
0656: setVisible(true);
0657: }
0658:
0659: /** Set mode to MODAL and suspend this thread. */
0660: private void enterModal() throws InterruptedException {
0661: _mode = MODAL;
0662: //no need to synchronized (_mutex) because no racing is possible
0663: Executions.wait(_mutex);
0664: }
0665:
0666: /** Resumes the suspendded thread and set mode to OVERLAPPED. */
0667: private void leaveModal() {
0668: _mode = OVERLAPPED;
0669: Executions.notifyAll(_mutex);
0670: }
0671:
0672: /** Makes sure it is not draggable. */
0673: private void checkOverlappable(int mode) {
0674: if (!"false".equals(getDraggable()))
0675: throw new UiException(
0676: "Draggable window cannot be modal, overlapped, popup, or highlighted: "
0677: + this );
0678:
0679: if (mode == MODAL || mode == HIGHLIGHTED)
0680: for (Component comp = this ; (comp = comp.getParent()) != null;)
0681: if (!comp.isVisible())
0682: throw new UiException(
0683: "One of its ancestors, "
0684: + comp
0685: + ", is not visible, so unable to be modal or highlighted");
0686: }
0687:
0688: /** Returns whether to show a close button on the title bar.
0689: */
0690: public boolean isClosable() {
0691: return _closable;
0692: }
0693:
0694: /** Sets whether to show a close button on the title bar.
0695: * If closable, a button is displayed and the onClose event is sent
0696: * if an user clicks the button.
0697: *
0698: * <p>Default: false.
0699: *
0700: * <p>You can intercept the default behavior by either overriding
0701: * {@link #onClose}, or listening the onClose event.
0702: *
0703: * <p>Note: the close button won't be displayed if no title or caption at all.
0704: */
0705: public void setClosable(boolean closable) {
0706: if (_closable != closable) {
0707: _closable = closable;
0708: invalidate(); //re-init is required
0709: }
0710: }
0711:
0712: /** Returns whether the window is sizable.
0713: */
0714: public boolean isSizable() {
0715: return _sizable;
0716: }
0717:
0718: /** Sets whether the window is sizable.
0719: * If true, an user can drag the border to change the window width.
0720: * <p>Default: false.
0721: */
0722: public void setSizable(boolean sizable) {
0723: if (_sizable != sizable) {
0724: _sizable = sizable;
0725: smartUpdate("z.sizable", sizable);
0726: }
0727: }
0728:
0729: /** Returns how to position the window at the client screen.
0730: * It is meaningless if the embedded mode is used.
0731: *
0732: * <p>Default: null which depends on {@link #getMode}:
0733: * If overlapped or popup, {@link #setLeft} and {@link #setTop} are
0734: * assumed. If modal or highlighted, it is centered.
0735: */
0736: public String getPosition() {
0737: return _pos;
0738: }
0739:
0740: /** Sets how to position the window at the client screen.
0741: * It is meaningless if the embedded mode is used.
0742: *
0743: * @param pos how to position. It can be null (the default), or
0744: * a combination of the following values (by separating with comma).
0745: * <dl>
0746: * <dt>center</dt>
0747: * <dd>Position the window at the center. {@link #setTop} and {@link #setLeft}
0748: * are both ignored.</dd>
0749: * <dt>left</dt>
0750: * <dd>Position the window at the left edge. {@link #setLeft} is ignored.</dd>
0751: * <dt>right</dt>
0752: * <dd>Position the window at the right edge. {@link #setLeft} is ignored.</dd>
0753: * <dt>top</dt>
0754: * <dd>Position the window at the top edge. {@link #setTop} is ignored.</dd>
0755: * <dt>bottom</dt>
0756: * <dd>Position the window at the bottom edge. {@link #setTop} is ignored.</dd>
0757: * <dt>parent</dt>
0758: * <dd>Position the window relative to its parent.
0759: * That is, the left and top ({@link #getTop} and {@link #getLeft})
0760: * is an offset to his parent's let-top corner. (since 3.0.2)</dd>
0761: * </dl>
0762: * <p>For example, "left,center" means to position it at the center of
0763: * the left edge.
0764: */
0765: public void setPosition(String pos) {
0766: //Note: we always update since the window might be dragged by an user
0767: _pos = pos;
0768: if (_mode != EMBEDDED)
0769: smartUpdate("z.pos", pos);
0770: }
0771:
0772: /** Process the onClose event sent when the close button is pressed.
0773: * <p>Default: detach itself.
0774: */
0775: public void onClose() {
0776: detach();
0777: }
0778:
0779: /** Process the onModal event by making itself a modal window.
0780: */
0781: public void onModal() throws InterruptedException {
0782: doModal();
0783: }
0784:
0785: /** Returns the CSS style for the content block of the window.
0786: */
0787: public String getContentStyle() {
0788: return _cntStyle;
0789: }
0790:
0791: /** Sets the CSS style for the content block of the window.
0792: *
0793: * <p>Default: null.
0794: */
0795: public void setContentStyle(String style) {
0796: if (!Objects.equals(_cntStyle, style)) {
0797: _cntStyle = style;
0798: smartUpdate("z.cntStyle", _cntStyle);
0799: }
0800: }
0801:
0802: /** Returns the style class used for the content block.
0803: *
0804: * <p>If {@link #setContentSclass} was called with a non-empty value,
0805: * say, "mycnt", then
0806: * <ol>
0807: * <li>Case 1: If {@link #getBorder} is "normal", "mycnt" is returned.</li>
0808: * <li>Case 2: Otherwise, "mycnt-<i>border</i>" is returned
0809: * where <i>border</i> is the value returned by {@link #getBorder}.</li>
0810: * </ol>
0811: *
0812: * <p>If {@link #setContentSclass} was not called, or called with null,
0813: * then the content style class is decided by {@link #getSclass} as follows:
0814: * <ol>
0815: * <li>Case 1: If {@link #getBorder} is "normal", "wc-<i>sclass</i>" is
0816: * returned, where <i>sclass</i> is the value returned by {@link #getSclass}.</li>
0817: * <li>Otherwise, "wc-<i>mode</i>-<i>border</i>",
0818: * where <i>border</i> is the value returned by {@link #getBorder}.</li>
0819: * </li>
0820: * @see #setContentSclass
0821: */
0822: public String getContentSclass() {
0823: String cntscls = _cntscls;
0824: if (cntscls == null) {
0825: cntscls = getSclass();
0826: cntscls = cntscls != null ? "wc-" + cntscls : "wc";
0827: }
0828: final String border = getBorder();
0829: return "normal".equals(border) ? cntscls : cntscls + '-'
0830: + border;
0831: }
0832:
0833: /** Sets the style class used for the content block.
0834: *
0835: * @see #getContentSclass
0836: * @since 3.0.0
0837: */
0838: public void setContentSclass(String scls) {
0839: if (!Objects.equals(_cntscls, scls)) {
0840: _cntscls = scls;
0841: smartUpdate("z.cntScls", getContentSclass());
0842: }
0843: }
0844:
0845: /** Returns the style class used for the title.
0846: *
0847: * <p>It returns "wt-<i>sclass</i>" is returned,
0848: * where <i>sclass</i> is the value returned by {@link #getSclass}.
0849: */
0850: public String getTitleSclass() {
0851: return "wt-" + getSclass();
0852: }
0853:
0854: //-- super --//
0855: /** Returns the style class.
0856: * If the style class is not defined ({@link #setSclass} is not called
0857: * or called with null or empty), it returns {@link #getMode}.
0858: * In other words, the style class is, by default, the same as
0859: * the mode name.
0860: */
0861: public String getSclass() {
0862: final String scls = super .getSclass();
0863: return scls != null ? scls : getMode();
0864: }
0865:
0866: public void setSclass(String sclass) {
0867: if (sclass != null && sclass.length() == 0)
0868: sclass = null;
0869: if (!Objects.equals(super .getSclass(), sclass)) {
0870: super .setSclass(sclass);
0871: invalidate();
0872: }
0873: }
0874:
0875: //-- Component --//
0876: public boolean insertBefore(Component child, Component insertBefore) {
0877: if (child instanceof Caption) {
0878: if (_caption != null && _caption != child)
0879: throw new UiException("Only one caption is allowed: "
0880: + this );
0881: insertBefore = getFirstChild();
0882: //always makes caption as the first child
0883: _caption = (Caption) child;
0884: invalidate();
0885: } else if (insertBefore instanceof Caption) {
0886: throw new UiException("caption must be the first child");
0887: }
0888: return super .insertBefore(child, insertBefore);
0889: }
0890:
0891: public void onChildRemoved(Component child) {
0892: if (child instanceof Caption) {
0893: _caption = null;
0894: invalidate();
0895: }
0896: super .onChildRemoved(child);
0897: }
0898:
0899: public void setPage(Page page) {
0900: super .setPage(page);
0901:
0902: if (page == null && _mode == MODAL)
0903: leaveModal();
0904: }
0905:
0906: public void setParent(Component parent) {
0907: super .setParent(parent);
0908:
0909: if (_mode == MODAL && getPage() == null)
0910: leaveModal();
0911: }
0912:
0913: /** Changes the visibility of the window.
0914: *
0915: * <p>Note: If a modal dialog becomes invisible, the modal state
0916: * will be ended automatically. In other words, the mode ({@link #getMode})
0917: * will become {@link #OVERLAPPED} and the suspending thread is resumed.
0918: */
0919: public boolean setVisible(boolean visible) {
0920: if (!visible && _mode == MODAL) {
0921: leaveModal();
0922: invalidate();
0923: } else if (_mode != EMBEDDED)
0924: smartUpdate("z.visible", visible);
0925: return super .setVisible(visible);
0926: }
0927:
0928: //-- super --//
0929: public void setDraggable(String draggable) {
0930: if (_mode != EMBEDDED) {
0931: if (draggable != null
0932: && (draggable.length() > 0 && !"false"
0933: .equals(draggable)))
0934: throw new UiException(
0935: "Only embedded window could be draggable: "
0936: + this );
0937: }
0938: super .setDraggable(draggable);
0939: }
0940:
0941: protected String getRealStyle() {
0942: final String style = super .getRealStyle();
0943: return _mode != EMBEDDED ? (isVisible() ? "position:absolute;display:none;"
0944: : "position:absolute;")
0945: + style
0946: : style;
0947: //If no absolute, Opera ignores left and top
0948: //
0949: //If not embedded we always generate display:none to have
0950: //better visual effect (the client will turn it on in zkWnd.init)
0951: }
0952:
0953: public String getOuterAttrs() {
0954: final StringBuffer sb = new StringBuffer(64).append(super
0955: .getOuterAttrs());
0956: appendAsapAttr(sb, Events.ON_MOVE);
0957: appendAsapAttr(sb, Events.ON_SIZE);
0958: appendAsapAttr(sb, Events.ON_Z_INDEX);
0959: appendAsapAttr(sb, Events.ON_OK);
0960: appendAsapAttr(sb, Events.ON_CANCEL);
0961: appendAsapAttr(sb, Events.ON_CTRL_KEY);
0962: appendAsapAttr(sb, Events.ON_OPEN);
0963: //no need to generate ON_CLOSE since it is always sent (as ASAP)
0964:
0965: final String clkattrs = getAllOnClickAttrs(false);
0966: if (clkattrs != null)
0967: sb.append(clkattrs);
0968: //though widget.js handles onclick (if 3d), it is useful
0969: //to support onClick for groupbox
0970: final String aos = getDefaultActionOnShow() != null ? getDefaultActionOnShow()
0971: : getDesktop()
0972: .getWebApp()
0973: .getConfiguration()
0974: .getPreference(
0975: "org.zkoss.zul.Window.defaultActionOnShow",
0976: null);
0977: if (aos != null)
0978: HTMLs.appendAttribute(sb, "z.aos",
0979: aos.length() == 0 ? "z_none" : aos);
0980: if (_closable)
0981: sb.append(" z.closable=\"true\"");
0982: if (_sizable)
0983: sb.append(" z.sizable=\"true\"");
0984:
0985: if (_mode != EMBEDDED) {
0986: if (_pos != null)
0987: HTMLs.appendAttribute(sb, "z.pos", _pos);
0988: HTMLs.appendAttribute(sb, "z.mode", getMode());
0989: HTMLs.appendAttribute(sb, "z.visible", isVisible());
0990: }
0991:
0992: HTMLs.appendAttribute(sb, "z.ctkeys", _ctkeys);
0993: return sb.toString();
0994: }
0995:
0996: //Cloneable//
0997: public Object clone() {
0998: final Window clone = (Window) super .clone();
0999: clone.init();
1000: if (clone._caption != null)
1001: clone.afterUnmarshal();
1002: return clone;
1003: }
1004:
1005: private void afterUnmarshal() {
1006: for (Iterator it = getChildren().iterator(); it.hasNext();) {
1007: final Object child = it.next();
1008: if (child instanceof Caption) {
1009: _caption = (Caption) child;
1010: break;
1011: }
1012: }
1013: }
1014:
1015: //Serializable//
1016: private synchronized void readObject(java.io.ObjectInputStream s)
1017: throws java.io.IOException, ClassNotFoundException {
1018: s.defaultReadObject();
1019: init();
1020: afterUnmarshal();
1021: }
1022:
1023: //-- ComponentCtrl --//
1024: protected Object newExtraCtrl() {
1025: return new ExtraCtrl();
1026: }
1027:
1028: /** A utility class to implement {@link #getExtraCtrl}.
1029: * It is used only by component developers.
1030: */
1031: protected class ExtraCtrl extends XulElement.ExtraCtrl implements
1032: MultiBranch, Openable, Floating {
1033: //-- MultiBranch --//
1034: public boolean inDifferentBranch(Component child) {
1035: return child instanceof Caption; //in different branch
1036: }
1037:
1038: //-- Openable --//
1039: public void setOpenByClient(boolean open) {
1040: setVisible(open);
1041: }
1042:
1043: //Floating//
1044: public boolean isFloating() {
1045: return _mode != EMBEDDED;
1046: }
1047: }
1048: }
|