/* Listbox.java
{{IS_NOTE
Purpose:
Description:
History:
Wed Jun 15 17:25:00 2005, Created by tomyeh
}}IS_NOTE
Copyright (C) 2005 Potix Corporation. All Rights Reserved.
{{IS_RIGHT
This program is distributed under GPL Version 2.0 in the hope that
it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.zul;
import java.util.AbstractCollection;
import java.util.AbstractList;
import java.util.AbstractSequentialList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.Set;
import org.zkoss.lang.Classes;
import org.zkoss.lang.D;
import org.zkoss.lang.Exceptions;
import org.zkoss.lang.Objects;
import org.zkoss.util.logging.Log;
import org.zkoss.xml.HTMLs;
import org.zkoss.zk.ui.AbstractComponent;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.ext.client.InnerWidth;
import org.zkoss.zk.ui.ext.client.RenderOnDemand;
import org.zkoss.zk.ui.ext.client.Selectable;
import org.zkoss.zk.ui.ext.render.ChildChangedAware;
import org.zkoss.zk.ui.ext.render.Cropper;
import org.zkoss.zul.event.ListDataEvent;
import org.zkoss.zul.event.ListDataListener;
import org.zkoss.zul.event.PagingEvent;
import org.zkoss.zul.event.ZulEvents;
import org.zkoss.zul.ext.Paginal;
import org.zkoss.zul.ext.Paginated;
import org.zkoss.zul.impl.XulElement;
/**
* A listbox.
*
* <p>Event:
* <ol>
* <li>org.zkoss.zk.ui.event.SelectEvent is sent when user changes
* the selection.</li>
* </ol>
*
* <p>See <a href="package-summary.html">Specification</a>.</p>
*
* <p>Besides creating {@link Listitem} programmingly, you could assign
* a data model (a {@link ListModel} or {@link GroupsModel} instance) to a listbox
* via {@link #setModel(ListModel)} or {@link #setModel(GroupsModel)} and then
* the listbox will retrieve data via {@link ListModel#getElementAt} when
* necessary.
*
* <p>Besides assign a list model, you could assign a renderer
* (a {@link ListitemRenderer} instance) to a listbox, such that
* the listbox will use this
* renderer to render the data returned by {@link ListModel#getElementAt}.
* If not assigned, the default renderer, which assumes a label per
* list item, is used.
* In other words, the default renderer adds a label to
* a row by calling toString against the object returned
* by {@link ListModel#getElementAt}
*
* <p>There are two ways to handle long content: scrolling and paging.
* If {@link #getMold} is "default", scrolling is used if {@link #setHeight}
* is called and too much content to display.
* If {@link #getMold} is "paging", paging is used if two or more pages are
* required. To control the number of items to display in a page, use
* {@link #setPageSize}.
*
* <p>If paging is used, the page controller is either created automatically
* or assigned explicity by {@link #setPaginal}.
* The paging controller specified explicitly by {@link #setPaginal} is called
* the external page controller. It is useful if you want to put the paging
* controller at different location (other than as a child component), or
* you want to use the same controller to control multiple listboxes.
*
* <p>Default {@link #getZclass}: z-listbox.(since 3.5.0)
*
* <p>To have a list box without stripping, you can specify a non-existent
* style class to {@link #setOddRowSclass}.
*
* @author tomyeh
* @see ListModel
* @see ListitemRenderer
* @see ListitemRendererExt
*/
public class Listbox extends XulElement implements Paginated, org.zkoss.zul.api.Listbox {
private static final Log log = Log.lookup(Listbox.class);
private static final String ATTR_ON_INIT_RENDER_POSTED =
"org.zkoss.zul.Listbox.onInitLaterPosted";
private transient List _items, _groupsInfo, _groups;
/** A list of selected items. */
private transient Set _selItems;
/** A readonly copy of {@link #_selItems}. */
private transient Set _roSelItems;
private int _maxlength;
private int _rows, _jsel = -1;
private transient Listhead _listhead;
private transient Listfoot _listfoot;
private ListModel _model;
private ListitemRenderer _renderer;
private transient ListDataListener _dataListener;
private transient Collection _heads;
private int _hdcnt;
private String _innerWidth = "100%";
/** ROD mold use only*/
private String _innerHeight = null,
_innerTop = "height:0px;display:none", _innerBottom = "height:0px;display:none";
private transient ListboxDrawerEngine _engine;
private String _pagingPosition = "bottom";
/** The name. */
private String _name;
/** The paging controller, used only if mold = "paging". */
private transient Paginal _pgi;
/** The paging controller, used only if mold = "paging" and user
* doesn't assign a controller via {@link #setPaginal}.
* If exists, it is the last child
*/
private transient Paging _paging;
private transient EventListener _pgListener, _pgImpListener;
/** The style class of the odd row. */
private String _scOddRow = null;
private int _tabindex = -1;
/** the # of rows to preload. */
private int _preloadsz = 7;
private boolean _multiple;
private boolean _disabled, _checkmark;
private boolean _vflex;
/** disable smartUpdate; usually caused by the client. */
private boolean _noSmartUpdate;
private boolean _fixedLayout;
/** maintain the number of the visible item in Paging mold. */
private int _visibleItemCount;
public Listbox() {
init();
}
private void init() {
_items = new AbstractSequentialList () {
public ListIterator listIterator(int index) {
return new ItemIter(index);
}
public Object get(int j) {
final Object o =
Listbox.this.getChildren().get(j + _hdcnt);
if (!(o instanceof Listitem))
throw new IndexOutOfBoundsException("Wrong index: "+j);
return o;
}
public int size() {
int sz = getChildren().size() - _hdcnt;
if (_listfoot != null) --sz;
if (_paging != null) --sz;
return sz;
}
/**
* override for Listgroup
* @since 3.5.1
*/
protected void removeRange(int fromIndex, int toIndex) {
ListIterator it = listIterator(toIndex);
for (int n = toIndex - fromIndex; --n >= 0;) {
it.previous();
it.remove();
}
}
};
_selItems = new LinkedHashSet(5);
_roSelItems = Collections.unmodifiableSet(_selItems);
_heads = new AbstractCollection() {
public int size() {
return _hdcnt;
}
public Iterator iterator() {
return new Iter();
}
};
_groupsInfo = new LinkedList();
_groups = new AbstractList() {
public int size() {
return getGroupCount();
}
public Iterator iterator() {
return new IterGroups();
}
public Object get(int index) {
return getItemAtIndex(((int[])_groupsInfo.get(index))[0]);
}
};
}
protected List newChildren() {
return new Children();
}
protected class Children extends AbstractComponent.Children {
protected void removeRange(int fromIndex, int toIndex) {
ListIterator it = listIterator(toIndex);
for (int n = toIndex - fromIndex; --n >= 0;) {
it.previous();
it.remove();
}
}
};
/** Initializes _dataListener and register the listener to the model
*/
private void initDataListener() {
if (_dataListener == null)
_dataListener = new ListDataListener() {
public void onChange(ListDataEvent event) {
onListDataChange(event);
}
};
_model.addListDataListener(_dataListener);
}
/**
* Sets the outline of listbox whether is fixed layout.
* If true, the outline of listbox will be depended on browser. It means, we don't
* calculate the width of each cell. Otherwise, the outl