001: /*
002: *
003: * Copyright (c) 2007, Sun Microsystems, Inc.
004: *
005: * All rights reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * * Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: * * Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in the
015: * documentation and/or other materials provided with the distribution.
016: * * Neither the name of Sun Microsystems nor the names of its contributors
017: * may be used to endorse or promote products derived from this software
018: * without specific prior written permission.
019: *
020: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
023: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
024: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031: */
032: package com.sun.svg.component;
033:
034: import java.util.Vector;
035: import org.w3c.dom.Document;
036: import org.w3c.dom.svg.SVGAnimationElement;
037: import org.w3c.dom.svg.SVGElement;
038:
039: /**
040: * The <code>SVGList</code> class displays the items of an arbitrary long list
041: * of data items in a limited number of visual elements. The list triggers
042: * a scrolling effect to move up or down in the list and has a notion of
043: * selected item (i.e., the one which reprents the current user selection).
044: *
045: * The <code>SVGList</code> class facilitates creating a list where the
046: * appearance of the list and the list animations are defined in SVG markup and
047: * the data displayed by the list is controlled by the <code>ListModel</code>
048: * implementation associated to the <code>SVGList</code>.
049: *
050: * The conventions for the SVG this class can hook into are:
051: *
052: * - The various elements which compose the list share the same identifier
053: * prefix which is given to the SVGList at construction time (e.g., 'myList').
054: * This prefix is call 'listIdPrefix' in the following.
055: *
056: * - The animations which produce the list's scroll up effect are started by
057: * the animation with id <listIdPrefix + "_scrollUp_anim">. The scroll down
058: * effect animations are started with the animation with id:
059: * <listIdPrefix + "_scrollDown_anim">.
060: *
061: * - The SVG elements used to display the list items have an id of the form:
062: * <listIdPrefix + "_item_" + n>. The display items must be in consecutive
063: * order starting at zero.
064: *
065: * - The item which displays the currently selected item should have the
066: * additional "_selectedItem" suffix, so its id should be formed as:
067: * <listIdPrefix + "_item_" + n + "_selectedItem">. If not selected item is
068: * specified, the selected item index defaults to zero.
069: *
070: * By default, <code>SVGList</code> assumes that the list items are SVG
071: * <text> elements. However, the list can be associated to a list item binders
072: * to handle more sophisticated item rendering.
073: *
074: * Example of SVG content which can be bound by this class:
075: *
076: * <pre>
077: * <g id="listA">
078: * <g>
079: * <text id="listA_item_0" x="20" y="-60">item 0</text>
080: * <text id="listA_item_1" x="20" y="40">item 1</text>
081: * <text id="listA_item_2" x="20" y="70">item 2</text>
082: * <text id="listA_item_3" x="20" y="100">item 3</text>
083: * </g>
084: *
085: * <g id="listA_item_4_selectedItem">
086: * <text x="40" y="130">item 4</text>
087: * <text x="40" y="150">item 4 details</text>
088: * </g>
089: *
090: * <g>
091: * <text id="listA_item_5" x="20" y="180">item 5</text>
092: * <text id="listA_item_6" x="20" y="210">item 6</text>
093: * <text id="listA_item_7" x="20" y="240">item 7</text>
094: * <text id="listA_item_8" x="20" y="270">item 8</text>
095: * <text id="listA_item_9" x="20" y="340">item 8</text>
096: * </g>
097: *
098: * <animate id="listA_scrollDown_anim" ... />
099: * <animate id="listA_scrollUp_anim" ... />
100: * </g>
101: * </pre>
102: */
103: public class SVGList {
104: /**
105: * Interface that list data sources must implement.
106: */
107: public interface ListModel {
108: /**
109: * @param index - the requested index.
110: * @return the data at the given index.
111: */
112: public Object getElementAt(int index);
113:
114: /**
115: * Returns the list's length.
116: * @return the length of the list.
117: */
118: public int getSize();
119: }
120:
121: /**
122: * Interface that item binders must implement. An item binder is responsible
123: * for transfering a list element value (see ListModel) to the SVG
124: * element displaying the list.
125: */
126: public interface ListItemBinder {
127: /**
128: * @param itemValue - the item value to transfer to the SVG element.
129: * @param itemElement - the item element where the item value is
130: * displayed.
131: */
132: public void bindItem(Object itemValue, SVGElement itemElement);
133: }
134:
135: /**
136: * Default ListItemBinder. Assumes that the SVG element displaying the
137: * list is a <text> element.
138: */
139: public class DefaultListItemBinder implements ListItemBinder {
140: /**
141: * @param itemValue - the item value to transfer to the SVG element.
142: * @param itemElement - the item element where the item value is
143: * displayed.
144: */
145: public void bindItem(Object itemValue, SVGElement itemElement) {
146: itemElement.setTrait("#text", itemValue.toString());
147: }
148:
149: }
150:
151: /**
152: * Id suffix used for the list scrollUp animation effect.
153: */
154: public static final String SCROLL_UP_ANIM_SUFFIX = "_scrollUp_anim";
155:
156: /**
157: * Id suffix used for the list scrollDown animation effect.
158: */
159: public static final String SCROLL_DOWN_ANIM_SUFFIX = "_scrollDown_anim";
160:
161: /**
162: * The animation to play to scroll up the list.
163: */
164: protected SVGAnimationElement scrollUpAnim;
165:
166: /**
167: * The animation to play to scroll down the list.
168: */
169: protected SVGAnimationElement scrollDownAnim;
170:
171: /**
172: * The number of items displayed in the list.
173: */
174: protected int nDisplayedItems;
175:
176: /**
177: * The model providing the list data.
178: */
179: protected ListModel listModel;
180:
181: /**
182: * Index of the currently selected list item.
183: */
184: protected int curIndex = 0;
185:
186: /**
187: * Index of the list item which corresponds to the selected item.
188: */
189: private int selectedItem = 0;
190:
191: /**
192: * This vector holds all the <text> SVGElement instances which represent
193: * items in the list.
194: */
195: protected Vector listItems = new Vector();
196:
197: /**
198: * The prefix for the identifiers which make the various list elements.
199: */
200: protected String listIdPrefix;
201:
202: /**
203: * The list item binder used to display common list elements.
204: */
205: protected ListItemBinder commonItemBinder;
206:
207: /**
208: * The list item binder used to display the selected list element.
209: */
210: protected ListItemBinder selectedItemBinder;
211: /**
212: *
213: */
214: protected SVGElement selectedItemDetails;
215:
216: /**
217: * The minimal number of items used to display list entries.
218: */
219: private static final int MIN_N_DISPLAYED_ITEMS = 1;
220:
221: /**
222: * Creates a new instance of SVGList.
223: * @param listModel - the <code>ListModel</code> which will provide the
224: * data for the list.
225: * @param listIdPrefix - the prefix used for the various elements which make the
226: * list.
227: */
228: public SVGList(final ListModel listModel, final String listIdPrefix) {
229: if (listModel == null) {
230: throw new NullPointerException();
231: }
232:
233: if (listIdPrefix == null || "".equals(listIdPrefix)) {
234: throw new IllegalArgumentException(
235: "List identifiers prefix cannot"
236: + "be null or empty string.");
237: }
238:
239: this .listModel = listModel;
240: this .listIdPrefix = listIdPrefix;
241: this .commonItemBinder = new DefaultListItemBinder();
242: this .selectedItemBinder = new DefaultListItemBinder();
243: }
244:
245: /**
246: * Sets a new <code>ListItemBinder</code> for rendering common list
247: * entries.
248: *
249: * @param commonItemBinder - the new ListItemBinder to use when
250: * rendering common list elements.
251: */
252: public void setCommonItemBinder(
253: final ListItemBinder commonItemBinder) {
254: if (commonItemBinder == null) {
255: throw new NullPointerException();
256: }
257:
258: this .commonItemBinder = commonItemBinder;
259: }
260:
261: /**
262: * Sets a new <code>ListItemBinder</code> for rendering the selected list
263: * entry.
264: *
265: * @param selectedItemBinder - the new ListItemBinder to use when
266: * rendering the selected list element.
267: */
268: public void setSelectedItemBinder(
269: final ListItemBinder selectedItemBinder) {
270: if (selectedItemBinder == null) {
271: throw new NullPointerException();
272: }
273:
274: this .selectedItemBinder = selectedItemBinder;
275: }
276:
277: /**
278: * Hooks the specified sking to the application.
279: *
280: * @param doc the new Document to hook into the application
281: */
282: public void hookSkin(final Document doc) {
283: scrollUpAnim = (SVGAnimationElement) doc
284: .getElementById(listIdPrefix + SCROLL_UP_ANIM_SUFFIX);
285: scrollDownAnim = (SVGAnimationElement) doc
286: .getElementById(listIdPrefix + SCROLL_DOWN_ANIM_SUFFIX);
287:
288: if (scrollUpAnim == null) {
289: throw new IllegalArgumentException(
290: "SVGList convention error: no animation with id '"
291: + (listIdPrefix + SCROLL_UP_ANIM_SUFFIX)
292: + "'");
293: }
294:
295: if (scrollDownAnim == null) {
296: throw new IllegalArgumentException(
297: "SVGList convention error: no animation with id '"
298: + (listIdPrefix + SCROLL_DOWN_ANIM_SUFFIX)
299: + "'");
300: }
301:
302: // Initialize the item elements which should be filled in with the data item values
303: boolean keepSearching = true;
304: nDisplayedItems = 0;
305: listItems.removeAllElements();
306: boolean hasFocusedItem = false;
307:
308: while (keepSearching) {
309: SVGElement item = (SVGElement) doc
310: .getElementById(listIdPrefix + "_item_"
311: + nDisplayedItems);
312: if (item != null) {
313: listItems.addElement(item);
314: nDisplayedItems++;
315: } else {
316: // Check if the item is the currently selected one:
317: item = (SVGElement) doc.getElementById(listIdPrefix
318: + "_item_" + nDisplayedItems + "_selectedItem");
319: if (item != null && !hasFocusedItem) {
320: listItems.addElement(item);
321: selectedItem = nDisplayedItems;
322: nDisplayedItems++;
323: } else {
324: keepSearching = false;
325: }
326: }
327: }
328:
329: // If we did not find any list items, then the list conventions are broken.
330: if (nDisplayedItems < MIN_N_DISPLAYED_ITEMS) {
331: throw new IllegalArgumentException(
332: "SVGList convention error: there are only "
333: + nDisplayedItems
334: + " elements with id "
335: + (listIdPrefix + "_item_<n>"
336: + " and SVGList requires at least " + MIN_N_DISPLAYED_ITEMS));
337: }
338:
339: // Now, apply the initial set of data items.
340: setDataItems();
341: }
342:
343: /**
344: * Returns the current index in the list, in the [0, getSize()[ range.
345: *
346: * @return the first shown list index.
347: */
348: public int getCurIndex() {
349: return curIndex;
350: }
351:
352: /**
353: * Sets the new current index, i.e., the index of the first displayed
354: * list item.
355: *
356: * @param curIndex - the index of the new first displayed list item.
357: */
358: public void setCurIndex(final int curIndex) {
359: if (curIndex < 0 || curIndex >= listModel.getSize()) {
360: throw new IllegalArgumentException();
361: }
362:
363: this .curIndex = curIndex;
364: }
365:
366: /**
367: * Returns the index of the index in the list with focus.
368: *
369: * @return the index of the currently selected list index.
370: */
371: public int getFocusedIndex() {
372: return (curIndex + selectedItem) % listModel.getSize();
373: }
374:
375: /**
376: * Sets the index of the item with selection focus.
377: *
378: * @param selectedIndex - the index of the new currently focused list item.
379: */
380: public void setSelectedIndex(final int selectedIndex) {
381: if (selectedIndex < 0 || selectedIndex >= listModel.getSize()) {
382: throw new IllegalArgumentException();
383: }
384:
385: int ci = selectedIndex - selectedItem;
386:
387: if (ci < 0) {
388: ci += listModel.getSize();
389: } else if (ci >= listModel.getSize()) {
390: ci -= listModel.getSize();
391: }
392:
393: setCurIndex(ci);
394: }
395:
396: /**
397: * Returns the current progress in the list, as a ration of the current
398: * index over the number of list items.
399: */
400: public float getPosition() {
401: return 1 + (curIndex - listModel.getSize() + selectedItem + 1)
402: / (float) (listModel.getSize() - 1);
403: }
404:
405: /**
406: * Transfers list data to the XML UI.
407: */
408: public void setDataItems() {
409: for (int i = 0; i < nDisplayedItems; i++) {
410: SVGElement uiItem = (SVGElement) listItems.elementAt(i);
411: int listIndex = 0;
412: if ((curIndex + i) >= listModel.getSize()) {
413: listIndex = curIndex + i - listModel.getSize();
414: } else if (curIndex + i < 0) {
415: listIndex = curIndex + i + listModel.getSize();
416: } else {
417: listIndex = curIndex + i;
418: }
419:
420: ListItemBinder itemBinder = commonItemBinder;
421: if (i == selectedItem) {
422: itemBinder = selectedItemBinder;
423: }
424:
425: itemBinder.bindItem(listModel.getElementAt(listIndex),
426: uiItem);
427: }
428: }
429:
430: public void next() {
431: curIndex = (curIndex + 1) % listModel.getSize();
432: setDataItems();
433: }
434:
435: public void scrollDown() {
436: next();
437: scrollDownAnim.beginElementAt(0);
438: }
439:
440: public void prev() {
441: curIndex = (curIndex + listModel.getSize() - 1)
442: % listModel.getSize();
443: setDataItems();
444: }
445:
446: public void scrollUp() {
447: prev();
448: scrollUpAnim.beginElementAt(0);
449: }
450: }
|