/* AbstractGroupsModel.java
Purpose:
Description:
History:
Tue Sep 2 08:45:01 2008, Created by tomyeh
Copyright (C) 2008 Potix Corporation. All Rights Reserved.
{{IS_RIGHT
This program is distributed under LGPL Version 2.1 in the hope that
it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.zul;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.zkoss.io.Serializables;
import org.zkoss.zul.event.GroupsDataEvent;
import org.zkoss.zul.event.GroupsDataListener;
import org.zkoss.zul.ext.GroupsSelectableModel;
import org.zkoss.zul.ext.Selectable;
import org.zkoss.zul.ext.SelectionControl;
/**
* A skeletal implementation for {@link GroupsModel}.
* <p> Implements {@link Selectable} interface to handle the selection status.
* (Since 6.0.0)
* <p>Generics:
* <dl>
* <dt>D</dt><dd>The class of each data</dd>
* <dt>H</dt><dd>The class of each group header</dd>
* <dt>F</dt><dd>The class of each group footer</dd>
* <dt>E</dt><dd>The class of each selection. It is the common base class
* of D, H, F. In other words, D, H and F must extend from E.</dd>
* </dl>
* @author tomyeh
* @since 3.5.0
* @see Selectable
*/
public abstract class AbstractGroupsModel<D, H, F, E>
implements GroupsModel<D, H, F>, GroupsSelectableModel<E>, java.io.Serializable {
private transient List<GroupsDataListener> _listeners = new LinkedList<GroupsDataListener>();
/** The current selection. */
protected transient Set<E> _selection;
private boolean _multiple;
private SelectionControl<E> _ctrl;
private boolean _groupSelectable;
protected AbstractGroupsModel() {
_selection = newEmptySelection();
_ctrl = new DefaultSelectionControl(this);
}
/** Fires a {@link GroupsDataEvent} for all registered listener
* (thru {@link #addGroupsDataListener}.
*
* <p>Note: you can invoke this method only in an event listener.
*/
protected void fireEvent(int type, int groupIndex, int index0, int index1) {
final GroupsDataEvent evt = new GroupsDataEvent(this, type, groupIndex, index0, index1);
for (GroupsDataListener l : _listeners)
l.onChange(evt);
}
public void setSelectionControl(SelectionControl ctrl) {
_ctrl = ctrl;
}
public SelectionControl getSelectionControl() {
return _ctrl;
}
//-- GroupsModel --//
public void addGroupsDataListener(GroupsDataListener l) {
if (l == null)
throw new NullPointerException();
_listeners.add(l);
}
public void removeGroupsDataListener(GroupsDataListener l) {
_listeners.remove(l);
}
//Selectable//
/** {@inheritDoc} */
public Set<E> getSelection() {
return Collections.unmodifiableSet(_selection);
}
/** {@inheritDoc} */
public void setSelection(Collection<? extends E> selection) {
if (!_selection.equals(selection)) {
if (!_multiple && _selection.size() > 1)
throw new IllegalArgumentException("Only one selection is allowed, not " + selection);
_selection.clear();
_selection.addAll(selection);
if (selection.isEmpty()) {
fireSelectionEvent(null);
} else
fireSelectionEvent(selection.iterator().next());
}
}
/** {@inheritDoc} */
public boolean isSelected(Object obj) {
return _selection.contains(obj);
}
/** {@inheritDoc} */
public boolean isSelectionEmpty() {
return _selection.isEmpty();
}
/** {@inheritDoc} */
public boolean addToSelection(E obj) {
if (_selection.add(obj)) {
if (!_multiple) {
_selection.clear();
_selection.add(obj);
}
fireSelectionEvent(obj);
return true;
}
return false;
}
/** {@inheritDoc} */
public boolean removeFromSelection(Object obj) {
if (_selection.remove(obj)) {
fireEvent(GroupsDataEvent.SELECTION_CHANGED, -1, -1, -1);
return true;
}
return false;
}
/** {@inheritDoc} */
public void clearSelection() {
if (!_selection.isEmpty()) {
_selection.clear();
fireEvent(GroupsDataEvent.SELECTION_CHANGED, -1, -1, -1);
}
}
/**
* Selectable's implementor use only.
* <p> Fires a selection event for component to scroll into view. The override
* subclass must put the index0 of {@link #fireEvent(int, int, int, int)} as
* the view index to scroll. By default, the value -1 is assumed which means
* no scroll into view.
* <p> The method is invoked when both methods are invoked. {@link #addToSelection(Object)}
* and {@link #setSelection(Collection)}.
* @param e selected object.
*/
protected void fireSelectionEvent(E e) {
fireEvent(GroupsDataEvent.SELECTION_CHANGED, -1, -1, -1);
}
/**Removes the selection of the given collection.
*/
protected void removeAllSelection(Collection<?> c) {
_selection.removeAll(c);
}
/**Removes the selection that doesn't belong to the given collection.
*/
protected void retainAllSelection(Collection<?> c) {
_selection.retainAll(c);
}
/** {@inheritDoc} */
public boolean isMultiple() {
return _multiple;
}
/** {@inheritDoc} */
public void setMultiple(boolean multiple) {
if (_multiple != multiple) {
_multiple = multiple;
fireEvent(GroupsDataEvent.MULTIPLE_CHANGED, -1, -1, -1);
if (!multiple && _selection.size() > 1) {
E v = _selection.iterator().next();
_selection.clear();
_selection.add(v);
fireEvent(GroupsDataEvent.SELECTION_CHANGED, -1, -1, -1);
}
}
}
public boolean isGroupSelectable() {
return _groupSelectable;
}
public void setGroupSelectable(boolean groupSelectable) {
_groupSelectable = groupSelectable;
}
/** Instantiation an empty set of the section.
* It is used to initialize {@link #_selection}.
* <p>By default, it instantiates an instance of LinkedHashMap.
* The deriving class might override to instantiate a different class.
*/
protected Set<E> newEmptySelection() {
return new LinkedHashSet<E>();
}
/** Writes {@link #_selection}.
* <p>Default: write it directly. Override it if E is not serializable.
*/
protected void writeSelection(java.io.ObjectOutputStream s) throws java.io.IOException {
s.writeObject(_selection);
}
/** Reads back {@link #_selection}.
* <p>Default: write it directly. Override it if E is not serializable.
*/
@SuppressWarnings("unchecked")
protected void readSelection(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
_selection = (Set<E>) s.readObject();
}
/**
* A default selection control implementation for {@link AbstractGroupsModel},
* by default it assumes all elements are selectable.
* <p>Note: the implementation is not used for a huge data model, if in this case,
* please implement your own one to speed up.</p>
* @since 8.0.0
*/
public static class DefaultSelectionControl<E> implements SelectionControl<E> {
private AbstractGroupsModel model;
public DefaultSelectionControl(AbstractGroupsModel model) {
this.model = model;
}
public boolean isSelectable(E e) {
return true;
}
public void setSelectAll(boolean selectAll) {
if (selectAll) {
boolean isGroupSelectable = model.isGroupSelectable();
List all = new LinkedList();
for (int i = 0, j = model.getGroupCount(); i < j; i++) {
if (isGroupSelectable) {
Object group = model.getGroup(i);
if (isSelectable((E) group)) {
all.add(group);
}
for (int childIndex = 0, childSize = model
.getChildCount(i); childIndex < childSize; childIndex++) {
Object child = model.getChild(i, childIndex);
if (isSelectable((E) child)) {
all.add(child);
}
}
if (model.hasGroupfoot(i)) {
group = model.getGroupfoot(i);
if (isSelectable((E) group)) {
all.add(group);
}
}
} else {
for (int childIndex = 0, childSize = model
.getChildCount(i); childIndex < childSize; childIndex++) {
Object child = model.getChild(i, childIndex);
if (isSelectable((E) child)) {
all.add(child);
}
}
}
}
// avoid scroll into view at client side.
model.fireEvent(GroupsDataEvent.DISABLE_CLIENT_UPDATE, -1, -1, -1);
if (model instanceof AbstractGroupsModel)
try {
((Selectable) model).setSelection(all);
} finally {
model.fireEvent(GroupsDataEvent.ENABLE_CLIENT_UPDATE, -1, -1, -1);
}
} else {
((Selectable) model).clearSelection();
}
}
public boolean isSelectAll() {
Selectable smodel = (Selectable) model;
boolean isGroupSelectable = model.isGroupSelectable();
for (int i = 0, j = model.getGroupCount(); i < j; i++) {
if (isGroupSelectable) {
Object group = model.getGroup(i);
if (isSelectable((E) group) && !smodel.isSelected(group)) {
return false;
}
for (int childIndex = 0, childSize = model.getChildCount(i); childIndex < childSize; childIndex++) {
Object child = model.getChild(i, childIndex);
if (isSelectable((E) child) && !smodel.isSelected(child)) {
return false;
}
}
if (model.hasGroupfoot(i)) {
group = model.getGroupfoot(i);
if (isSelectable((E) group) && !smodel.isSelected(group)) {
return false;
}
}
} else {
for (int childIndex = 0, childSize = model.getChildCount(i); childIndex < childSize; childIndex++) {
Object child = model.getChild(i, childIndex);
if (isSelectable((E) child) && !smodel.isSelected(child)) {
return false;
}
}
}
}
return true;
}
}
//Serializable//
private synchronized void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
s.defaultWriteObject();
writeSelection(s);
Serializables.smartWrite(s, _listeners);
}
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
readSelection(s);
_listeners = new LinkedList<GroupsDataListener>();
Serializables.smartRead(s, _listeners);
}
@SuppressWarnings("unchecked")
public Object clone() {
final AbstractGroupsModel clone;
try {
clone = (AbstractGroupsModel) super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
clone._listeners = new LinkedList<GroupsDataListener>();
clone._selection = clone.newEmptySelection();
clone._selection.addAll(_selection);
return clone;
}
}