001: /* Radiogroup.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Fri Jun 17 09:20:41 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.Strings;
024: import org.zkoss.lang.Objects;
025: import org.zkoss.lang.MutableInteger;
026:
027: import org.zkoss.zk.ui.Component;
028: import org.zkoss.zk.ui.UiException;
029: import org.zkoss.zk.ui.WrongValueException;
030: import org.zkoss.zk.ui.event.Event;
031: import org.zkoss.zk.ui.event.Events;
032: import org.zkoss.zk.ui.event.EventListener;
033: import org.zkoss.zk.ui.event.Deferrable;
034:
035: import org.zkoss.zul.impl.XulElement;
036:
037: /**
038: * A radio group.
039: *
040: * <p>Note: To support the versatile layout, a radio group accepts any kind of
041: * children, including {@link Radio}. On the other hand, the parent of
042: * a radio, if any, must be a radio group.
043: *
044: * @author tomyeh
045: */
046: public class Radiogroup extends XulElement {
047: private String _orient = "horizontal";
048: /** The name of all child radio buttons. */
049: private String _name;
050: private int _jsel = -1;
051: private transient EventListener _listener;
052:
053: public Radiogroup() {
054: _name = genGroupName();
055: init();
056: }
057:
058: private void init() {
059: _listener = new Listener();
060: }
061:
062: /** Returns the orient.
063: * <p>Default: "horizontal".
064: */
065: public String getOrient() {
066: return _orient;
067: }
068:
069: /** Sets the orient.
070: * @param orient either "horizontal" or "vertical".
071: */
072: public void setOrient(String orient) throws WrongValueException {
073: if (!"horizontal".equals(orient) && !"vertical".equals(orient))
074: throw new WrongValueException("orient cannot be " + orient);
075:
076: if (!Objects.equals(_orient, orient)) {
077: _orient = orient;
078: invalidate();
079: }
080: }
081:
082: /** Returns the radio button at the specified index.
083: */
084: public Radio getItemAtIndex(int index) {
085: if (index < 0)
086: throw new IndexOutOfBoundsException("Wrong index: " + index);
087:
088: final MutableInteger cur = new MutableInteger(0);
089: final Radio radio = getAt(this , cur, index);
090: if (radio == null)
091: throw new IndexOutOfBoundsException(index + " out of 0.."
092: + (cur.value - 1));
093: return radio;
094: }
095:
096: private static Radio getAt(Component comp, MutableInteger cur,
097: int index) {
098: for (Iterator it = comp.getChildren().iterator(); it.hasNext();) {
099: final Component child = (Component) it.next();
100: if (child instanceof Radio) {
101: if (cur.value++ == index)
102: return (Radio) child;
103: } else if (!(child instanceof Radiogroup)) { //skip nested radiogroup
104: Radio r = getAt(child, cur, index);
105: if (r != null)
106: return r;
107: }
108: }
109: return null;
110: }
111:
112: /** Returns the number of radio buttons in this group.
113: */
114: public int getItemCount() {
115: return countItems(this );
116: }
117:
118: private static int countItems(Component comp) {
119: int sum = 0;
120: for (Iterator it = comp.getChildren().iterator(); it.hasNext();) {
121: final Component child = (Component) it.next();
122: if (child instanceof Radio)
123: ++sum;
124: else if (!(child instanceof Radiogroup)) //skip nested radiogroup
125: sum += countItems(child);
126: }
127: return sum;
128: }
129:
130: /** Returns the index of the selected radio button (-1 if no one is selected).
131: */
132: public int getSelectedIndex() {
133: return _jsel;
134: }
135:
136: /** Deselects all of the currently selected radio button and selects
137: * the radio button with the given index.
138: */
139: public void setSelectedIndex(int jsel) {
140: if (jsel < 0)
141: jsel = -1;
142: if (_jsel != jsel) {
143: if (jsel < 0) {
144: getSelectedItem().setSelected(false);
145: } else {
146: getItemAtIndex(jsel).setSelected(true);
147: }
148: }
149: }
150:
151: /** Returns the selected radio button.
152: */
153: public Radio getSelectedItem() {
154: return _jsel >= 0 ? getItemAtIndex(_jsel) : null;
155: }
156:
157: /** Deselects all of the currently selected radio buttons and selects
158: * the given radio button.
159: */
160: public void setSelectedItem(Radio item) {
161: if (item == null) {
162: setSelectedIndex(-1);
163: } else {
164: if (item.getParent() != this )
165: throw new UiException("Not a child: " + item);
166: item.setSelected(true);
167: }
168: }
169:
170: /** Appends a radio button.
171: */
172: public Radio appendItem(String label, String value) {
173: final Radio item = new Radio();
174: item.setLabel(label);
175: item.setValue(value);
176: item.setParent(this );
177: return item;
178: }
179:
180: /** Removes the child radio button in the list box at the given index.
181: * @return the removed radio button.
182: */
183: public Radio removeItemAt(int index) {
184: final Radio item = getItemAtIndex(index);
185: removeChild(item);
186: return item;
187: }
188:
189: /** Returns the name of this group of radio buttons.
190: * All child radio buttons shared the same name ({@link Radio#getName}).
191: * <p>Default: automatically generated an unique name
192: * <p>Don't use this method if your application is purely based
193: * on ZK's event-driven model.
194: */
195: public String getName() {
196: return _name;
197: }
198:
199: /** Sets the name of this group of radio buttons.
200: * All child radio buttons shared the same name ({@link Radio#getName}).
201: * <p>Don't use this method if your application is purely based
202: * on ZK's event-driven model.
203: */
204: public void setName(String name) {
205: if (name != null && name.length() == 0)
206: name = null;
207: if (!Objects.equals(_name, name)) {
208: _name = name;
209: smartUpdate("name", _name);
210: }
211: }
212:
213: //utilities for radio//
214: /** Called when a radio is added to this group.
215: */
216: /*package*/void fixOnAdd(Radio child) {
217: if (_jsel >= 0 && child.isSelected()) {
218: child.setSelected(false); //it will call fixSelectedIndex
219: } else {
220: fixSelectedIndex();
221: }
222: child.addEventListener(Events.ON_CHECK, _listener);
223: }
224:
225: /** Called when a radio is removed from this group.
226: */
227: /*package*/void fixOnRemove(Radio child) {
228: if (child.isSelected()) {
229: _jsel = -1;
230: } else if (_jsel > 0) { //excluding 0
231: fixSelectedIndex();
232: }
233: child.removeEventListener(Events.ON_CHECK, _listener);
234: }
235:
236: /** Fix the selected index, _jsel, assuming there are no selected one
237: * before (and excludes) j-the radio button.
238: */
239: /*package*/void fixSelectedIndex() {
240: _jsel = fixSelIndex(this , new MutableInteger(0));
241: }
242:
243: private static int fixSelIndex(Component comp, MutableInteger cur) {
244: for (Iterator it = comp.getChildren().iterator(); it.hasNext();) {
245: final Component child = (Component) it.next();
246: if (child instanceof Radio) {
247: if (((Radio) child).isSelected())
248: return cur.value;
249: ++cur.value;
250: } else if (!(child instanceof Radiogroup)) { //skip nested radiogroup
251: int jsel = fixSelIndex(child, cur);
252: if (jsel >= 0)
253: return jsel;
254: }
255: }
256: return -1;
257: }
258:
259: /** Generates the group name for child radio buttons.
260: */
261: private String genGroupName() {
262: return Strings.encode(new StringBuffer(16).append("_pg"),
263: System.identityHashCode(this )).toString();
264: }
265:
266: //Cloneable//
267: public Object clone() {
268: final Radiogroup clone = (Radiogroup) super .clone();
269: fixClone(clone);
270: return clone;
271: }
272:
273: private static void fixClone(Radiogroup clone) {
274: if (clone._name.startsWith("_pg"))
275: clone._name = clone.genGroupName();
276:
277: rmListenerDown(clone, clone._listener);
278: //remove listener from children first
279:
280: //create and add back listener
281: clone.init();
282: clone.afterUnmarshal();
283: }
284:
285: private static void rmListenerDown(Component comp,
286: EventListener listener) {
287: for (Iterator it = comp.getChildren().iterator(); it.hasNext();) {
288: final Component child = (Component) it.next();
289: if (child instanceof Radio) {
290: ((Radio) child).removeEventListener(Events.ON_CHECK,
291: listener);
292: } else if (!(child instanceof Radiogroup)) { //skip nested radiogroup
293: rmListenerDown(child, listener);
294: }
295: }
296: }
297:
298: private void afterUnmarshal() {
299: addListenerDown(this , _listener);
300: }
301:
302: private static void addListenerDown(Component comp,
303: EventListener listener) {
304: for (Iterator it = comp.getChildren().iterator(); it.hasNext();) {
305: final Component child = (Component) it.next();
306: if (child instanceof Radio) {
307: ((Radio) child).addEventListener(Events.ON_CHECK,
308: listener);
309: } else if (!(child instanceof Radiogroup)) { //skip nested radiogroup
310: addListenerDown(child, listener);
311: }
312: }
313: }
314:
315: //Serializable//
316: private synchronized void readObject(java.io.ObjectInputStream s)
317: throws java.io.IOException, ClassNotFoundException {
318: s.defaultReadObject();
319:
320: init();
321: afterUnmarshal();
322: //Issue:
323: //if we re-generate the group name, client will mismatch with
324: //the server when the component is deserialized by Web Container.
325: //If we don't, the group name might be conflict when developer
326: //deserializes explicitly and add back the same desktop
327: }
328:
329: private class Listener implements EventListener, Deferrable {
330: public void onEvent(Event event) {
331: Events.sendEvent(Radiogroup.this , event);
332: }
333:
334: public boolean isDeferrable() {
335: return !Events.isListened(Radiogroup.this , Events.ON_CHECK,
336: true);
337: }
338: }
339: }
|