001: /* Tabbox.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Tue Jul 12 10:42:31 2005, Created by tomyeh
010: }}IS_NOTE
011:
012: Copyright (C) 2005 Potix Corporation. All Rights Reserved.
013:
014: {{IS_RIGHT
015: This program is distributed under GPL Version 2.0 in the hope that
016: it will be useful, but WITHOUT ANY WARRANTY.
017: }}IS_RIGHT
018: */
019: package org.zkoss.zul;
020:
021: import java.util.Iterator;
022:
023: import org.zkoss.lang.Objects;
024: import org.zkoss.xml.HTMLs;
025:
026: import org.zkoss.zk.ui.Component;
027: import org.zkoss.zk.ui.UiException;
028: import org.zkoss.zk.ui.WrongValueException;
029: import org.zkoss.zk.ui.event.Event;
030: import org.zkoss.zk.ui.event.Events;
031: import org.zkoss.zk.ui.event.EventListener;
032: import org.zkoss.zk.ui.event.Deferrable;
033: import org.zkoss.zk.ui.ext.render.ChildChangedAware;
034: import org.zkoss.zk.au.out.AuInvoke;
035:
036: import org.zkoss.zul.impl.XulElement;
037:
038: /**
039: * A tabbox.
040: *
041: * <p>Event:
042: * <ol>
043: * <li>org.zkoss.zk.ui.event.SelectEvent is sent when user changes
044: * the tab.</li>
045: * </ol>
046: *
047: * <p>Mold:
048: * <dl>
049: * <dt>default</dt>
050: * <dd>The default tabbox.</dd>
051: * <dt>accordion</dt>
052: * <dd>The accordion tabbox.</dd>
053: * </dl>
054: *
055: * <p>Note: {@link #getSclass} also controls {@link Tabs} and {@link Tab}.
056: * Refer to {@link #getTabLook} for details.
057: *
058: * @author tomyeh
059: */
060: public class Tabbox extends XulElement {
061: private transient Tabs _tabs;
062: private transient Tabpanels _tabpanels;
063: private transient Tab _seltab;
064: private String _panelSpacing;
065: private String _orient = "horizontal";
066: /** The event listener used to listen onSelect for each tab. */
067: /*package*/transient EventListener _listener;
068:
069: public Tabbox() {
070: init();
071: }
072:
073: private void init() {
074: _listener = new Listener();
075: }
076:
077: /** Returns whether it is in the accordion mold.
078: */
079: /*package*/boolean inAccordionMold() {
080: return "accordion".equals(getMold());
081: }
082:
083: /** Returns the tabs that this tabbox owns.
084: */
085: public Tabs getTabs() {
086: return _tabs;
087: }
088:
089: /** Returns the tabpanels that this tabbox owns.
090: */
091: public Tabpanels getTabpanels() {
092: return _tabpanels;
093: }
094:
095: /** Returns the spacing between {@link Tabpanel}.
096: * This is used by certain molds, such as accordion.
097: * <p>Default: null (no spacing).
098: */
099: public String getPanelSpacing() {
100: return _panelSpacing;
101: }
102:
103: /** Sets the spacing between {@link Tabpanel}.
104: * This is used by certain molds, such as accordion.
105: */
106: public void setPanelSpacing(String panelSpacing) {
107: if (panelSpacing != null && panelSpacing.length() == 0)
108: panelSpacing = null;
109:
110: if (!Objects.equals(_panelSpacing, panelSpacing)) {
111: _panelSpacing = panelSpacing;
112: invalidate();
113: }
114: }
115:
116: /** Returns the selected index.
117: */
118: public int getSelectedIndex() {
119: return _seltab != null ? _seltab.getIndex() : -1;
120: }
121:
122: /*** Sets the selected index.
123: */
124: public void setSelectedIndex(int j) {
125: final Tabs tabs = getTabs();
126: if (tabs == null)
127: throw new IllegalStateException("No tab at all");
128: setSelectedTab((Tab) tabs.getChildren().get(j));
129: }
130:
131: /** Returns the selected tab panel.
132: */
133: public Tabpanel getSelectedPanel() {
134: return _seltab != null ? _seltab.getLinkedPanel() : null;
135: }
136:
137: /** Sets the selected tab panel.
138: */
139: public void setSelectedPanel(Tabpanel panel) {
140: if (panel != null && panel.getTabbox() != this )
141: throw new UiException("Not a child: " + panel);
142: final Tab tab = panel.getLinkedTab();
143: if (tab != null)
144: setSelectedTab(tab);
145: }
146:
147: /** Returns the selected tab.
148: */
149: public Tab getSelectedTab() {
150: return _seltab;
151: }
152:
153: /** Sets the selected tab.
154: */
155: public void setSelectedTab(Tab tab) {
156: selectTabDirectly(tab, false);
157: }
158:
159: /** Sets the selected tab. */
160: /*packge*/void selectTabDirectly(Tab tab, boolean byClient) {
161: if (tab == null)
162: throw new IllegalArgumentException("null tab");
163: if (tab.getTabbox() != this )
164: throw new UiException("Not my child: " + tab);
165: if (tab != _seltab) {
166: if (_seltab != null)
167: _seltab.setSelectedDirectly(false);
168:
169: _seltab = tab;
170: _seltab.setSelectedDirectly(true);
171: if (!byClient)
172: response("sel", new AuInvoke(_seltab, "selTab"));
173: }
174: }
175:
176: /** Returns the orient.
177: *
178: * <p>Default: "horizontal".
179: *
180: * <p>Note: only the default mold supports it (not supported if accordion).
181: */
182: public String getOrient() {
183: return _orient;
184: }
185:
186: /** Sets the orient.
187: * @param orient either "horizontal" or "vertical".
188: */
189: public void setOrient(String orient) throws WrongValueException {
190: if (!"horizontal".equals(orient) && !"vertical".equals(orient))
191: throw new WrongValueException(orient);
192:
193: if (!Objects.equals(_orient, orient)) {
194: _orient = orient;
195: invalidate();
196: }
197: }
198:
199: /** Returns whether it is a horizontal tabbox.
200: * @since 3.0.3
201: */
202: public boolean isHorizontal() {
203: return "horizontal".equals(getOrient());
204: }
205:
206: /** Returns whether it is a vertical tabbox.
207: * @since 3.0.3
208: */
209: public boolean isVertical() {
210: return "vertical".equals(getOrient());
211: }
212:
213: /** Returns the look of the {@link Tab} and {@link Tabbox}.
214: * It is, in fact, a portion of the style class that are used to
215: * generate the style of {@link Tabs} and {@link Tab}.
216: *
217: * <p>If the style class ({@link #getSclass}) of this tab box is not
218: * defined and the mold is default,
219: * "tab-3d" and "tab-v3d" are returned for horizontal and vertical
220: * orient, respectively.
221: * If the style class not defined and the mold is accordion,
222: * "tabaccd-3d" and "tabaccd-v3d" returned (note: accordion doesn't support vertical yet).
223: *
224: * <p>If the style class is defined, say "lite",
225: * then this method return "tab-lite" and "tab-vlite" for
226: * horizontal and vertical orient, respectively, and "tabacc-lite" for horizontal accordion.
227: *
228: * <p>If the mold is not "default" nor "accordion", this method returns
229: * "tab" + getMold() + "-" + (vertical ? 'v': '') + getSclass().
230: *
231: * <p>With this method, {@link Tab} and {@link Tabpanel} generate
232: * the style class accordingly. For example, if the mold is "default"
233: * and the style class not defined, then
234: * "tab-3d-tl-sel" for the top-left corner of the selected tab,
235: * "tab-3d-tm-uns" for the top-middle border of the
236: * non-selected tab, and so on.
237: *
238: * @since 3.0,0
239: */
240: public String getTabLook() {
241: final String mold = getMold();
242: String prefix = "default".equals(mold) ? "tab-" : "accordion"
243: .equals(mold) ? "tabaccd-" : "tab" + mold + '-';
244:
245: if ("vertical".equals(_orient))
246: prefix += 'v';
247:
248: final String scls = getSclass();
249: return scls != null && scls.length() > 0 ? prefix + scls
250: : prefix + "3d";
251: }
252:
253: //-- Component --//
254: /** Auto-creates {@link Tabpanel} and select one of tabs if necessary.
255: */
256: public void onCreate() {
257: if (_tabs != null) {
258: final int sz = _tabs.getChildren().size();
259: if (_tabpanels == null)
260: insertBefore(new Tabpanels(), null);
261: for (int n = _tabpanels.getChildren().size(); n < sz; ++n)
262: _tabpanels.insertBefore(new Tabpanel(), null);
263: if (sz > 0 && _seltab == null)
264: setSelectedTab((Tab) _tabs.getFirstChild());
265: }
266: }
267:
268: public boolean insertBefore(Component child, Component insertBefore) {
269: if (child instanceof Tabs) {
270: if (_tabs != null && _tabs != child)
271: throw new UiException("Only one tabs is allowed: "
272: + this );
273:
274: _tabs = (Tabs) child;
275: for (Iterator it = _tabs.getChildren().iterator(); it
276: .hasNext();) {
277: final Tab tab = (Tab) it.next();
278: if (tab.isSelected()) {
279: _seltab = tab;
280: break;
281: }
282: }
283:
284: addTabsListeners();
285: } else if (child instanceof Tabpanels) {
286: if (_tabpanels != null && _tabpanels != child)
287: throw new UiException("Only one tabpanels is allowed: "
288: + this );
289: _tabpanels = (Tabpanels) child;
290: } else {
291: throw new UiException("Unsupported child for tabbox: "
292: + child);
293: }
294: if (super .insertBefore(child, insertBefore)) {
295: invalidate(); //due to DSP might implemented diff for children order
296: return true;
297: }
298: return false;
299: }
300:
301: public void onChildRemoved(Component child) {
302: if (child instanceof Tabs) {
303: removeTabsListeners();
304: _tabs = null;
305: _seltab = null;
306: } else if (child instanceof Tabpanels) {
307: _tabpanels = null;
308: }
309: super .onChildRemoved(child);
310: }
311:
312: /** Removes _listener from all {@link Tab} instances. */
313: private void removeTabsListeners() {
314: if (_tabs != null) {
315: for (Iterator it = _tabs.getChildren().iterator(); it
316: .hasNext();) {
317: final Tab tab = (Tab) it.next();
318: tab.removeEventListener(Events.ON_SELECT, _listener);
319: }
320: }
321: }
322:
323: /** Adds _listener to all {@link Tab} instances. */
324: private void addTabsListeners() {
325: if (_tabs != null) {
326: for (Iterator it = _tabs.getChildren().iterator(); it
327: .hasNext();) {
328: final Tab tab = (Tab) it.next();
329: tab.addEventListener(Events.ON_SELECT, _listener);
330: }
331: }
332: }
333:
334: //-- super --//
335: public String getOuterAttrs() {
336: final StringBuffer sb = new StringBuffer(64).append(super
337: .getOuterAttrs());
338: appendAsapAttr(sb, Events.ON_RIGHT_CLICK);
339: //no z.dbclk/z.lfclk since it is covered by both Tab and Tabpanel
340:
341: if (isVertical())
342: HTMLs.appendAttribute(sb, "z.orient", "v");
343: if (_tabs != null && !inAccordionMold())
344: HTMLs.appendAttribute(sb, "z.tabs", _tabs.getUuid());
345: return sb.toString();
346: }
347:
348: //Cloneable//
349: public Object clone() {
350: final Tabbox clone = (Tabbox) super .clone();
351:
352: clone.removeTabsListeners();
353: clone.init();
354:
355: int cnt = 0;
356: if (clone._tabs != null)
357: ++cnt;
358: if (clone._tabpanels != null)
359: ++cnt;
360: if (cnt > 0)
361: clone.afterUnmarshal(cnt);
362:
363: return clone;
364: }
365:
366: private void afterUnmarshal(int cnt) {
367: for (Iterator it = getChildren().iterator(); it.hasNext();) {
368: final Object child = it.next();
369: if (child instanceof Tabs) {
370: _tabs = (Tabs) child;
371: for (Iterator e = _tabs.getChildren().iterator(); e
372: .hasNext();) {
373: final Tab tab = (Tab) e.next();
374: if (tab.isSelected()) {
375: _seltab = tab;
376: break;
377: }
378: }
379: if (--cnt == 0)
380: break;
381: } else if (child instanceof Tabpanels) {
382: _tabpanels = (Tabpanels) child;
383: if (--cnt == 0)
384: break;
385: }
386: }
387:
388: addTabsListeners();
389: }
390:
391: //-- Serializable --//
392: private synchronized void readObject(java.io.ObjectInputStream s)
393: throws java.io.IOException, ClassNotFoundException {
394: s.defaultReadObject();
395:
396: init();
397: afterUnmarshal(-1);
398: }
399:
400: //-- ComponentCtrl --//
401: protected Object newExtraCtrl() {
402: return new ExtraCtrl();
403: }
404:
405: /** A utility class to implement {@link #getExtraCtrl}.
406: * It is used only by component developers.
407: */
408: protected class ExtraCtrl extends XulElement.ExtraCtrl implements
409: ChildChangedAware {
410: //ChildChangedAware//
411: public boolean isChildChangedAware() {
412: return !inAccordionMold();
413: //we have to adjust the width of last cell
414: }
415: }
416:
417: private class Listener implements EventListener, Deferrable {
418: public void onEvent(Event event) {
419: Events.sendEvent(Tabbox.this , event);
420: }
421:
422: public boolean isDeferrable() {
423: return !Events.isListened(Tabbox.this , Events.ON_SELECT,
424: true);
425: }
426: }
427: }
|