001: /* Listbox.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Jun 14, 2007 5:03:14 PM, Created by henrichen
010: }}IS_NOTE
011:
012: Copyright (C) 2007 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:
020: package org.zkoss.mil;
021:
022: import java.util.AbstractSequentialList;
023: import java.util.Collections;
024: import java.util.LinkedHashSet;
025: import java.util.Iterator;
026: import java.util.List;
027: import java.util.ListIterator;
028: import java.util.NoSuchElementException;
029: import java.util.Set;
030:
031: import org.zkoss.lang.Objects;
032: import org.zkoss.xml.HTMLs;
033: import org.zkoss.zk.ui.Component;
034: import org.zkoss.zk.ui.UiException;
035: import org.zkoss.zk.ui.ext.client.Selectable;
036:
037: /**
038: * The Listbox item component that can be layout ouder Frame.
039: *
040: * @author henrichen
041: */
042: public class Listbox extends Item {
043: private static final long serialVersionUID = 200706151102L;
044:
045: private static final int EXCLUSIVE = 1;
046: private static final int MULTIPLE = 2;
047: private static final int POPUP = 4;
048:
049: private String _wrap; //item's wrapping policy
050: private boolean _multiple; //whether select multiple item
051: private int _jsel = -1; //index of the selected item
052: private transient Command _command;
053: private transient List _items;
054: private transient Set _selItems;
055: private transient Set _roSelItems; //read only selected items
056: /** disable smartUpdate; usually caused by the client. */
057: private boolean _noSmartUpdate;
058: private int _commandSize; //how many commands children
059:
060: public Listbox() {
061: init();
062: }
063:
064: private void init() {
065: _items = new AbstractSequentialList() {
066: public ListIterator listIterator(int index) {
067: return new ItemIter(index);
068: }
069:
070: public Object get(int j) {
071: final Object o = Listbox.this .getChildren().get(j);
072: if (!(o instanceof Listitem))
073: throw new IndexOutOfBoundsException("Wrong index: "
074: + j);
075: return o;
076: }
077:
078: public int size() {
079: int sz = getChildren().size();
080: return sz - _commandSize;
081: }
082: };
083: _selItems = new LinkedHashSet(5);
084: _roSelItems = Collections.unmodifiableSet(_selItems);
085: }
086:
087: /** whether allow select multiple line */
088: public boolean isMultiple() {
089: return _multiple;
090: }
091:
092: public void setMultiple(boolean b) {
093: if (b != _multiple) {
094: _multiple = b;
095:
096: if (!"select".equals(getMold())) {
097: smartUpdate("tp", getChoiceType());
098: }
099: }
100: }
101:
102: /**
103: * Get the wrap policy ("on", "off", "default").
104: * @return current wrap policy.
105: */
106: public String getWrap() {
107: return _wrap;
108: }
109:
110: /**
111: * set the Listitem text wrapping policy.
112: * @param wrap can be "on", "off", or "default". default is mobile device implementation decided.
113: */
114: public void setWrap(String wrap) {
115: if (wrap != null && wrap.length() == 0)
116: wrap = null;
117:
118: if (!Objects.equals(_wrap, wrap)) {
119: _wrap = wrap;
120: smartUpdate("fp", getFitPolicy());
121: }
122: }
123:
124: /** Returns a live list of all {@link Listitem}.
125: * By live we mean you can add or remove them directly with
126: * the List interface. In other words, you could add or remove
127: * an item by manipulating the returned list directly.
128: */
129: public List getItems() {
130: return _items;
131: }
132:
133: /** Returns the number of items.
134: */
135: public int getItemCount() {
136: return getItems().size();
137: }
138:
139: /** Returns the item at the specified index.
140: */
141: public Listitem getItemAtIndex(int index) {
142: return (Listitem) getItems().get(index);
143: }
144:
145: /** Returns the index of the specified item, or -1 if not found.
146: */
147: public int getIndexOfItem(Listitem item) {
148: return item == null ? -1 : getItems().indexOf(item);
149: }
150:
151: /** Returns the index of the selected item (-1 if no one is selected).
152: */
153: public int getSelectedIndex() {
154: return _jsel;
155: }
156:
157: /** Deselects all of the currently selected items and selects
158: * the item with the given index.
159: */
160: public void setSelectedIndex(int jsel) {
161: final int sz = getItems().size();
162: if (jsel >= sz)
163: throw new UiException("Out of bound: " + jsel
164: + " while size=" + sz);
165: if (jsel < -1)
166: jsel = -1;
167: if (jsel < 0) { //unselct all
168: clearSelection();
169: } else if (jsel != _jsel || (_multiple && _selItems.size() > 1)) {
170: for (Iterator it = _selItems.iterator(); it.hasNext();) {
171: final Listitem item = (Listitem) it.next();
172: item.setSelectedDirectly(false);
173: }
174: _selItems.clear();
175:
176: _jsel = jsel;
177: final Listitem item = getItemAtIndex(_jsel);
178: item.setSelectedDirectly(true);
179: _selItems.add(item);
180: smartUpdate("selectedIndex", Integer.toString(_jsel));
181: }
182: }
183:
184: /** Deselects all of the currently selected items and selects
185: * the given item.
186: * <p>It is the same as {@link #setSelectedItem}.
187: * @param item the item to select. If null, all items are deselected.
188: */
189: public void selectItem(Listitem item) {
190: if (item == null) {
191: setSelectedIndex(-1);
192: } else {
193: if (item.getParent() != this )
194: throw new UiException("Not a child: " + item);
195: if (_multiple || !item.isSelected())
196: setSelectedIndex(getIndexOfItem(item));
197: }
198: }
199:
200: /** Selects the given item, without deselecting any other items
201: * that are already selected..
202: */
203: public void addItemToSelection(Listitem item) {
204: if (item.getParent() != this )
205: throw new UiException("Not a child: " + item);
206:
207: if (!item.isSelected()) {
208: if (!_multiple) {
209: selectItem(item);
210: } else {
211: final int index = getIndexOfItem(item);
212: if (index < _jsel || _jsel < 0) {
213: _jsel = index;
214: }
215: item.setSelectedDirectly(true);
216: _selItems.add(item);
217: smartUpdateSelection();
218: }
219: }
220: }
221:
222: /** Deselects the given item without deselecting other items.
223: */
224: public void removeItemFromSelection(Listitem item) {
225: if (item.getParent() != this )
226: throw new UiException("Not a child: " + item);
227:
228: if (item.isSelected()) {
229: if (!_multiple) {
230: clearSelection();
231: } else {
232: item.setSelectedDirectly(false);
233: _selItems.remove(item);
234: fixSelectedIndex(0);
235: smartUpdateSelection();
236: }
237: }
238: }
239:
240: /** Note: we have to update all selection at once, since addItemToSelection
241: * and removeItemFromSelection might be called interchangeably.
242: */
243: private void smartUpdateSelection() {
244: final StringBuffer sb = new StringBuffer(80);
245: int sz = _selItems.size();
246:
247: if (sz > 0) {
248: int j = 0;
249: for (Iterator it = getItems().iterator(); it.hasNext()
250: && sz > 0; ++j) {
251: final Object item = it.next();
252: if (_selItems.contains(item)) {
253: if (sb.length() > 0)
254: sb.append(',');
255: sb.append(j);
256: --sz;
257: }
258: }
259: smartUpdate("chgSel", sb.toString());
260: }
261: }
262:
263: /** If the specified item is selected, it is deselected.
264: * If it is not selected, it is selected. Other items in the list box
265: * that are selected are not affected, and retain their selected state.
266: */
267: public void toggleItemSelection(Listitem item) {
268: if (item.isSelected())
269: removeItemFromSelection(item);
270: else
271: addItemToSelection(item);
272: }
273:
274: /** Clears the selection.
275: */
276: public void clearSelection() {
277: if (!_selItems.isEmpty()) {
278: for (Iterator it = _selItems.iterator(); it.hasNext();) {
279: final Listitem item = (Listitem) it.next();
280: item.setSelectedDirectly(false);
281: }
282: _selItems.clear();
283: _jsel = -1;
284: smartUpdate("selectedIndex", "-1");
285: }
286: }
287:
288: /** Selects all items.
289: */
290: public void selectAll() {
291: if (!_multiple)
292: throw new UiException(
293: "Appliable only to the multiple seltype: " + this );
294:
295: if (getChildren().size() != _selItems.size()) {
296: for (Iterator it = getChildren().iterator(); it.hasNext();) {
297: final Listitem item = (Listitem) it.next();
298: _selItems.add(item);
299: item.setSelectedDirectly(true);
300: }
301: _jsel = getChildren().isEmpty() ? -1 : 0;
302: smartUpdate("selectAll", "true");
303: }
304: }
305:
306: /** Returns the selected item.
307: */
308: public Listitem getSelectedItem() {
309: return _jsel >= 0 ? _jsel > 0 && _selItems.size() == 1 ? //optimize for performance
310: (Listitem) _selItems.iterator().next()
311: : getItemAtIndex(_jsel) : null;
312: }
313:
314: /** Deselects all of the currently selected items and selects
315: * the given item.
316: * <p>It is the same as {@link #selectItem}.
317: */
318: public void setSelectedItem(Listitem item) {
319: selectItem(item);
320: }
321:
322: /** Returns all selected items.
323: */
324: public Set getSelectedItems() {
325: return _roSelItems;
326: }
327:
328: /** Returns the number of items being selected.
329: */
330: public int getSelectedCount() {
331: return _selItems.size();
332: }
333:
334: /** Appends an item.
335: */
336: public Listitem appendItem(String label, String value) {
337: final Listitem item = new Listitem(label, value);
338: item.applyProperties();
339: item.setParent(this );
340: return item;
341: }
342:
343: /** Removes the child item in the list box at the given index.
344: *
345: * @return the removed item.
346: */
347: public Listitem removeItemAt(int index) {
348: final Listitem item = getItemAtIndex(index);
349: removeChild(item);
350: return item;
351: }
352:
353: //-- Component --//
354: public void smartUpdate(String attr, String value) {
355: if (!_noSmartUpdate)
356: super .smartUpdate(attr, value);
357: }
358:
359: public String getInnerAttrs() {
360: final StringBuffer sb = new StringBuffer(64).append(super
361: .getInnerAttrs());
362: HTMLs.appendAttribute(sb, "tp", getChoiceType());
363: HTMLs.appendAttribute(sb, "fp", getFitPolicy());
364: return sb.toString();
365: }
366:
367: public String getOuterAttrs() {
368: final StringBuffer sb = new StringBuffer(64).append(super
369: .getOuterAttrs());
370:
371: appendAsapAttr(sb, "onSelect");
372:
373: return sb.toString();
374: }
375:
376: public boolean insertBefore(Component newChild, Component refChild) {
377: if (newChild instanceof Listitem) {
378: //last: Commands
379: if (refChild != null && refChild.getParent() != this )
380: refChild = null; //Bug 1649625: it becomes the last child
381:
382: if (refChild == null) {
383: if (_command != null)
384: refChild = _command;
385: } else if (refChild instanceof Command) {
386: refChild = _command;
387: }
388:
389: final Listitem newItem = (Listitem) newChild;
390: final int jfrom = newItem.getParent() == this ? getIndexOfItem(newItem)
391: : -1;
392:
393: if (super .insertBefore(newChild, refChild)) {
394: final List children = getChildren();
395: int jto = refChild instanceof Listitem ? getIndexOfItem((Listitem) refChild)
396: : -1;
397:
398: //Maintain selected
399: final int newIndex = getIndexOfItem(newItem);
400: if (newItem.isSelected()) {
401: if (_jsel < 0) {
402: _jsel = newIndex;
403: smartUpdate("z.selId", getSelectedId());
404: _selItems.add(newItem);
405: } else if (_multiple) {
406: if (_jsel > newIndex) {
407: _jsel = newIndex;
408: smartUpdate("z.selId", getSelectedId());
409: }
410: _selItems.add(newItem);
411: } else { //deselect
412: newItem.setSelectedDirectly(false);
413: }
414: } else {
415: final int oldjsel = _jsel;
416: if (jfrom < 0) { //new child
417: if (_jsel >= newIndex)
418: ++_jsel;
419: } else if (_jsel >= 0) { //any selected
420: if (jfrom > _jsel) { //from below
421: if (jto >= 0 && jto <= _jsel)
422: ++_jsel;
423: } else { //from above
424: if (jto < 0 || jto > _jsel)
425: --_jsel;
426: }
427: }
428:
429: if (oldjsel != _jsel)
430: smartUpdate("z.selId", getSelectedId());
431: }
432: return true;
433: }
434: return false;
435: } else if (newChild instanceof Command) {
436: if (refChild instanceof Command) {
437: _command = (Command) newChild;
438: } else if (refChild instanceof Listitem) {
439: refChild = _command;
440: _command = (Command) newChild;
441: } else if (_command == null) { //refChild == null
442: _command = (Command) newChild;
443: }
444: ++_commandSize;
445: return super .insertBefore(newChild, refChild);
446: } else {
447: throw new UiException("Unsupported child for Listbox: "
448: + newChild);
449: }
450: }
451:
452: public boolean removeChild(Component child) {
453: final int index = child instanceof Listitem ? getIndexOfItem((Listitem) child)
454: : -1;
455:
456: if (!super .removeChild(child))
457: return false;
458:
459: if (child instanceof Command) {
460: --_commandSize;
461: if (_command == child) {
462: _command = _commandSize > 0 ? (Command) getChildren()
463: .get(getItems().size()) : null;
464: }
465: } else if (child instanceof Listitem) {
466: //maintain items
467: final Listitem item = (Listitem) child;
468:
469: //Maintain selected
470: if (item.isSelected()) {
471: _selItems.remove(item);
472: if (_jsel == index) {
473: fixSelectedIndex(index);
474: smartUpdate("z.selId", getSelectedId());
475: }
476: } else {
477: if (_jsel >= index) {
478: --_jsel;
479: smartUpdate("z.selId", getSelectedId());
480: }
481: }
482: return true;
483: }
484: invalidate();
485: return true;
486: }
487:
488: /** Returns the ID of the selected item (it is stored as the z.selId
489: * attribute of the listbox).
490: */
491: private String getSelectedId() {
492: final Listitem sel = getSelectedItem();
493: return sel != null ? sel.getUuid() : "zk_n_a";
494: }
495:
496: /** Fix the selected index, _jsel, assuming there are no selected one
497: * before (and excludes) j-the item.
498: */
499: private void fixSelectedIndex(int j) {
500: if (!_selItems.isEmpty()) {
501: for (Iterator it = getChildren().listIterator(j); it
502: .hasNext(); ++j) {
503: final Listitem item = (Listitem) it.next();
504: if (item.isSelected()) {
505: _jsel = j;
506: return;
507: }
508: }
509: }
510: _jsel = -1;
511: }
512:
513: private int getFitPolicy() {
514: if ("on".equals(_wrap) || "yes".equals(_wrap)) { //allow wrapping
515: return 1;
516: } else if ("off".equals(_wrap) || "no".equals(_wrap)) { //not allow wrapping
517: return 2;
518: }
519: return 0; //default to zero
520: }
521:
522: private int getChoiceType() {
523: if ("select".equals(getMold())) {
524: return POPUP;
525: } else {
526: return _multiple ? MULTIPLE : EXCLUSIVE;
527: }
528: }
529:
530: //-- ComponentCtrl --//
531: protected Object newExtraCtrl() {
532: return new ExtraCtrl();
533: }
534:
535: /** A utility class to implement {@link #getExtraCtrl}.
536: * It is used only by component developers.
537: */
538: protected class ExtraCtrl implements Selectable {
539: //-- Selectable --//
540: public void selectItemsByClient(Set selItems) {
541: _noSmartUpdate = true;
542: try {
543: if (!_multiple || selItems == null
544: || selItems.size() <= 1) {
545: final Listitem item = selItems != null
546: && selItems.size() > 0 ? (Listitem) selItems
547: .iterator().next()
548: : null;
549: selectItem(item);
550: } else {
551: int j = 0;
552: for (Iterator it = getChildren().iterator(); it
553: .hasNext(); ++j) {
554: final Listitem item = (Listitem) it.next();
555: if (selItems.contains(item)) {
556: addItemToSelection(item);
557: } else {
558: removeItemFromSelection(item);
559: }
560: }
561: }
562: } finally {
563: _noSmartUpdate = false;
564: }
565: }
566: }
567:
568: private class ItemIter implements ListIterator,
569: java.io.Serializable {
570: private ListIterator _it;
571: private int _j;
572: private boolean _bNxt;
573:
574: private ItemIter(int index) {
575: _j = index;
576: }
577:
578: public void add(Object o) {
579: prepare();
580: _it.add(o);
581: ++_j;
582: }
583:
584: public boolean hasNext() {
585: return _j < _items.size();
586: }
587:
588: public boolean hasPrevious() {
589: return _j > 0;
590: }
591:
592: public Object next() {
593: if (!hasNext())
594: throw new NoSuchElementException();
595:
596: prepare();
597: final Object o = _it.next();
598: ++_j;
599: _bNxt = true;
600: return o;
601: }
602:
603: public Object previous() {
604: if (!hasPrevious())
605: throw new NoSuchElementException();
606:
607: prepare();
608: final Object o = _it.previous();
609: --_j;
610: _bNxt = false;
611: return o;
612: }
613:
614: public int nextIndex() {
615: return _j;
616: }
617:
618: public int previousIndex() {
619: return _j - 1;
620: }
621:
622: public void remove() {
623: if (_it == null)
624: throw new IllegalStateException();
625: _it.remove();
626: if (_bNxt)
627: --_j;
628: }
629:
630: public void set(Object o) {
631: if (_it == null)
632: throw new IllegalStateException();
633: _it.set(o);
634: }
635:
636: private void prepare() {
637: if (_it == null) {
638: int v = _j;
639: _it = getChildren().listIterator(v);
640: }
641: }
642: }
643: }
|