001: /* Treechildren.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Wed Jul 6 18:55:45 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: import java.util.Collection;
023: import java.util.AbstractCollection;
024: import java.util.Set;
025: import java.util.HashSet;
026:
027: import org.zkoss.lang.Objects;
028:
029: import org.zkoss.zk.ui.Executions;
030: import org.zkoss.zk.ui.Component;
031: import org.zkoss.zk.ui.UiException;
032: import org.zkoss.zk.ui.WrongValueException;
033: import org.zkoss.zk.ui.ext.render.Cropper;
034:
035: import org.zkoss.zul.impl.XulElement;
036: import org.zkoss.zul.ext.Pageable;
037:
038: /**
039: * A treechildren.
040: *
041: * @author tomyeh
042: */
043: public class Treechildren extends XulElement implements Pageable {
044: private static final String ATTR_NO_CHILD = "org.zkoss.zul.Treechildren.noChild";
045:
046: /** the active page. */
047: private int _actpg;
048: /** # of items per page. Zero means the same as Tree's. */
049: private int _pgsz;
050:
051: /** Returns the {@link Tree} instance containing this element.
052: */
053: public Tree getTree() {
054: for (Component p = this ; (p = p.getParent()) != null;)
055: if (p instanceof Tree)
056: return (Tree) p;
057: return null;
058: }
059:
060: /** Returns the {@link Treerow} that is associated with
061: * this treechildren, or null if no such treerow.
062: * In other words, it is {@link Treeitem#getTreerow} of
063: * {@link #getParent}.
064: *
065: * @since 2.4.1
066: * @see Treerow#getLinkedTreechildren
067: */
068: public Treerow getLinkedTreerow() {
069: final Component parent = getParent();
070: return parent instanceof Treeitem ? ((Treeitem) parent)
071: .getTreerow() : null;
072: }
073:
074: /** Returns whether this is visible.
075: * <p>Besides depends on {@link #setVisible}, it also depends on
076: * whether all its ancestors is open.
077: */
078: public boolean isVisible() {
079: if (!super .isVisible())
080: return false;
081:
082: Component comp = getParent();
083: if (!(comp instanceof Treeitem))
084: return true;
085: if (!((Treeitem) comp).isOpen())
086: return false;
087: comp = comp.getParent();
088: return !(comp instanceof Treechildren)
089: || ((Treechildren) comp).isVisible(); //recursive
090: }
091:
092: /** Returns a readonly list of all descending {@link Treeitem}
093: * (children's children and so on).
094: *
095: * <p>Note: the performance of the size method of returned collection
096: * is no good.
097: */
098: public Collection getItems() {
099: return new AbstractCollection() {
100: public int size() {
101: return getItemCount();
102: }
103:
104: public boolean isEmpty() {
105: return getChildren().isEmpty();
106: }
107:
108: public Iterator iterator() {
109: return new Iterator() {
110: private final Iterator _it = getChildren()
111: .iterator();
112: private Iterator _sub;
113:
114: public boolean hasNext() {
115: return (_sub != null && _sub.hasNext())
116: || _it.hasNext();
117: }
118:
119: public Object next() {
120: if (_sub != null && _sub.hasNext())
121: return _sub.next();
122:
123: final Treeitem item = (Treeitem) _it.next();
124: final Treechildren tc = item.getTreechildren();
125: _sub = tc != null ? tc.getItems().iterator()
126: : null;
127: return item;
128: }
129:
130: public void remove() {
131: throw new UnsupportedOperationException(
132: "readonly");
133: }
134: };
135: }
136: };
137: }
138:
139: /** Returns the number of child {@link Treeitem}
140: * including all descendants. The same as {@link #getItems}.size().
141: * <p>Note: the performance is no good.
142: */
143: public int getItemCount() {
144: int sz = 0;
145: for (Iterator it = getChildren().iterator(); it.hasNext(); ++sz) {
146: final Treeitem item = (Treeitem) it.next();
147: final Treechildren tchs = item.getTreechildren();
148: if (tchs != null)
149: sz += tchs.getItemCount();
150: }
151: return sz;
152: }
153:
154: /** Returns the page size which controls the number of
155: * visible child {@link Treeitem}, or -1 if no limitation.
156: *
157: * <p>If {@link #setPageSize} is called with a non-zero value,
158: * this method return the non-zero value.
159: * If {@link #setPageSize} is called with zero, this method
160: * returns {@link Tree#getPageSize} of {@link #getTree}.
161: *
162: * <p>Default: the same as {@link #getTree}'s {@link Tree#getPageSize}.
163: *
164: * @since 2.4.1
165: * @see Tree#getPageSize
166: * @see #setPageSize
167: */
168: public int getPageSize() {
169: if (_pgsz != 0)
170: return _pgsz;
171: final Tree tree = getTree();
172: return tree != null ? tree.getPageSize() : -1;
173: }
174:
175: /*package*/int getPageSizeDirectly() {
176: return _pgsz;
177: }
178:
179: /** Sets the page size which controls the number of
180: * visible child {@link Treeitem}.
181: *
182: * @param size the page size. If zero, the page size will be
183: * the same as {@link Tree#getPageSize} of {@link #getTree}.
184: * In other words, zero means the default page size is used.
185: * If negative, all {@link Treeitem} are shown.
186: * @since 2.4.1
187: */
188: public void setPageSize(int size) throws WrongValueException {
189: if (size < 0)
190: size = -1;
191: //Note: -1=no limitation, 0=tree's default
192: if (_pgsz != size) {
193: boolean realChanged = true;
194: if (_pgsz == 0 || size == 0) {
195: final Tree tree = getTree();
196: if (tree != null) {
197: final int treepgsz = tree.getPageSize();
198: if ((_pgsz == 0 && treepgsz == size)
199: || (size == 0 && treepgsz == _pgsz))
200: realChanged = false;
201: }
202: }
203:
204: _pgsz = size;
205:
206: if (realChanged) {
207: final int pgcnt = getPageCount();
208: if (_actpg >= pgcnt)
209: _actpg = pgcnt - 1;
210:
211: invalidate(); //due to client's limit, we have to redraw
212: smartUpdatePaging();
213: //it affect treerow (so invalidate won't 'eat' it)
214: }
215: }
216: }
217:
218: /** Returns the number of pages (at least one).
219: * Note: there is at least one page even no item at all.
220: *
221: * @since 2.4.1
222: */
223: public int getPageCount() {
224: final int cnt = getChildren().size();
225: if (cnt <= 0)
226: return 1;
227: final int pgsz = getPageSize();
228: return pgsz <= 0 ? 1 : 1 + (cnt - 1) / pgsz;
229: }
230:
231: /** Returns the active page (starting from 0).
232: *
233: * @since 2.4.1
234: */
235: public int getActivePage() {
236: return _actpg;
237: }
238:
239: /** Sets the active page (starting from 0).
240: *
241: * @exception WrongValueException if no such page
242: * @since 2.4.1
243: */
244: public void setActivePage(int pg) throws WrongValueException {
245: final int pgcnt = getPageCount();
246: if (pg >= pgcnt || pg < 0)
247: throw new WrongValueException(
248: "Unable to set active page to " + pg
249: + " since only " + pgcnt + " pages");
250: if (_actpg != pg) {
251: _actpg = pg;
252:
253: invalidate();
254: smartUpdatePaging();
255: //it affect treerow (so invalidate won't 'eat' it)
256: }
257: }
258:
259: /** Called by {@link Tree} to set the active page directly. */
260: /*package*/void setActivePageDirectly(int pg) {
261: _actpg = pg;
262: }
263:
264: /** Returns the index of the first visible child.
265: * <p>Used only for component development, not for application developers.
266: * @since 2.4.1
267: */
268: public int getVisibleBegin() {
269: final int pgsz = getPageSize();
270: return pgsz <= 0 ? 0 : getActivePage() * pgsz;
271: }
272:
273: /** Returns the index of the last visible child.
274: * <p>Used only for component development, not for application developers.
275: * @since 2.4.1
276: */
277: public int getVisibleEnd() {
278: final int pgsz = getPageSize();
279: return pgsz <= 0 ? Integer.MAX_VALUE : (getActivePage() + 1)
280: * getPageSize() - 1; //inclusive
281: }
282:
283: //-- Component --//
284: public void setParent(Component parent) {
285: final Component oldp = getParent();
286: if (oldp == parent)
287: return; //nothing changed
288:
289: if (parent != null && !(parent instanceof Tree)
290: && !(parent instanceof Treeitem))
291: throw new UiException("Wrong parent: " + parent);
292:
293: final Tree oldtree = oldp != null ? getTree() : null;
294:
295: super .setParent(parent);
296:
297: //maintain the selected status
298: if (oldtree != null)
299: oldtree.onTreechildrenRemoved(this );
300: if (parent != null) {
301: final Tree tree = getTree();
302: if (tree != null)
303: tree.onTreechildrenAdded(this );
304: }
305: }
306:
307: public boolean insertBefore(Component child, Component insertBefore) {
308: if (!(child instanceof Treeitem))
309: throw new UiException(
310: "Unsupported child for treechildren: " + child);
311:
312: if (super .insertBefore(child, insertBefore)) {
313: final int sz = getChildren().size();
314: if (sz == 1) { //the first child been added
315: Executions.getCurrent().setAttribute(ATTR_NO_CHILD,
316: Boolean.TRUE);
317: //Denote this execution has no children at beginning
318: } else { //second child
319: final int pgsz = getPageSize();
320: if (pgsz > 0 && ((sz % pgsz) == 1 || pgsz == 1)) //one more page
321: smartUpdatePaging();
322: }
323: return true;
324: }
325: return false;
326: }
327:
328: public void onChildRemoved(Component child) {
329: super .onChildRemoved(child);
330:
331: final int pgsz = getPageSize();
332: if (pgsz > 0) {
333: final int sz = getChildren().size();
334: if (sz > 0 && ((sz % pgsz) == 0 || pgsz == 1)) { //one page less
335: final int pgcnt = smartUpdatePaging();
336: if (_actpg >= pgcnt) { //removing the last page
337: _actpg = pgcnt - 1;
338: getParent().invalidate();
339: //We have to invalidate the parent, since
340: //no client at the item when user removes the last one
341: //Server: generate rm and outer in this case
342: }
343: } else if (getParent() instanceof Tree) {
344: smartUpdatePaging(); //Bug 1877059
345: }
346: }
347: }
348:
349: public void invalidate() {
350: final Component parent = getParent();
351: if (parent instanceof Tree) {
352: //Browser Limitation (IE/FF): we cannot update TBODY only
353: parent.invalidate();
354: } else if (!getChildren().isEmpty()
355: && Executions.getCurrent().getAttribute(ATTR_NO_CHILD) == null) {
356: //Don't invalidate if no child at all, since there is no
357: //counter-part at the client
358: super .invalidate();
359: }
360: }
361:
362: public void smartUpdate(String name, String value) {
363: Component comp = getParent();
364: if (comp instanceof Treeitem)
365: comp = ((Treeitem) comp).getTreerow();
366: if (comp != null)
367: comp.smartUpdate(name, value);
368: }
369:
370: /** Updates paging related information.
371: * @return # of pages
372: */
373: private int smartUpdatePaging() {
374: //We update all attributes at once, because
375: //1) if pgsz <= 1, none of them are generated (to save HTML size)
376: final int pgcnt = getPageCount();
377: smartUpdate("z.pgInfo", pgcnt + "," + getActivePage() + ","
378: + getPageSize());
379: return pgcnt;
380: }
381:
382: //-- ComponentCtrl --//
383: protected Object newExtraCtrl() {
384: return new ExtraCtrl();
385: }
386:
387: /** A utility class to implement {@link #getExtraCtrl}.
388: * It is used only by component developers.
389: */
390: protected class ExtraCtrl extends XulElement.ExtraCtrl implements
391: Cropper {
392: //--Cropper--//
393: public boolean isCropper() {
394: return getPageSize() > 0;
395: }
396:
397: public Set getAvailableAtClient() {
398: int pgsz = getPageSize(), sz = getChildren().size();
399: if (pgsz <= 0 || sz <= pgsz)
400: return null;
401:
402: final Set avail = new HashSet(37);
403: final int ofs = getActivePage() * pgsz;
404: for (final Iterator it = getChildren().listIterator(ofs); --pgsz >= 0
405: && it.hasNext();)
406: avail.add(it.next());
407: return avail;
408: }
409: }
410: }
|