1////////////////////////////////////////////////////////////////////////////////
2//
3//  ADOBE SYSTEMS INCORPORATED
4//  Copyright 2005-2007 Adobe Systems Incorporated
5//  All Rights Reserved.
6//
7//  NOTICE: Adobe permits you to use, modify, and distribute this file
8//  in accordance with the terms of the license agreement accompanying it.
9//
10////////////////////////////////////////////////////////////////////////////////
11
12package mx.collections
13{
14
15import flash.events.Event;
16import flash.events.EventDispatcher;
17import flash.utils.Proxy;
18import flash.utils.flash_proxy;
19import flash.utils.getQualifiedClassName;
20import mx.collections.errors.CollectionViewError;
21import mx.collections.errors.CursorError;
22import mx.collections.errors.ItemPendingError;
23import mx.collections.errors.SortError;
24import mx.core.IMXMLObject;
25import mx.core.mx_internal;
26import mx.events.CollectionEvent;
27import mx.events.CollectionEventKind;
28import mx.events.FlexEvent;
29import mx.events.PropertyChangeEvent;
30import mx.managers.ISystemManager;
31import mx.managers.SystemManager;
32import mx.resources.IResourceManager;
33import mx.resources.ResourceManager;
34import mx.utils.ObjectUtil;
35
36use namespace mx_internal;
37
38/**
39 *  Dispatched when the ICollectionView has been updated in some way.
40 *
41 *  @eventType mx.events.CollectionEvent.COLLECTION_CHANGE
42 *
43 *  @langversion 3.0
44 *  @playerversion Flash 9
45 *  @playerversion AIR 1.1
46 *  @productversion Flex 3
47 */
48[Event(name="collectionChange", type="mx.events.CollectionEvent")]
49
50[ResourceBundle("collections")]
51
52/**
53 * The ListCollectionView class adds the properties and methods of the
54 * <code>ICollectionView</code> interface to an object that conforms to the
55 * <code>IList</code> interface. As a result, you can pass an object of this class
56 * to anything that requires an <code>IList</code> or <code>ICollectionView</code>.
57 *
58 * <p>This class also lets you use [ ] array notation
59 * to access the <code>getItemAt()</code> and <code>setItemAt()</code> methods.
60 * If you use code such as <code>myListCollectionView[index]</code>
61 * Flex calls the <code>myListCollectionView</code> object's
62 * <code>getItemAt()</code> or <code>setItemAt()</code> method.</p>
63 *
64 * @mxml
65 *
66 *  <p>The <code>&lt;mx:ListCollectionView&gt;</code> has the following attributes,
67 *  which all of its subclasses inherit:</p>
68 *
69 *  <pre>
70 *  &lt;mx:ListCollectionView
71 *  <b>Properties</b>
72 *  filterFunction="null"
73 *  list="null"
74 *  sort="null"
75 *  <b>Events</b>
76 *  collectionChange="<i>No default</i>"
77 *  /&gt;
78 *  </pre>
79 *
80 *  @langversion 3.0
81 *  @playerversion Flash 9
82 *  @playerversion AIR 1.1
83 *  @productversion Flex 3
84 */
85public class ListCollectionView extends Proxy
86       implements ICollectionView, IList, IMXMLObject
87{
88    include "../core/Version.as";
89
90    //--------------------------------------------------------------------------
91    //
92    // Private variables
93    //
94    //--------------------------------------------------------------------------
95
96    /**
97     *  @private
98     *  Internal event dispatcher.
99     */
100    private var eventDispatcher:EventDispatcher;
101
102    /**
103     *  @private
104     *  Revisions are used for bookmark maintenace,
105     *  see getBookmark() and getBookmarkIndex() along with reset().
106     */
107    private var revision:int;
108
109    /**
110     *  @private
111     *  Used internally for managing disableAutoUpdate and enableAutoUpdate
112     *  calls.  disableAutoUpdate increments the counter, enable decrements.
113     *  When the counter reaches 0 handlePendingUpdates is called.
114     */
115    private var autoUpdateCounter:int;
116
117    /**
118     *  @private
119     *  Any update events that occured while autoUpdateCounter > 0
120     *  are stored here.
121     *  This may be null when there are no updates.
122     */
123    private var pendingUpdates:Array;
124
125    /**
126     *  @private
127     *  Flag that indicates whether a RESET type of collectionChange
128     *  event should be emitted when reset() is called.
129     */
130    mx_internal var dispatchResetEvent:Boolean = true;
131
132    /**
133     *  @private
134     *  Used for accessing localized Error messages.
135     */
136    private var resourceManager:IResourceManager =
137                                    ResourceManager.getInstance();
138
139    //--------------------------------------------------------------------------
140    //
141    // Protected variables
142    //
143    //--------------------------------------------------------------------------
144
145    /**
146     *  When the view is sorted or filtered the <code>localIndex</code> property
147     *  contains an array of items in the sorted or filtered (ordered, reduced)
148     *  view, in the sorted order.
149     *  The ListCollectionView class uses this property to access the items in
150     *  the view.
151     *  The <code>localIndex</code> property should never contain anything
152     *  that is not in the source, but may not have everything in the source.
153     *  This property is <code>null</code> when there is no sort.
154     *
155     *  @langversion 3.0
156     *  @playerversion Flash 9
157     *  @playerversion AIR 1.1
158     *  @productversion Flex 3
159     */
160    protected var localIndex:Array;
161
162    //--------------------------------------------------------------------------
163    //
164    // Constructor
165    //
166    //--------------------------------------------------------------------------
167
168    /**
169     *  The ListCollectionView constructor.
170     *
171     *  @param list the IList this ListCollectionView is meant to wrap.
172     *
173     *  @langversion 3.0
174     *  @playerversion Flash 9
175     *  @playerversion AIR 1.1
176     *  @productversion Flex 3
177     */
178    public function ListCollectionView(list:IList = null)
179    {
180        super();
181
182        eventDispatcher = new EventDispatcher(this);
183        this.list = list;
184    }
185
186     /**
187      *  Called automatically by the MXML compiler when the ListCollectionView
188      *  is created using an MXML tag.
189      *  If you create the ListCollectionView through ActionScript, you
190      *  must call this method passing in the MXML document and
191      *  <code>null</code> for the <code>id</code>.
192      *
193      *  @param document The MXML document containing this ListCollectionView.
194      *
195      *  @param id Ignored.
196      *
197      *  @langversion 3.0
198      *  @playerversion Flash 9
199      *  @playerversion AIR 1.1
200      *  @productversion Flex 3
201      */
202    public function initialized(document:Object, id:String):void
203    {
204        refresh();
205    }
206
207    //--------------------------------------------------------------------------
208    //
209    // Properties
210    //
211    //--------------------------------------------------------------------------
212
213    //----------------------------------
214    // length
215    //----------------------------------
216
217    [Bindable("collectionChange")]
218
219    /**
220     *  @inheritDoc
221     *
222     *  @langversion 3.0
223     *  @playerversion Flash 9
224     *  @playerversion AIR 1.1
225     *  @productversion Flex 3
226     */
227    public function get length():int
228    {
229        if (localIndex)
230        {
231            return localIndex.length;
232        }
233        else if (list)
234        {
235            return list.length;
236        }
237        else
238        {
239            return 0;
240        }
241    }
242
243    //----------------------------------
244    //  list
245    //----------------------------------
246
247    /**
248     *  @private
249     *  Storage for the list property.
250     */
251    private var _list:IList;
252
253    [Inspectable(category="General")]
254    [Bindable("listChanged")]
255
256    /**
257     *  The IList that this collection view wraps.
258     *
259     *  @langversion 3.0
260     *  @playerversion Flash 9
261     *  @playerversion AIR 1.1
262     *  @productversion Flex 3
263     */
264    public function get list():IList
265    {
266        return _list;
267    }
268
269    /**
270     *  @private
271     */
272    public function set list(value:IList):void
273    {
274        if (_list != value)
275        {
276            var oldHasItems:Boolean;
277            var newHasItems:Boolean;
278            if (_list)
279            {
280                _list.removeEventListener(CollectionEvent.COLLECTION_CHANGE,
281                                          listChangeHandler);
282                oldHasItems = _list.length > 0;
283            }
284
285            _list = value;
286
287            if (_list)
288            {
289                // weak listeners to collections and dataproviders
290                _list.addEventListener(CollectionEvent.COLLECTION_CHANGE,
291                                       listChangeHandler, false, 0, true);
292                newHasItems = _list.length > 0;
293            }
294
295            if (oldHasItems || newHasItems)
296                reset();
297            dispatchEvent(new Event("listChanged"));
298        }
299    }
300
301    //----------------------------------
302    //  filterFunction
303    //----------------------------------
304
305    /**
306     *  @private
307     *  Storage for the filterFunction property.
308     */
309    private var _filterFunction:Function;
310
311    [Bindable("filterFunctionChanged")]
312    [Inspectable(category="General")]
313
314    /**
315     *  @inheritDoc
316     *
317     *  @see #refresh()
318     *
319     *  @langversion 3.0
320     *  @playerversion Flash 9
321     *  @playerversion AIR 1.1
322     *  @productversion Flex 3
323     */
324    public function get filterFunction():Function
325    {
326        return _filterFunction;
327    }
328
329    /**
330     *  @private
331     */
332    public function set filterFunction(f:Function):void
333    {
334        _filterFunction = f;
335        dispatchEvent(new Event("filterFunctionChanged"));
336    }
337
338    //----------------------------------
339    //  sort
340    //----------------------------------
341
342    /**
343     *  @private
344     *  Storage for the sort property.
345     */
346    private var _sort:ISort;
347
348    [Bindable("sortChanged")]
349    [Inspectable(category="General")]
350
351    /**
352     *  @inheritDoc
353     *
354     *  @see #refresh()
355     *
356     *  @langversion 3.0
357     *  @playerversion Flash 9
358     *  @playerversion AIR 1.1
359     *  @productversion Flex 3
360     */
361    public function get sort():ISort
362    {
363        return _sort;
364    }
365
366    /**
367     *  @private
368     */
369    public function set sort(s:ISort):void
370    {
371        _sort = s;
372        dispatchEvent(new Event("sortChanged"));
373    }
374
375    //--------------------------------------------------------------------------
376    //
377    // ICollectionView Methods
378    //
379    //--------------------------------------------------------------------------
380
381    /**
382     *  @inheritDoc
383     *
384     *  @see #enableAutoUpdate()
385     *  @see mx.events.CollectionEvent
386     *
387     *  @langversion 3.0
388     *  @playerversion Flash 9
389     *  @playerversion AIR 1.1
390     *  @productversion Flex 3
391     */
392    public function contains(item:Object):Boolean
393    {
394        return getItemIndex(item) != -1;
395    }
396
397    /**
398     *  @inheritDoc
399     *
400     *  @see mx.collections.ICollectionView#enableAutoUpdate()
401     *  @see mx.events.CollectionEvent
402     *
403     *  @langversion 3.0
404     *  @playerversion Flash 9
405     *  @playerversion AIR 1.1
406     *  @productversion Flex 3
407     */
408    public function disableAutoUpdate():void
409    {
410        autoUpdateCounter++;
411    }
412
413    /**
414     *  @inheritDoc
415     *
416     *  @see mx.collections.ICollectionView#disableAutoUpdate()
417     *
418     *  @langversion 3.0
419     *  @playerversion Flash 9
420     *  @playerversion AIR 1.1
421     *  @productversion Flex 3
422     */
423    public function enableAutoUpdate():void
424    {
425        if (autoUpdateCounter > 0)
426        {
427            autoUpdateCounter--;
428            if (autoUpdateCounter == 0)
429            {
430                handlePendingUpdates();
431            }
432        }
433    }
434
435    /**
436     *  @inheritDoc
437     *
438     *  @langversion 3.0
439     *  @playerversion Flash 9
440     *  @playerversion AIR 1.1
441     *  @productversion Flex 3
442     */
443    public function createCursor():IViewCursor
444    {
445        return new ListCollectionViewCursor(this);
446    }
447
448    /**
449     *  @inheritDoc
450     *
451     *  @see mx.events.CollectionEvent
452     *  @see mx.core.IPropertyChangeNotifier
453     *  @see mx.events.PropertyChangeEvent
454     *
455     *  @langversion 3.0
456     *  @playerversion Flash 9
457     *  @playerversion AIR 1.1
458     *  @productversion Flex 3
459     */
460    public function itemUpdated(item:Object, property:Object = null,
461                                oldValue:Object = null,
462                                newValue:Object = null):void
463    {
464        list.itemUpdated(item, property, oldValue, newValue);
465    }
466
467    /**
468     * @inheritDoc
469     *
470     *  @langversion 3.0
471     *  @playerversion Flash 9
472     *  @playerversion AIR 1.1
473     *  @productversion Flex 3
474     */
475    public function refresh():Boolean
476    {
477        return internalRefresh(true);
478    }
479
480    //--------------------------------------------------------------------------
481    //
482    // IList Methods
483    //
484    //--------------------------------------------------------------------------
485
486    [Bindable("collectionChange")]
487
488    /**
489     * @inheritDoc
490     *
491     *  @langversion 3.0
492     *  @playerversion Flash 9
493     *  @playerversion AIR 1.1
494     *  @productversion Flex 3
495     */
496    public function getItemAt(index:int, prefetch:int = 0):Object
497    {
498        if (index < 0 || index >= length)
499        {
500            var message:String = resourceManager.getString(
501                "collections", "outOfBounds", [ index ]);
502            throw new RangeError(message);
503        }
504
505        if (localIndex)
506        {
507            return localIndex[index];
508        }
509        else if (list)
510        {
511            return list.getItemAt(index, prefetch);
512        }
513
514        return null;
515    }
516
517    /**
518     * @inheritDoc
519     *
520     *  @langversion 3.0
521     *  @playerversion Flash 9
522     *  @playerversion AIR 1.1
523     *  @productversion Flex 3
524     */
525    public function setItemAt(item:Object, index:int):Object
526    {
527        if (index < 0 || !list || index >= length)
528         {
529            var message:String = resourceManager.getString(
530                "collections", "outOfBounds", [ index ]);
531            throw new RangeError(message);
532        }
533
534        var listIndex:int = index;
535        if (localIndex)
536        {
537            if (index > localIndex.length)
538            {
539                listIndex = list.length;
540            }
541            else
542            {
543                var oldItem:Object = localIndex[index];
544                listIndex = list.getItemIndex(oldItem);
545            }
546        }
547        return list.setItemAt(item, listIndex);
548    }
549
550    /**
551     * @inheritDoc
552     *
553     *  @langversion 3.0
554     *  @playerversion Flash 9
555     *  @playerversion AIR 1.1
556     *  @productversion Flex 3
557     */
558    public function addItem(item:Object):void
559    {
560        addItemAt(item, length);
561    }
562
563    /**
564     * @inheritDoc
565     *
566     *  @langversion 3.0
567     *  @playerversion Flash 9
568     *  @playerversion AIR 1.1
569     *  @productversion Flex 3
570     */
571    public function addItemAt(item:Object, index:int):void
572    {
573        if (index < 0 || !list || index > length)
574        {
575            var message:String = resourceManager.getString(
576                "collections", "outOfBounds", [ index ]);
577            throw new RangeError(message);
578        }
579
580        var listIndex:int = index;
581        //if we're sorted addItemAt is meaningless, just add to the end
582        if (localIndex && sort)
583        {
584            listIndex = list.length;
585        }
586        else if (localIndex && filterFunction != null)
587        {
588            // if end of filtered list, put at end of source list
589            if (listIndex == localIndex.length)
590                listIndex = list.length;
591            // if somewhere in filtered list, find it and insert before it
592            // or at beginning
593            else
594                listIndex = list.getItemIndex(localIndex[index]);
595        }
596        list.addItemAt(item, listIndex);
597    }
598
599    /**
600     *  Adds a list of items to the current list, placing them at the end of
601     *  the list in the order they are passed.
602     *
603     *  @param IList The list of items to add to the current list
604     *
605     *  @langversion 3.0
606     *  @playerversion Flash 9
607     *  @playerversion AIR 1.1
608     *  @productversion Flex 3
609     */
610    public function addAll(addList:IList):void
611    {
612        addAllAt(addList, length);
613    }
614
615    /**
616     *  Adds a list of items to the current list, placing them at the position
617     *  index passed in to the function.  The items are placed at the index location
618     *  and placed in the order they are recieved.
619     *
620     *  @param IList The list of items to add to the current list
621     *  @param index The location of the current list to place the new items.
622     *  @throws RangeError if index is less than 0 or greater than the length of the list.
623     *
624     *  @langversion 3.0
625     *  @playerversion Flash 9
626     *  @playerversion AIR 1.1
627     *  @productversion Flex 3
628     */
629    public function addAllAt(addList:IList, index:int):void
630    {
631        var length:int = addList.length;
632        for (var i:int=0; i < length; i++)
633        {
634            this.addItemAt(addList.getItemAt(i), i+index);
635        }
636    }
637
638    /**
639     * @inheritDoc
640     *
641     *  @langversion 3.0
642     *  @playerversion Flash 9
643     *  @playerversion AIR 1.1
644     *  @productversion Flex 3
645     */
646    public function getItemIndex(item:Object):int
647    {
648        var i:int;
649
650        if (localIndex && sort)
651        {
652            var startIndex:int = findItem(item, Sort.FIRST_INDEX_MODE);
653            if (startIndex == -1)
654                return -1;
655
656            var endIndex:int = findItem(item, Sort.LAST_INDEX_MODE);
657            for (i = startIndex; i <= endIndex; i++)
658            {
659                if (localIndex[i] == item)
660                    return i;
661            }
662
663            return -1;
664        }
665        else if (localIndex && filterFunction != null)
666        {
667            var len:int = localIndex.length;
668            for (i = 0; i < len; i++)
669            {
670                if (localIndex[i] == item)
671                    return i;
672            }
673
674            return -1;
675        }
676
677        // fallback
678        return list.getItemIndex(item);
679    }
680
681    /**
682     * @inheritDoc
683     */
684    mx_internal function getLocalItemIndex(item:Object):int
685    {
686        var i:int;
687
688        var len:int = localIndex.length;
689        for (i = 0; i < len; i++)
690        {
691            if (localIndex[i] == item)
692                return i;
693        }
694
695        return -1;
696    }
697
698    /**
699     * @private
700     */
701    private function getFilteredItemIndex(item:Object):int
702    {
703        //loc is wrong
704        //the intent of this function is to find where this new item
705        //should be in the filtered list, by looking at the main list
706        //for it's neighbor that is also in this filtered list
707        //and trying to insert item after that neighbor in the insert locao filtered list
708
709        //1st get the position in the original list
710        var loc:int = list.getItemIndex(item);
711
712        //if it's 0 then item must be also the first in the filtered list
713        if (loc == 0)
714            return 0;
715
716        // scan backwards for an item that also in the filtered list
717        for (var i:int = loc - 1; i >= 0; i--)
718        {
719            var prevItem:Object = list.getItemAt(i);
720            if (filterFunction(prevItem))
721            {
722                var len:int = localIndex.length;
723                // get the index of the item in the filtered set
724                //for (var j:int = 0; j < len; j++)
725                for (var j:int = 0; j < len; j++)
726                {
727                    if (localIndex[j] == prevItem)
728                        return j + 1;
729                }
730            }
731        }
732
733        //turns out that there are no neighbors of item in the filtered
734        //list, so item is the 1st item
735        return 0;
736    }
737
738
739    /**
740     * @inheritDoc
741     *
742     *  @langversion 3.0
743     *  @playerversion Flash 9
744     *  @playerversion AIR 1.1
745     *  @productversion Flex 3
746     */
747    public function removeItemAt(index:int):Object
748    {
749        if (index < 0 || index >= length)
750        {
751            var message:String = resourceManager.getString(
752                "collections", "outOfBounds", [ index ]);
753            throw new RangeError(message);
754        }
755
756        var listIndex:int = index;
757        if (localIndex)
758        {
759            var oldItem:Object = localIndex[index];
760            listIndex = list.getItemIndex(oldItem);
761        }
762        return list.removeItemAt(listIndex);
763    }
764
765    /**
766     * Remove all items from the list.
767     *
768     *  @langversion 3.0
769     *  @playerversion Flash 9
770     *  @playerversion AIR 1.1
771     *  @productversion Flex 3
772     */
773    public function removeAll():void
774    {
775        var len:int = length;
776        if (len > 0)
777        {
778            if (localIndex)
779            {
780                for (var i:int = len - 1; i >= 0; i--)
781                {
782                    removeItemAt(i);
783                }
784            }
785            else
786            {
787                list.removeAll();
788            }
789        }
790    }
791
792    /**
793     * @inheritDoc
794     *
795     *  @langversion 3.0
796     *  @playerversion Flash 9
797     *  @playerversion AIR 1.1
798     *  @productversion Flex 3
799     */
800    public function toArray():Array
801    {
802        var ret:Array;
803        if (localIndex)
804        {
805            ret = localIndex.concat();
806        }
807        else
808        {
809            ret = list.toArray();
810        }
811        return ret;
812    }
813
814    /**
815     *  Prints the contents of this view to a string and returns it.
816     *
817     *  @return The contents of this view, in string form.
818     *
819     *  @langversion 3.0
820     *  @playerversion Flash 9
821     *  @playerversion AIR 1.1
822     *  @productversion Flex 3
823     */
824    public function toString():String
825    {
826        if (localIndex)
827        {
828            return ObjectUtil.toString(localIndex);
829        }
830        else
831        {
832            if (list && Object(list).toString)
833                return Object(list).toString();
834            else
835                return getQualifiedClassName(this);
836        }
837    }
838
839    //--------------------------------------------------------------------------
840    //
841    // Proxy methods
842    //
843    //--------------------------------------------------------------------------
844
845    /**
846     *  @private
847     *  Attempts to call getItemAt(), converting the property name into an int.
848     */
849    override flash_proxy function getProperty(name:*):*
850    {
851        if (name is QName)
852            name = name.localName;
853
854        var index:int = -1;
855        try
856        {
857            // If caller passed in a number such as 5.5, it will be floored.
858            var n:Number = parseInt(String(name));
859            if (!isNaN(n))
860                index = int(n);
861        }
862        catch(e:Error) // localName was not a number
863        {
864        }
865
866        if (index == -1)
867        {
868            var message:String = resourceManager.getString(
869                "collections", "unknownProperty", [ name ]);
870            throw new Error(message);
871        }
872        else
873        {
874            return getItemAt(index);
875        }
876    }
877
878    /**
879     *  @private
880     *  Attempts to call setItemAt(), converting the property name into an int.
881     */
882    override flash_proxy function setProperty(name:*, value:*):void
883    {
884        if (name is QName)
885            name = name.localName;
886
887        var index:int = -1;
888        try
889        {
890            // If caller passed in a number such as 5.5, it will be floored.
891            var n:Number = parseInt(String(name));
892            if (!isNaN(n))
893                index = int(n);
894        }
895        catch(e:Error) // localName was not a number
896        {
897        }
898
899        if (index == -1)
900        {
901            var message:String = resourceManager.getString(
902                "collections", "unknownProperty", [ name ]);
903            throw new Error(message);
904        }
905        else
906        {
907            setItemAt(value, index);
908        }
909    }
910
911    /**
912     *  @private
913     *  This is an internal function.
914     *  The VM will call this method for code like <code>"foo" in bar</code>
915     *
916     *  @param name The property name that should be tested for existence.
917     */
918    override flash_proxy function hasProperty(name:*):Boolean
919    {
920        if (name is QName)
921            name = name.localName;
922
923        var index:int = -1;
924        try
925        {
926            // If caller passed in a number such as 5.5, it will be floored.
927            var n:Number = parseInt(String(name));
928            if (!isNaN(n))
929                index = int(n);
930        }
931        catch(e:Error) // localName was not a number
932        {
933        }
934
935        if (index == -1)
936            return false;
937
938        return index >= 0 && index < length;
939    }
940
941    /**
942     *  @private
943     */
944    override flash_proxy function nextNameIndex(index:int):int
945    {
946        return index < length ? index + 1 : 0;
947    }
948
949    /**
950     *  @private
951     */
952    override flash_proxy function nextName(index:int):String
953    {
954        return (index - 1).toString();
955    }
956
957    /**
958     *  @private
959     */
960    override flash_proxy function nextValue(index:int):*
961    {
962        return getItemAt(index - 1);
963    }
964
965    /**
966     *  @private
967     *  Any methods that can't be found on this class shouldn't be called,
968     *  so return null
969     */
970    override flash_proxy function callProperty(name:*, ... rest):*
971    {
972        return null;
973    }
974
975    //--------------------------------------------------------------------------
976    //
977    // EventDispatcher methods
978    //
979    //--------------------------------------------------------------------------
980
981    /**
982     *  @inheritDoc
983     *
984     *  @langversion 3.0
985     *  @playerversion Flash 9
986     *  @playerversion AIR 1.1
987     *  @productversion Flex 3
988     */
989    public function addEventListener(type:String,
990                                     listener:Function,
991                                     useCapture:Boolean = false,
992                                     priority:int = 0,
993                                     useWeakReference:Boolean = false):void
994    {
995        eventDispatcher.addEventListener(type, listener, useCapture,
996                                         priority, useWeakReference);
997    }
998
999    /**
1000     *  @inheritDoc
1001     *
1002     *  @langversion 3.0
1003     *  @playerversion Flash 9
1004     *  @playerversion AIR 1.1
1005     *  @productversion Flex 3
1006     */
1007    public function removeEventListener(type:String,
1008                                        listener:Function,
1009                                        useCapture:Boolean = false):void
1010    {
1011        eventDispatcher.removeEventListener(type, listener, useCapture);
1012    }
1013
1014    /**
1015     *  @inheritDoc
1016     *
1017     *  @langversion 3.0
1018     *  @playerversion Flash 9
1019     *  @playerversion AIR 1.1
1020     *  @productversion Flex 3
1021     */
1022    public function dispatchEvent(event:Event):Boolean
1023    {
1024        return eventDispatcher.dispatchEvent(event);
1025    }
1026
1027    /**
1028     *  @inheritDoc
1029     *
1030     *  @langversion 3.0
1031     *  @playerversion Flash 9
1032     *  @playerversion AIR 1.1
1033     *  @productversion Flex 3
1034     */
1035    public function hasEventListener(type:String):Boolean
1036    {
1037        return eventDispatcher.hasEventListener(type);
1038    }
1039
1040    /**
1041     *  @inheritDoc
1042     *
1043     *  @langversion 3.0
1044     *  @playerversion Flash 9
1045     *  @playerversion AIR 1.1
1046     *  @productversion Flex 3
1047     */
1048    public function willTrigger(type:String):Boolean
1049    {
1050        return eventDispatcher.willTrigger(type);
1051    }
1052
1053    //--------------------------------------------------------------------------
1054    //
1055    // Internal methods
1056    //
1057    //--------------------------------------------------------------------------
1058
1059    /**
1060     *  Take the item and insert it into the view.  If we don't have a sort
1061     *  use the sourceLocation.  Dispatch the CollectionEvent with kind ADD
1062     *  if dispatch is true.
1063     *
1064     *  @param items the items to add into the view
1065     *  @param sourceLocation the location within the list where the items were added
1066     *  @param extendedInfo Object reference to any additional event information
1067     *         that needs to be preserved.
1068     *  @param dispatch true if the view should dispatch a corresponding
1069     *                 CollectionEvent with kind ADD (default is true)
1070     *
1071     *  @langversion 3.0
1072     *  @playerversion Flash 9
1073     *  @playerversion AIR 1.1
1074     *  @productversion Flex 3
1075     */
1076    private function addItemsToView(items:Array, sourceLocation:int,
1077                                      dispatch:Boolean = true):int
1078    {
1079        var addedItems:Array = localIndex ? [] : items;
1080        var addLocation:int = sourceLocation;
1081        var firstOne:Boolean = true;
1082
1083        if (localIndex)
1084        {
1085            var loc:int = sourceLocation;
1086            for (var i:int = 0; i < items.length; i++)
1087            {
1088                var item:Object = items[i];
1089                if (filterFunction == null || filterFunction(item))
1090                {
1091                    if (sort)
1092                    {
1093                        loc = findItem(item, Sort.ANY_INDEX_MODE, true);
1094                        if (firstOne)
1095                        {
1096                            addLocation = loc;
1097                            firstOne = false;
1098                        }
1099                    }
1100                    else
1101                    {
1102                        loc = getFilteredItemIndex(item);
1103                        if (firstOne)
1104                        {
1105                            addLocation = loc;
1106                            firstOne = false;
1107                        }
1108                    }
1109
1110                    if (sort && sort.unique && sort.compareFunction(item, localIndex[loc]) == 0)
1111                    {
1112                        // We cause all adds to fail here, not just the one.
1113                        var message:String = resourceManager.getString(
1114                            "collections", "incorrectAddition");
1115                        throw new CollectionViewError(message);
1116                    }
1117                    localIndex.splice(loc++, 0, item);
1118                    addedItems.push(item);
1119                }
1120                else
1121                    addLocation = -1;
1122             }
1123        }
1124
1125        if (localIndex && addedItems.length > 1)
1126        {
1127            addLocation = -1;
1128        }
1129
1130        if (dispatch && addedItems.length > 0)
1131        {
1132            var event:CollectionEvent =
1133                new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
1134            event.kind = CollectionEventKind.ADD;
1135            event.location = addLocation;
1136            event.items = addedItems;
1137            dispatchEvent(event);
1138        }
1139
1140        return addLocation;
1141    }
1142
1143    /**
1144     *  Find the item specified using the Sort find mode constants.
1145     *  If there is no sort assigned throw an error.
1146     *
1147     *  @param values the values object that can be passed into Sort.findItem
1148     *  @param mode the mode to pass to Sort.findItem (see Sort)
1149     *  @param insertIndex true if it should find the insertion point
1150     *  @return the index where the item is located, -1 if not found or SortError
1151     *  caught
1152     *
1153     *  @langversion 3.0
1154     *  @playerversion Flash 9
1155     *  @playerversion AIR 1.1
1156     *  @productversion Flex 3
1157     */
1158    mx_internal function findItem(values:Object, mode:String, insertIndex:Boolean = false):int
1159    {
1160        if (!sort || !localIndex)
1161        {
1162            var message:String = resourceManager.getString(
1163                "collections", "itemNotFound");
1164            throw new CollectionViewError(message);
1165        }
1166
1167        if (localIndex.length == 0)
1168            return insertIndex ? 0 : -1;
1169
1170        try
1171        {
1172            return sort.findItem(localIndex, values, mode, insertIndex);
1173        }
1174        catch (e:SortError)
1175        {
1176            // usually because the find critieria is not compatible with the sort.
1177        }
1178
1179        return -1;
1180    }
1181
1182    /**
1183     *  Create a bookmark for this view.  This method is called by
1184     *  ListCollectionViewCursor.
1185     *
1186     *  @param index the index to bookmark
1187     *  @return a new bookmark instance
1188     *  @throws a CollectionViewError if the index is out of bounds
1189     *
1190     *  @langversion 3.0
1191     *  @playerversion Flash 9
1192     *  @playerversion AIR 1.1
1193     *  @productversion Flex 3
1194     */
1195    mx_internal function getBookmark(index:int):ListCollectionViewBookmark
1196    {
1197        if (index < 0 || index > length)
1198        {
1199            var message:String = resourceManager.getString(
1200                "collections", "invalidIndex", [ index ]);
1201            throw new CollectionViewError(message);
1202        }
1203
1204        var value:Object;
1205        try
1206        {
1207            value = getItemAt(index);
1208        }
1209        catch(e:Error)
1210        {
1211            // the cursor was over something that is not yet on the client
1212            value = null;
1213        }
1214        return new ListCollectionViewBookmark(value,
1215                                              this,
1216                                              revision,
1217                                              index);
1218
1219    }
1220
1221    /**
1222     *  Given a bookmark find the location for the value.  If the
1223     *  view has been modified since the bookmark was created attempt
1224     *  to relocate the item.  If the bookmark represents an item
1225     *  that is no longer in the view (removed or filtered out) return
1226     *  -1.
1227     *
1228     *  @param bookmark the bookmark to locate
1229     *  @return the new location of the bookmark, -1 if not in the view anymore
1230     *  @throws CollectionViewError if the bookmark is invalid
1231     *
1232     *  @langversion 3.0
1233     *  @playerversion Flash 9
1234     *  @playerversion AIR 1.1
1235     *  @productversion Flex 3
1236     */
1237    mx_internal function getBookmarkIndex(bookmark:CursorBookmark):int
1238    {
1239        if (!(bookmark is ListCollectionViewBookmark)
1240            || ListCollectionViewBookmark(bookmark).view != this)
1241        {
1242            var message:String = resourceManager.getString(
1243                "collections", "bookmarkNotFound");
1244            throw new CollectionViewError(message);
1245        }
1246
1247        var bm:ListCollectionViewBookmark = ListCollectionViewBookmark(bookmark);
1248
1249        if (bm.viewRevision != revision)
1250        {
1251            // getItemAt has a side-effect of throwing an exception if the index is out-
1252            // of-range, here we are checking to see if the index falls with-in the range
1253            // and only then calling getItemAt.
1254            if (bm.index < 0 || bm.index >= length || getItemAt(bm.index) != bm.value)
1255            {
1256                try
1257                {
1258                    bm.index = getItemIndex(bm.value);
1259                }
1260                catch (e:SortError)
1261                {
1262                    bm.index = getLocalItemIndex(bm.value);
1263                }
1264            }
1265
1266            bm.viewRevision = revision;
1267        }
1268        return bm.index;
1269    }
1270
1271    /**
1272     * The view is a listener of CollectionEvents on its underlying IList
1273     *
1274     *  @langversion 3.0
1275     *  @playerversion Flash 9
1276     *  @playerversion AIR 1.1
1277     *  @productversion Flex 3
1278     */
1279    private function listChangeHandler(event:CollectionEvent):void
1280    {
1281        if (autoUpdateCounter > 0)
1282        {
1283            if (!pendingUpdates)
1284            {
1285                pendingUpdates = [];
1286            }
1287            pendingUpdates.push(event);
1288        }
1289        else
1290        {
1291            switch (event.kind)
1292            {
1293                case CollectionEventKind.ADD:
1294                    addItemsToView(event.items, event.location);
1295                break;
1296
1297                case CollectionEventKind.MOVE:
1298                    var n:int = event.items.length;
1299                    for (var i:int = 0; i < n; i++)
1300                        moveItemInView(event.items[i]);
1301                break;
1302
1303                case CollectionEventKind.RESET:
1304                    reset();
1305                break;
1306
1307                case CollectionEventKind.REMOVE:
1308                    removeItemsFromView(event.items, event.location);
1309                break;
1310
1311                case CollectionEventKind.REPLACE:
1312                     replaceItemsInView(event.items, event.location);
1313                break;
1314
1315                case CollectionEventKind.UPDATE:
1316                     handlePropertyChangeEvents(event.items);
1317                break;
1318
1319                default:
1320                    dispatchEvent(event);
1321            } // switch
1322        }
1323    }
1324
1325    /**
1326     * Given a set of PropertyChangeEvents go through and update the view.
1327     * This is currently not optimized.
1328     *
1329     *  @langversion 3.0
1330     *  @playerversion Flash 9
1331     *  @playerversion AIR 1.1
1332     *  @productversion Flex 3
1333     */
1334    private function handlePropertyChangeEvents(events:Array):void
1335    {
1336        var eventItems:Array = events;
1337        if (sort || filterFunction != null)
1338        {
1339            //go through the events and find all the individual objects
1340            //that have been updated
1341            //then for each one determine whether we should move it or
1342            //just fire an update event
1343            var updatedItems:Array = [];
1344            var updateEntry:Object;
1345            var i:int;
1346            for (i = 0; i < events.length; i++)
1347            {
1348                var updateInfo:PropertyChangeEvent = events[i];
1349                var item:Object;
1350                var defaultMove:Boolean;
1351                if (updateInfo.target)
1352                {
1353                    item = updateInfo.target;
1354                    //if the target != source that means the update
1355                    //happened to some subprop of the item in the collection
1356                    //if we have a custom comparator this will affect
1357                    //the sort so for now say we should move but
1358                    //maybe we could optimize further
1359                    defaultMove = updateInfo.target != updateInfo.source;
1360                }
1361                else
1362                {
1363                    item = updateInfo.source;
1364                    defaultMove = false;
1365                }
1366
1367                //see if the item is already in the list
1368                var j:int = 0;
1369                for (; j < updatedItems.length; j++)
1370                {
1371					if (updatedItems[j].item == item)
1372					{
1373						// even if it is, if a different property changed, track that too.
1374						var events:Array = updatedItems[j].events;
1375						var l:int = events.length;
1376						for (var k:int = 0; k < l; k++)
1377						{
1378							if (events[k].property != updateInfo.property)
1379							{
1380								events.push(updateInfo);
1381								break;
1382							}
1383							// we could also merge events for changes to the same
1384							// property but that's hopefully low probability
1385							// and leads to questions of event order.
1386						}
1387                        break;
1388					}
1389                }
1390
1391                if (j < updatedItems.length)
1392                {
1393                    updateEntry = updatedItems[j];
1394                }
1395                else
1396                {
1397                    updateEntry = { item: item, move: defaultMove, events: [ updateInfo ] };
1398                    updatedItems.push(updateEntry);
1399                }
1400
1401                //if we've already set replace don't unset it
1402                //if there's a filterFunction need to go through replace
1403                //if there's no property specified for the sort we'll need
1404                //to assume we have to replace
1405                //if there is a property see if it affects the sort
1406                updateEntry.move =
1407                    updateEntry.move
1408                    || filterFunction
1409                    || !updateInfo.property
1410                    || (sort && sort.propertyAffectsSort(String(updateInfo.property)));
1411            }
1412
1413			// go through the items and move and send move events for ones that moved
1414			// and build the list of remaining items we need to send UPDATE events for
1415            eventItems = [];
1416            for (i = 0; i < updatedItems.length; i++)
1417            {
1418                updateEntry = updatedItems[i];
1419                if (updateEntry.move)
1420                {
1421                    moveItemInView(updateEntry.item, updateEntry.item, eventItems);
1422                }
1423                else
1424                {
1425                    eventItems.push(updateEntry.item);
1426                }
1427            }
1428
1429			// now go through the updated items and add all events for all
1430			// properties that changed in that item (except for those items
1431			// we moved
1432            var temp:Array = [];
1433            for (var ctr:int = 0; ctr < eventItems.length; ctr++)
1434                for (var ctr1:int = 0; ctr1 < updatedItems.length; ctr1++)
1435                    if (eventItems[ctr] == updatedItems[ctr1].item)
1436					{
1437                        temp = temp.concat(updatedItems[ctr1].events);
1438					}
1439            eventItems = temp;
1440        }
1441
1442        if (eventItems.length > 0)
1443        {
1444
1445            var updateEvent:CollectionEvent =
1446                new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
1447            updateEvent.kind = CollectionEventKind.UPDATE;
1448            updateEvent.items = eventItems;
1449            dispatchEvent(updateEvent);
1450        }
1451    }
1452
1453    /**
1454     * When enableAutoUpdates pushes autoUpdateCounter back down to 0
1455     * this method will execute to consolidate the pending update
1456     * events or turn it into a massive refresh().
1457     *
1458     *  @langversion 3.0
1459     *  @playerversion Flash 9
1460     *  @playerversion AIR 1.1
1461     *  @productversion Flex 3
1462     */
1463    private function handlePendingUpdates():void
1464    {
1465        if (pendingUpdates)
1466        {
1467            var pu:Array = pendingUpdates;
1468            pendingUpdates = null;
1469
1470            // Could further optimize to consolidate various events
1471            // and make a decision if there are too many updates
1472            // and we should just refresh.
1473            var singleUpdateEvent:CollectionEvent;
1474            for (var i:int = 0; i < pu.length; i++)
1475            {
1476                var event:CollectionEvent = pu[i];
1477                if (event.kind == CollectionEventKind.UPDATE)
1478                {
1479                    if (!singleUpdateEvent)
1480                    {
1481                        singleUpdateEvent = event;
1482                    }
1483                    else
1484                    {
1485                        for (var j:int = 0; j < event.items.length; j++)
1486                        {
1487                            singleUpdateEvent.items.push(event.items[j]);
1488                        }
1489                    }
1490                }
1491                else
1492                {
1493                    listChangeHandler(event);
1494                }
1495            }
1496
1497            if (singleUpdateEvent)
1498            {
1499                listChangeHandler(singleUpdateEvent);
1500            }
1501        }
1502    }
1503
1504    private function internalRefresh(dispatch:Boolean):Boolean
1505    {
1506        if (sort || filterFunction != null)
1507        {
1508            try
1509            {
1510                populateLocalIndex();
1511            }
1512            catch(pending:ItemPendingError)
1513            {
1514                pending.addResponder(new ItemResponder(
1515                    function(data:Object, token:Object = null):void
1516                    {
1517                        internalRefresh(dispatch);
1518                    },
1519                    function(info:Object, token:Object = null):void
1520                    {
1521                        //no-op
1522                    }));
1523                return false;
1524            }
1525
1526            if (filterFunction != null)
1527            {
1528                var tmp:Array = [];
1529                var len:int = localIndex.length;
1530                for (var i:int = 0; i < len; i++)
1531                {
1532                    var item:Object = localIndex[i];
1533                    if (filterFunction(item))
1534                    {
1535                        tmp.push(item);
1536                    }
1537                }
1538                localIndex = tmp;
1539            }
1540            if (sort)
1541            {
1542                sort.sort(localIndex);
1543                dispatch = true;
1544            }
1545        }
1546        else if (localIndex)
1547        {
1548            localIndex = null;
1549        }
1550
1551        revision++;
1552        pendingUpdates = null;
1553        if (dispatch)
1554        {
1555            var refreshEvent:CollectionEvent =
1556                new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
1557            refreshEvent.kind = CollectionEventKind.REFRESH;
1558            dispatchEvent(refreshEvent);
1559        }
1560        return true;
1561    }
1562
1563    /**
1564     * Remove the old value from the view and replace it with the value
1565     *
1566     *  @langversion 3.0
1567     *  @playerversion Flash 9
1568     *  @playerversion AIR 1.1
1569     *  @productversion Flex 3
1570     */
1571    private function moveItemInView(item:Object,
1572                                      dispatch:Boolean = true, updateEventItems:Array = null):void
1573    {
1574        if (localIndex)
1575        {
1576            //we're guaranteed that removeItemsFromView isn't going
1577            //to work here because the item has probably
1578            //already been updated so getItemIndex is going to fail
1579            //so we'll just do a linear search and find it if it's here
1580            var removeLocation:int = -1;
1581            for (var i:int = 0; i < localIndex.length; i++)
1582            {
1583                if (localIndex[i] == item)
1584                {
1585                    removeLocation = i;
1586                    break;
1587                }
1588            }
1589            if (removeLocation > -1)
1590            {
1591                localIndex.splice(removeLocation, 1);
1592            }
1593
1594            var addLocation:int = addItemsToView([item], removeLocation, false);
1595
1596            if (dispatch)
1597            {
1598                var event:CollectionEvent =
1599                    new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
1600                event.items.push(item);
1601                if (updateEventItems && addLocation == removeLocation && addLocation > -1)
1602                {
1603                    updateEventItems.push(item);
1604                    return;
1605                }
1606                if (addLocation > -1 && removeLocation > -1)
1607                {
1608                    event.kind = CollectionEventKind.MOVE;
1609                    event.location = addLocation;
1610                    event.oldLocation = removeLocation;
1611                }
1612                else if (addLocation > -1)
1613                {
1614                    event.kind = CollectionEventKind.ADD;
1615                    event.location = addLocation;
1616                }
1617                else if (removeLocation > -1)
1618                {
1619                    event.kind = CollectionEventKind.REMOVE;
1620                    event.location = removeLocation;
1621                }
1622                else
1623                {
1624                    dispatch = false;
1625                }
1626
1627                if (dispatch)
1628                {
1629                    dispatchEvent(event);
1630                }
1631            }
1632        }
1633    }
1634
1635    /**
1636     * Copy all of the data from the source list into the local index.
1637     *
1638     *  @langversion 3.0
1639     *  @playerversion Flash 9
1640     *  @playerversion AIR 1.1
1641     *  @productversion Flex 3
1642     */
1643    private function populateLocalIndex():void
1644    {
1645        if (list)
1646        {
1647            localIndex = list.toArray();
1648        }
1649        else
1650        {
1651            localIndex = [];
1652        }
1653    }
1654
1655    /**
1656     *  Take the item and remove it from the view.  If we don't have a sort
1657     *  use the sourceLocation.  Dispatch the CollectionEvent with kind REMOVE
1658     *  if dispatch is true.
1659     *
1660     *  @param items the items to remove from the view
1661     *  @param sourceLocation the location within the list where the item was removed
1662     *  @param dispatch true if the view should dispatch a corresponding
1663     *                 CollectionEvent with kind REMOVE (default is true)
1664     *
1665     *  @langversion 3.0
1666     *  @playerversion Flash 9
1667     *  @playerversion AIR 1.1
1668     *  @productversion Flex 3
1669     */
1670    private function removeItemsFromView(items:Array, sourceLocation:int, dispatch:Boolean = true):void
1671    {
1672        var removedItems:Array = localIndex ? [] : items;
1673        var removeLocation:int = sourceLocation;
1674        if (localIndex)
1675        {
1676            for (var i:int = 0; i < items.length; i++)
1677            {
1678                var item:Object = items[i];
1679                var loc:int = getItemIndex(item);
1680                if (loc > -1)
1681                {
1682                    localIndex.splice(loc, 1);
1683                    removedItems.push(item);
1684                    removeLocation = loc;
1685                }
1686            }
1687        }
1688        if (dispatch && removedItems.length > 0)
1689        {
1690            var event:CollectionEvent =
1691                new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
1692            event.kind = CollectionEventKind.REMOVE;
1693            event.location = (!localIndex || removedItems.length == 1)
1694                ? removeLocation
1695                : -1;
1696            event.items = removedItems;
1697            dispatchEvent(event);
1698        }
1699    }
1700
1701    /**
1702     * Items is an array of PropertyChangeEvents so replace the oldValues with the new
1703     * newValues.  Start at the location specified and move forward, it's unlikely
1704     * that the length of items is > 1.
1705     *
1706     *  @langversion 3.0
1707     *  @playerversion Flash 9
1708     *  @playerversion AIR 1.1
1709     *  @productversion Flex 3
1710     */
1711    private function replaceItemsInView(items:Array,
1712                                          location:int,
1713                                          dispatch:Boolean = true):void
1714    {
1715        if (localIndex)
1716        {
1717            var len:int = items.length;
1718            var oldItems:Array = [];
1719            var newItems:Array = [];
1720            for (var i:int = 0; i < len; i++)
1721            {
1722                var propertyEvent:PropertyChangeEvent = items[i];
1723                oldItems.push(propertyEvent.oldValue);
1724                newItems.push(propertyEvent.newValue);
1725            }
1726            removeItemsFromView(oldItems, location, dispatch);
1727            addItemsToView(newItems, location, dispatch);
1728        }
1729        else
1730        {
1731            var event:CollectionEvent =
1732                new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
1733            event.kind = CollectionEventKind.REPLACE;
1734            event.location = location;
1735            event.items = items;
1736            dispatchEvent(event);
1737        }
1738    }
1739
1740    /**
1741     *  @private
1742     *  When the source list is replaced, reset.
1743     */
1744    mx_internal function reset():void
1745    {
1746        internalRefresh(false);
1747        if (dispatchResetEvent)
1748        {
1749            var event:CollectionEvent =
1750                new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
1751            event.kind = CollectionEventKind.RESET;
1752            dispatchEvent(event);
1753        }
1754    }
1755
1756}
1757
1758}
1759
1760import flash.events.EventDispatcher;
1761import flash.events.Event;
1762import flash.events.IEventDispatcher;
1763
1764import mx.events.*;
1765import mx.collections.*;
1766import mx.collections.errors.*;
1767import mx.core.mx_internal;
1768import mx.managers.*;
1769import mx.resources.IResourceManager;
1770import mx.resources.ResourceManager;
1771
1772use namespace mx_internal;
1773
1774/**
1775 *  Dispatched whenever the cursor position is updated.
1776 *
1777 *  @eventType mx.events.FlexEvent.CURSOR_UPDATE
1778 *
1779 *  @langversion 3.0
1780 *  @playerversion Flash 9
1781 *  @playerversion AIR 1.1
1782 *  @productversion Flex 3
1783 */
1784[Event(name="cursorUpdate", type="mx.events.FlexEvent")]
1785
1786[ResourceBundle("collections")]
1787
1788/**
1789 *  @private
1790 *  The internal implementation of cursor for the ListCollectionView.
1791 */
1792class ListCollectionViewCursor extends EventDispatcher implements IViewCursor
1793{
1794    //--------------------------------------------------------------------------
1795    //
1796    //  Class constants
1797    //
1798    //--------------------------------------------------------------------------
1799
1800    /**
1801     *  @private
1802     */
1803    private static const BEFORE_FIRST_INDEX:int = -1;
1804
1805    /**
1806     *  @private
1807     */
1808    private static const AFTER_LAST_INDEX:int = -2;
1809
1810    //--------------------------------------------------------------------------
1811    //
1812    //  Constructor
1813    //
1814    //--------------------------------------------------------------------------
1815
1816    /**
1817     *  Constructor.
1818     *
1819     *  <p>Creates the cursor for the view.</p>
1820     *
1821     *  @langversion 3.0
1822     *  @playerversion Flash 9
1823     *  @playerversion AIR 1.1
1824     *  @productversion Flex 3
1825     */
1826    public function ListCollectionViewCursor(view:ListCollectionView)
1827    {
1828        super();
1829
1830        _view = view;
1831        _view.addEventListener(CollectionEvent.COLLECTION_CHANGE, collectionEventHandler, false, 0, true);
1832        currentIndex = view.length > 0 ? 0 : AFTER_LAST_INDEX;
1833        if (currentIndex == 0)
1834        {
1835            try
1836            {
1837                setCurrent(view.getItemAt(0), false);
1838            }
1839            catch(e:ItemPendingError)
1840            {
1841                currentIndex = BEFORE_FIRST_INDEX;
1842                setCurrent(null, false);
1843            }
1844        }
1845    }
1846
1847    //--------------------------------------------------------------------------
1848    //
1849    //  Variables
1850    //
1851    //--------------------------------------------------------------------------
1852
1853    /**
1854     *  @private
1855     */
1856    private var _view:ListCollectionView;
1857
1858    /**
1859     *  @private
1860     */
1861    private var currentIndex:int;
1862
1863    /**
1864     *  @private
1865     */
1866    private var currentValue:Object;
1867
1868    /**
1869     *  @private
1870     */
1871    private var invalid:Boolean;
1872
1873    /**
1874     *  @private
1875     *  Used for accessing localized Error messages.
1876     */
1877    private var resourceManager:IResourceManager =
1878                                    ResourceManager.getInstance();
1879
1880    //--------------------------------------------------------------------------
1881    //
1882    //  Properties
1883    //
1884    //--------------------------------------------------------------------------
1885
1886    /**
1887     *  Get a reference to the view that this cursor is associated with.
1888     *  @return the associated <code>ICollectionView</code>
1889     *
1890     *  @langversion 3.0
1891     *  @playerversion Flash 9
1892     *  @playerversion AIR 1.1
1893     *  @productversion Flex 3
1894     */
1895    public function get view():ICollectionView
1896    {
1897        checkValid();
1898        return _view;
1899    }
1900
1901    [Bindable("cursorUpdate")]
1902    /**
1903     *  Provides access the object at the current location referenced by
1904     *  this cursor within the source collection.
1905     *  If the cursor is beyond the ends of the collection (beforeFirst,
1906     *  afterLast) this will return <code>null</code>.
1907     *
1908     *  @see mx.collections.IViewCursor#moveNext
1909     *  @see mx.collections.IViewCursor#movePrevious
1910     *  @see mx.collections.IViewCursor#seek
1911     *  @see mx.collections.IViewCursor#beforeFirst
1912     *  @see mx.collections.IViewCursor#afterLast
1913     *
1914     *  @langversion 3.0
1915     *  @playerversion Flash 9
1916     *  @playerversion AIR 1.1
1917     *  @productversion Flex 3
1918     */
1919    public function get current():Object
1920    {
1921        checkValid();
1922
1923        return currentValue;
1924    }
1925
1926    [Bindable("cursorUpdate")]
1927
1928    /**
1929     *  Provides access to the bookmark of the item returned by the
1930     *  <code>current</code> property.
1931     *  The bookmark can be used to move the cursor to a previously visited
1932     *  item, or one relative to it (see the <code>seek()</code> method for
1933     *  more information).
1934     *
1935     *  @see mx.collections.IViewCursor#current
1936     *  @see mx.collections.IViewCursor#seek
1937     *
1938     *  @langversion 3.0
1939     *  @playerversion Flash 9
1940     *  @playerversion AIR 1.1
1941     *  @productversion Flex 3
1942     */
1943    public function get bookmark():CursorBookmark
1944    {
1945        checkValid();
1946        if (view.length == 0 || beforeFirst) return CursorBookmark.FIRST;
1947        else if (afterLast) return CursorBookmark.LAST;
1948        //if currentIndex > view.length this is a bug in cursor and i want the
1949        //exception thrown to track it down
1950        else return ListCollectionView(view).getBookmark(currentIndex);
1951    }
1952
1953
1954    [Bindable("cursorUpdate")]
1955    /**
1956     * true if the current is sitting before the first item in the view.
1957     * If the ICollectionView is empty (length == 0) this will always
1958     * be true.
1959     *
1960     *  @langversion 3.0
1961     *  @playerversion Flash 9
1962     *  @playerversion AIR 1.1
1963     *  @productversion Flex 3
1964     */
1965    public function get beforeFirst():Boolean
1966    {
1967        checkValid();
1968        return currentIndex == BEFORE_FIRST_INDEX || view.length == 0;
1969    }
1970
1971
1972    [Bindable("cursorUpdate")]
1973    /**
1974     * true if the cursor is sitting after the last item in the view.
1975     * If the ICollectionView is empty (length == 0) this will always
1976     * be true.
1977     *
1978     *  @langversion 3.0
1979     *  @playerversion Flash 9
1980     *  @playerversion AIR 1.1
1981     *  @productversion Flex 3
1982     */
1983    public function get afterLast():Boolean
1984    {
1985        checkValid();
1986        return currentIndex == AFTER_LAST_INDEX || view.length == 0;
1987    }
1988
1989    /**
1990     *  Finds the item with the specified properties within the
1991     *  collection and positions the cursor on that item.
1992     *  If the item can not be found no change to the current location will be
1993     *  made.
1994     *  <code>findAny()</code> can only be called on sorted views, if the view
1995     *  isn't sorted, or items in the view do not contain properties used
1996     *  to compute the sort order, a <code>CursorError</code> will be thrown.
1997     *  <p>
1998     *  If the associated collection is remote, and not all of the items have
1999     *  been cached locally this method will begin an asynchronous fetch from the
2000     *  remote collection, or if one is already in progress wait for it to
2001     *  complete before making another fetch request.
2002     *  If multiple items can match the search criteria then the item found is
2003     *  non-deterministic.
2004     *  If it is important to find the first or last occurrence of an item in a
2005     *  non-unique index use the <code>findFirst()</code> or
2006     *  <code>findLast()</code>.
2007     *  The values specified must be configured as name-value pairs, as in an
2008     *  associative array (or the actual object to search for).
2009     *  The values of the names specified must match those properties specified in
2010     *  the sort. for example
2011     *  If properties "x", "y", and "z" are the in the current index, the values
2012     *  specified should be {x:x-value, y:y-value,z:z-value}.
2013     *  When all of the data is local this method will return <code>true</code> if
2014     *  the item can be found and false otherwise.
2015     *  If the data is not local and an asynchronous operation must be performed,
2016     *  an <code>ItemPendingError</code> will be thrown.
2017     *
2018     *  @see mx.collections.IViewCursor#findFirst
2019     *  @see mx.collections.IViewCursor#findLast
2020     *  @see mx.collections.errors.ItemPendingError
2021     *
2022     *  @langversion 3.0
2023     *  @playerversion Flash 9
2024     *  @playerversion AIR 1.1
2025     *  @productversion Flex 3
2026     */
2027    public function findAny(values:Object):Boolean
2028    {
2029        checkValid();
2030        var lcView:ListCollectionView = ListCollectionView(view);
2031        var index:int;
2032        try
2033        {
2034            index = lcView.findItem(values, Sort.ANY_INDEX_MODE);
2035        }
2036        catch(e:SortError)
2037        {
2038            //this is because the find critieria is not compatible with the
2039            //sort
2040            throw new CursorError(e.message);
2041        }
2042        if (index > -1)
2043        {
2044            currentIndex = index;
2045            setCurrent(lcView.getItemAt(currentIndex));
2046        }
2047        return index > -1;
2048    }
2049
2050    /**
2051     *  Finds the first item with the specified properties
2052     *  within the collection and positions the cursor on that item.
2053     *  If the item can not be found no change to the current location will be
2054     *  made.
2055     *  <code>findFirst()</code> can only be called on sorted views, if the view
2056     *  isn't sorted, or items in the view do not contain properties used
2057     *  to compute the sort order, a <code>CursorError</code> will be thrown.
2058     *  <p>
2059     *  If the associated collection is remote, and not all of the items have been
2060     *  cached locally this method will begin an asynchronous fetch from the
2061     *  remote collection, or if one is already in progress wait for it to
2062     *  complete before making another fetch request.
2063     *  If it is not important to find the first occurrence of an item in a
2064     *  non-unique index use <code>findAny()</code> as it may be a little faster.
2065     *  The values specified must be configured as name-value pairs, as in an
2066     *  associative array (or the actual object to search for).
2067     *  The values of the names specified must match those properties specified in
2068     *  the sort. for example If properties "x", "y", and "z" are the in the current
2069     *  index, the values specified should be {x:x-value, y:y-value,z:z-value}.
2070     *  When all of the data is local this method will
2071     *  return <code>true</code> if the item can be found and false otherwise.
2072     *  If the data is not local and an asynchronous operation must be performed,
2073     *  an <code>ItemPendingError</code> will be thrown.
2074     *
2075     *  @see mx.collections.IViewCursor#findAny
2076     *  @see mx.collections.IViewCursor#findLast
2077     *  @see mx.collections.errors.ItemPendingError
2078     *
2079     *  @langversion 3.0
2080     *  @playerversion Flash 9
2081     *  @playerversion AIR 1.1
2082     *  @productversion Flex 3
2083     */
2084     public function findFirst(values:Object):Boolean
2085    {
2086        checkValid();
2087        var lcView:ListCollectionView = ListCollectionView(view);
2088        var index:int;
2089        try
2090        {
2091            index = lcView.findItem(values, Sort.FIRST_INDEX_MODE);
2092        }
2093        catch(sortError:SortError)
2094        {
2095            //this is because the find critieria is not compatible with the
2096            //sort
2097            throw new CursorError(sortError.message);
2098        }
2099        if (index > -1)
2100        {
2101            currentIndex = index;
2102            setCurrent(lcView.getItemAt(currentIndex));
2103        }
2104        return index > -1;
2105    }
2106
2107
2108    /**
2109     *  Finds the last item with the specified properties
2110     *  within the collection and positions the cursor on that item.
2111     *  If the item can not be found no change to the current location will be
2112     *  made.
2113     *  <code>findLast()</code> can only be called on sorted views, if the view
2114     *  isn't sorted, or items in the view do not contain properties used
2115     *  to compute the sort order, a <code>CursorError</code> will be thrown.
2116     *  <p>
2117     *  If the associated collection is remote, and not all of the items have been
2118     *  cached locally this method will begin an asynchronous fetch from the
2119     *  remote collection, or if one is already in progress wait for it to
2120     *  complete before making another fetch request.
2121     *  If it is not important to find the last occurrence of an item in a
2122     *  non-unique index use <code>findAny()</code> as it may be a little faster.
2123     *  The values specified must be configured as  name-value pairs, as in an
2124     *  associative array (or the actual object to search for).
2125     *  The values of the names specified must match those properties specified in
2126     *  the sort. for example If properties "x", "y", and "z" are the in the current
2127     *  index, the values specified should be {x:x-value, y:y-value,z:z-value}.
2128     *  When all of the data is local this method will
2129     *  return <code>true</code> if the item can be found and false otherwise.
2130     *  If the data is not local and an asynchronous operation must be performed,
2131     *  an <code>ItemPendingError</code> will be thrown.
2132     *
2133     *  @see mx.collections.IViewCursor#findAny
2134     *  @see mx.collections.IViewCursor#findFirst
2135     *  @see mx.collections.errors.ItemPendingError
2136     *
2137     *  @langversion 3.0
2138     *  @playerversion Flash 9
2139     *  @playerversion AIR 1.1
2140     *  @productversion Flex 3
2141     */
2142    public function findLast(values:Object):Boolean
2143    {
2144        checkValid();
2145        var lcView:ListCollectionView = ListCollectionView(view);
2146        var index:int;
2147        try
2148        {
2149            index = lcView.findItem(values, Sort.LAST_INDEX_MODE);
2150        }
2151        catch(sortError:SortError)
2152        {
2153            //this is because the find critieria is not compatible with the
2154            //sort
2155            throw new CursorError(sortError.message);
2156        }
2157        if (index > -1)
2158        {
2159            currentIndex = index;
2160            setCurrent(lcView.getItemAt(currentIndex));
2161        }
2162        return index > -1;
2163    }
2164
2165    /**
2166     * Insert the specified item before the cursor's current position.
2167     * If the cursor is <code>afterLast</code> the insertion
2168     * will happen at the end of the View.  If the cursor is
2169     * <code>beforeFirst</code> on a non-empty view an error will be thrown.
2170     *
2171     *  @langversion 3.0
2172     *  @playerversion Flash 9
2173     *  @playerversion AIR 1.1
2174     *  @productversion Flex 3
2175     */
2176    public function insert(item:Object):void
2177    {
2178        var insertIndex:int;
2179        if (afterLast)
2180        {
2181            insertIndex = view.length;
2182        }
2183        else if (beforeFirst)
2184        {
2185            if (view.length > 0)
2186            {
2187                var message:String = resourceManager.getString(
2188                    "collections", "invalidInsert");
2189                throw new CursorError(message);
2190            }
2191            else
2192            {
2193                insertIndex = 0;
2194            }
2195        }
2196        else
2197        {
2198            insertIndex = currentIndex;
2199        }
2200        ListCollectionView(view).addItemAt(item, insertIndex);
2201    }
2202
2203    /**
2204     *  Moves the cursor to the next item within the collection. On success
2205     *  the <code>current</code> property will be updated to reference the object at this
2206     *  new location.  Returns true if current is valid, false if not (afterLast).
2207     *  If the data is not local and an asynchronous operation must be performed, an
2208     *  <code>ItemPendingError</code> will be thrown. See the ItemPendingError docs
2209     *  as well as the collections documentation for more information on using the
2210     *  ItemPendingError.
2211     *
2212     *  @return true if still in the list, false if current is now afterLast
2213     *
2214     *  @see mx.collections.IViewCursor#current
2215     *  @see mx.collections.IViewCursor#movePrevious
2216     *  @see mx.collections.errors.ItemPendingError
2217     *  @see mx.collectoins.events.ItemAvailableEvent
2218     *  @example
2219     *  <pre>
2220     *    var myArrayCollection:ICollectionView = new ArrayCollection(["Bobby", "Mark", "Trevor", "Jacey", "Tyler"]);
2221     *    var cursor:IViewCursor = myArrayCollection.createCursor();
2222     *    while (!cursor.afterLast)
2223     *    {
2224     *       trace(cursor.current);
2225     *       cursor.moveNext();
2226     *     }
2227     *  </pre>
2228     *
2229     *  @langversion 3.0
2230     *  @playerversion Flash 9
2231     *  @playerversion AIR 1.1
2232     *  @productversion Flex 3
2233     */
2234    public function moveNext():Boolean
2235    {
2236        //the afterLast getter checks validity and also checks length > 0
2237        if (afterLast)
2238        {
2239            return false;
2240        }
2241        // we can't set the index until we know that we can move there first.
2242        var tempIndex:int = beforeFirst ? 0 : currentIndex + 1;
2243        if (tempIndex >= view.length)
2244        {
2245            tempIndex = AFTER_LAST_INDEX;
2246            setCurrent(null);
2247        }
2248        else
2249        {
2250            setCurrent(ListCollectionView(view).getItemAt(tempIndex));
2251        }
2252        currentIndex = tempIndex;
2253        return !afterLast;
2254    }
2255
2256    /**
2257     *  Moves the cursor to the previous item within the collection. On success
2258     *  the <code>current</code> property will be updated to reference the object at this
2259     *  new location.  Returns true if current is valid, false if not (beforeFirst).
2260     *  If the data is not local and an asynchronous operation must be performed, an
2261     *  <code>ItemPendingError</code> will be thrown. See the ItemPendingError docs
2262     * as well as the collections documentation for more information on using the
2263     * ItemPendingError.
2264     *
2265     *  @return true if still in the list, false if current is now beforeFirst
2266     *
2267     *  @see mx.collections.IViewCursor#current
2268     *  @see mx.collections.IViewCursor#moveNext
2269     *  @see mx.collections.errors.ItemPendingError
2270     *  @see mx.collectoins.events.ItemAvailableEvent
2271     *  @example
2272     *  <pre>
2273     *     var myArrayCollection:ICollectionView = new ArrayCollection(["Bobby", "Mark", "Trevor", "Jacey", "Tyler"]);
2274     *     var cursor:ICursor = myArrayCollection.createCursor();
2275     *     cursor.seek(CursorBookmark.last);
2276     *     while (!cursor.beforeFirst)
2277     *     {
2278     *        trace(current);
2279     *        cursor.movePrevious();
2280     *      }
2281     *  </pre>
2282     *
2283     *  @langversion 3.0
2284     *  @playerversion Flash 9
2285     *  @playerversion AIR 1.1
2286     *  @productversion Flex 3
2287     */
2288
2289    public function movePrevious():Boolean
2290    {
2291        //the afterLast getter checks validity and also checks length > 0
2292        if (beforeFirst)
2293        {
2294            return false;
2295        }
2296        // we can't set the index until we know that we can move there first
2297        var tempIndex:int = afterLast ? view.length - 1 : currentIndex - 1;
2298        if (tempIndex == -1)
2299        {
2300            tempIndex = BEFORE_FIRST_INDEX;
2301            setCurrent(null);
2302        }
2303        else
2304        {
2305            setCurrent(ListCollectionView(view).getItemAt(tempIndex));
2306        }
2307        currentIndex = tempIndex;
2308        return !beforeFirst;
2309    }
2310
2311    /**
2312     * Remove the current item and return it.  If the cursor is
2313     * <code>beforeFirst</code> or <code>afterLast</code> throw a
2314     * CursorError.
2315     *
2316     *  @langversion 3.0
2317     *  @playerversion Flash 9
2318     *  @playerversion AIR 1.1
2319     *  @productversion Flex 3
2320     */
2321    public function remove():Object
2322    {
2323        if (beforeFirst || afterLast)
2324        {
2325            var message:String = resourceManager.getString(
2326                "collections", "invalidRemove");
2327            throw new CursorError(message);
2328        }
2329        var oldIndex:int = currentIndex;
2330        currentIndex++;
2331        if (currentIndex >= view.length)
2332        {
2333            currentIndex = AFTER_LAST_INDEX;
2334            setCurrent(null);
2335        }
2336        else
2337        {
2338            try
2339            {
2340                setCurrent(ListCollectionView(view).getItemAt(currentIndex));
2341            }
2342            catch(e:ItemPendingError)
2343            {
2344                setCurrent(null, false);
2345                ListCollectionView(view).removeItemAt(oldIndex);
2346                throw e;
2347            }
2348        }
2349        var removed:Object = ListCollectionView(view).removeItemAt(oldIndex);
2350        return removed;
2351    }
2352
2353    /**
2354     *  Moves the cursor to a location at an offset from the specified
2355     *  bookmark.
2356     *  The offset can be negative in which case the cursor is positioned an
2357     *  offset number of items prior to the specified bookmark.
2358     *  If the associated collection is remote, and not all of the items have been
2359     *  cached locally this method will begin an asynchronous fetch from the
2360     *  remote collection.
2361     *
2362     *  If the data is not local and an asynchronous operation must be performed, an
2363     *  <code>ItemPendingError</code> will be thrown. See the ItemPendingError docs
2364     *  as well as the collections documentation for more information on using the
2365     *  ItemPendingError.
2366     *
2367     *
2368     *  @param bookmark <code>CursorBookmark</code> reference to marker information that
2369     *                 allows repositioning to a specific location.
2370     *           In addition to supplying a value returned from the <code>bookmark</code>
2371     *           property, there are three constant bookmark values that can be
2372     *           specified:
2373     *            <ul>
2374     *                <li><code>CursorBookmark.FIRST</code> - seek from
2375     *                the start (first element) of the collection</li>
2376     *                <li><code>CursorBookmark.CURRENT</code> - seek from
2377     *                the current position in the collection</li>
2378     *                <li><code>CursorBookmark.LAST</code> - seek from the
2379     *                end (last element) of the collection</li>
2380     *            </ul>
2381     *  @param offset indicates how far from the specified bookmark to seek.
2382     *           If the specified number is negative the cursor will attempt to
2383     *           move prior to the specified bookmark, if the offset specified is
2384     *           beyond the end points of the collection the cursor will be
2385     *           positioned off the end (beforeFirst or afterLast).
2386     *  @param prefetch indicates the intent to iterate in a specific direction once the
2387     *           seek operation completes, this reduces the number of required
2388     *           network round trips during a seek.
2389     *           If the iteration direction is known at the time of the request
2390     *           the appropriate amount of data can be returned ahead of the
2391     *           request to iterate it.
2392     *
2393     *  @langversion 3.0
2394     *  @playerversion Flash 9
2395     *  @playerversion AIR 1.1
2396     *  @productversion Flex 3
2397     */
2398    public function seek(bookmark:CursorBookmark, offset:int = 0, prefetch:int = 0):void
2399    {
2400        checkValid();
2401        if (view.length == 0)
2402        {
2403            currentIndex = AFTER_LAST_INDEX;
2404            setCurrent(null, false);
2405            return;
2406        }
2407
2408        var newIndex:int = currentIndex;
2409        if (bookmark == CursorBookmark.FIRST)
2410        {
2411            newIndex = 0;
2412        }
2413        else if (bookmark == CursorBookmark.LAST)
2414        {
2415            newIndex = view.length - 1;
2416        }
2417        else if (bookmark != CursorBookmark.CURRENT)
2418        {
2419            var message:String;
2420            try
2421            {
2422                newIndex = ListCollectionView(view).getBookmarkIndex(bookmark);
2423                if (newIndex < 0)
2424                {
2425                    setCurrent(null);
2426
2427                    message = resourceManager.getString(
2428                        "collections", "bookmarkInvalid");
2429                    throw new CursorError(message);
2430                }
2431            }
2432            catch(bmError:CollectionViewError)
2433            {
2434                message = resourceManager.getString(
2435                    "collections", "bookmarkInvalid");
2436                throw new CursorError(message);
2437            }
2438        }
2439
2440        newIndex += offset;
2441
2442        var newCurrent:Object = null;
2443        if (newIndex >= view.length)
2444        {
2445            currentIndex = AFTER_LAST_INDEX;
2446        }
2447        else if (newIndex < 0)
2448        {
2449            currentIndex = BEFORE_FIRST_INDEX;
2450        }
2451        else
2452        {
2453            newCurrent = ListCollectionView(view).getItemAt(newIndex, prefetch);
2454            currentIndex = newIndex;
2455        }
2456        setCurrent(newCurrent);
2457    }
2458
2459    //--------------------------------------------------------------------------
2460    //
2461    // Internal methods
2462    //
2463    //--------------------------------------------------------------------------
2464
2465    private function checkValid():void
2466    {
2467        if (invalid)
2468        {
2469            var message:String = resourceManager.getString(
2470                "collections", "invalidCursor");
2471            throw new CursorError(message);
2472        }
2473    }
2474
2475    private function collectionEventHandler(event:CollectionEvent):void
2476    {
2477        switch (event.kind)
2478        {
2479            case CollectionEventKind.ADD:
2480                if (event.location <= currentIndex)
2481                {
2482                    currentIndex += event.items.length;
2483                }
2484            break;
2485
2486            case CollectionEventKind.REMOVE:
2487                if (event.location < currentIndex)
2488                {
2489                    currentIndex -= event.items.length;
2490                }
2491                else if (event.location == currentIndex)
2492                {
2493                    if (currentIndex < view.length)
2494                    {
2495                        try
2496                        {
2497                            setCurrent(ListCollectionView(view).getItemAt(currentIndex));
2498                        }
2499                        catch(error:ItemPendingError)
2500                        {
2501                            setCurrent(null, false);
2502                        }
2503                    }
2504                    else // currentIndex == view.length
2505                    {
2506                        //we were removed!  is this an error?
2507                        //should cursor move to now last item, view.length - 1??
2508                        currentIndex = AFTER_LAST_INDEX;
2509                        setCurrent(null); //dispatch the updated at least
2510                    }
2511                }
2512            break;
2513
2514            case CollectionEventKind.MOVE:
2515                if (event.oldLocation == currentIndex)
2516                {
2517                    currentIndex = event.location;
2518                }
2519                else
2520                {
2521                    if (event.oldLocation < currentIndex)
2522                    {
2523                        currentIndex -= event.items.length;
2524                    }
2525                    if (event.location <= currentIndex)
2526                    {
2527                        currentIndex += event.items.length;
2528                    }
2529                }
2530            break;
2531
2532            case CollectionEventKind.REFRESH:
2533                if (!(beforeFirst || afterLast))
2534                {
2535                    try
2536                    {
2537                        currentIndex = ListCollectionView(view).getItemIndex(currentValue);
2538                    }
2539                    catch (e:SortError)
2540                    {
2541                        if (ListCollectionView(view).sort)
2542                        {
2543                            currentIndex = ListCollectionView(view).getLocalItemIndex(currentValue);
2544                        }
2545                    }
2546                    if (currentIndex == -1)
2547                    {
2548                        setCurrent(null);
2549                    }
2550                }
2551            break;
2552
2553            case CollectionEventKind.REPLACE:
2554                if (event.location == currentIndex)
2555                {
2556                    try
2557                    {
2558                        setCurrent(ListCollectionView(view).getItemAt(currentIndex));
2559                    }
2560                    catch(error:ItemPendingError)
2561                    {
2562                        setCurrent(null, false);
2563                    }
2564                }
2565            break;
2566
2567            case CollectionEventKind.RESET:
2568                //just move to the beginning
2569                currentIndex = BEFORE_FIRST_INDEX;
2570                setCurrent(null);
2571                break;
2572        }
2573    }
2574
2575    /**
2576     *  @private
2577     */
2578    private function setCurrent(value:Object, dispatch:Boolean = true):void
2579    {
2580        currentValue = value;
2581
2582        if (dispatch)
2583            dispatchEvent(new FlexEvent(FlexEvent.CURSOR_UPDATE));
2584    }
2585}
2586
2587/**
2588 *  @private
2589 *  Encapsulates the positional aspects of a cursor within an ListCollectionView.
2590 *  Only the ListCollectionView should construct this.
2591 */
2592class ListCollectionViewBookmark extends CursorBookmark
2593{
2594    mx_internal var index:int;
2595    mx_internal var view:ListCollectionView;
2596    mx_internal var viewRevision:int;
2597
2598    /**
2599     *  @private
2600     */
2601    public function ListCollectionViewBookmark(value:Object,
2602                                               view:ListCollectionView,
2603                                               viewRevision:int,
2604                                               index:int)
2605    {
2606        super(value);
2607        this.view = view;
2608        this.viewRevision = viewRevision;
2609        this.index = index;
2610    }
2611
2612    /**
2613     * Get the approximate index of the item represented by this bookmark
2614     * in its view.  If the item has been paged out this may throw an
2615     * ItemPendingError.  If the item is not in the current view -1 will be
2616     * returned.  This method may also return -1 if index-based location is not
2617     * possible.
2618     *
2619     *  @langversion 3.0
2620     *  @playerversion Flash 9
2621     *  @playerversion AIR 1.1
2622     *  @productversion Flex 3
2623     */
2624    override public function getViewIndex():int
2625    {
2626        return view.getBookmarkIndex(this);
2627    }
2628}
2629