1////////////////////////////////////////////////////////////////////////////////
2//
3//  ADOBE SYSTEMS INCORPORATED
4//  Copyright 2006-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.controls.fileSystemClasses
13{
14
15import flash.events.Event;
16import flash.events.KeyboardEvent;
17import flash.filesystem.File;
18import flash.system.Capabilities;
19import flash.ui.Keyboard;
20import mx.collections.ArrayCollection;
21import mx.controls.FileSystemEnumerationMode;
22import mx.controls.dataGridClasses.DataGridColumn;
23import mx.core.mx_internal;
24import mx.events.FileEvent;
25import mx.events.FlexEvent;
26import mx.events.ListEvent;
27import mx.resources.IResourceManager;
28import mx.resources.ResourceManager;
29import mx.utils.DirectoryEnumeration;
30
31use namespace mx_internal;
32
33[ExcludeClass]
34
35/**
36 *  @private
37 */
38public class FileSystemControlHelper
39{
40    include "../../core/Version.as";
41
42    //--------------------------------------------------------------------------
43    //
44    //  Class initialization
45    //
46    //--------------------------------------------------------------------------
47
48    /**
49     *  @private
50     */
51    public static var COMPUTER:File;
52
53    /**
54     *  @private
55     */
56    private static function initClass():void
57    {
58		if (Capabilities.os.substring(0, 3) == "Win")
59   	 	 	COMPUTER = new File("root$:\\Computer");
60      	else // Mac or Unix
61      		COMPUTER = new File("/Computer");
62    }
63
64    initClass();
65
66    //--------------------------------------------------------------------------
67    //
68    //  Class methods
69    //
70    //--------------------------------------------------------------------------
71
72    /**
73     *  @private
74     */
75	private static function fileSystemIsCaseInsensitive():Boolean
76	{
77		var os:String = Capabilities.os.substring(0, 3);
78		return os == "Win" || os == "Mac";
79	}
80
81    //--------------------------------------------------------------------------
82    //
83    //  Constructor
84    //
85    //--------------------------------------------------------------------------
86
87	/**
88	 *  Constructor.
89	 *
90	 *  @langversion 3.0
91	 *  @playerversion AIR 1.1
92	 *  @productversion Flex 3
93	 */
94	public function FileSystemControlHelper(owner:Object, hierarchical:Boolean)
95	{
96		super();
97
98		this.owner = owner;
99		this.hierarchical = hierarchical;
100
101		owner.addEventListener(FlexEvent.UPDATE_COMPLETE,
102							   updateCompleteHandler);
103	}
104
105    //--------------------------------------------------------------------------
106    //
107    //  Variables
108    //
109    //--------------------------------------------------------------------------
110
111	/**
112	 *  @private
113	 *  A reference to the FileSystemList, FileSystemDataGrid,
114	 *  FileSystemTree, or FileSystemComboBox using this object.
115	 */
116	mx_internal var owner:Object;
117
118	/**
119	 *  @private
120	 *  A flag indicating whether the dataProvider of the owner
121	 *  is hierarchical or flat.
122	 *  In other words, this flag is true if the owner
123	 *  is a FileSystemTree and false otherwise.
124	 */
125	mx_internal var hierarchical:Boolean;
126
127	/**
128	 *  @private
129	 */
130	mx_internal var resourceManager:IResourceManager =
131						ResourceManager.getInstance();
132
133    //--------------------------------------------------------------------------
134    //
135    //  Properties
136    //
137    //--------------------------------------------------------------------------
138
139    //----------------------------------
140	//  backHistory
141    //----------------------------------
142
143	/**
144	 *  @private
145	 */
146	public function get backHistory():Array
147	{
148		return historyIndex > 0 ?
149			   history.slice(0, historyIndex).reverse() :
150			   [];
151	}
152
153    //----------------------------------
154	//  canNavigateBack
155    //----------------------------------
156
157	/**
158	 *  @private
159	 */
160	public function get canNavigateBack():Boolean
161	{
162		return historyIndex > 0;
163	}
164
165    //----------------------------------
166	//  canNavigateDown
167    //----------------------------------
168
169	/**
170	 *  @private
171	 */
172	public function get canNavigateDown():Boolean
173	{
174		var selectedFile:File = File(owner.selectedItem);
175		return selectedFile && selectedFile.isDirectory;
176	}
177
178    //----------------------------------
179	//  canNavigateForward
180    //----------------------------------
181
182	/**
183	 *  @private
184	 */
185	public function get canNavigateForward():Boolean
186	{
187		return historyIndex < history.length - 1;
188	}
189
190    //----------------------------------
191	//  canNavigateUp
192    //----------------------------------
193
194	/**
195	 *  @private
196	 */
197	public function get canNavigateUp():Boolean
198	{
199		return !isComputer(directory);
200	}
201
202    //----------------------------------
203	//  directory
204    //----------------------------------
205
206	/**
207	 *  @private
208	 *  Storage for the directory property.
209	 */
210	private var _directory:File;
211
212	/**
213	 *  @private
214	 */
215	private var directoryChanged:Boolean = false;
216
217	/**
218	 *  @private
219	 */
220	public function get directory():File
221	{
222		return _directory;
223	}
224
225	/**
226	 *  @private
227	 */
228	public function set directory(value:File):void
229	{
230		if (!value ||
231			(!isComputer(value) &&
232			 (!value.exists || !value.isDirectory)))
233		{
234			throw(new Error("No such directory: " + value.nativePath));
235		}
236
237		resetHistory(value);
238
239		setDirectory(value);
240	}
241
242    //----------------------------------
243	//  directoryEnumeration
244    //----------------------------------
245
246	/**
247	 *  @private
248	 */
249	mx_internal var directoryEnumeration:DirectoryEnumeration =
250						new DirectoryEnumeration();
251
252    //----------------------------------
253	//  enumerationMode
254    //----------------------------------
255
256	/**
257	 *  @private
258	 *  Storage for the enumerationMode property.
259	 */
260	private var _enumerationMode:String =
261					FileSystemEnumerationMode.DIRECTORIES_FIRST;
262
263	/**
264	 *  @private
265	 */
266	private var enumerationModeChanged:Boolean = false;
267
268	/**
269	 *  @private
270	 */
271	public function get enumerationMode():String
272	{
273		return _enumerationMode;
274	}
275
276	/**
277	 *  @private
278	 */
279	public function set enumerationMode(value:String):void
280	{
281		_enumerationMode = value;
282		enumerationModeChanged = true;
283
284		owner.invalidateProperties();
285	}
286
287    //----------------------------------
288	//  extensions
289    //----------------------------------
290
291	/**
292	 *  @private
293	 *  Storage for the extensions property.
294	 */
295   	private var _extensions:Array /* of String */;
296
297	/**
298	 *  @private
299	 */
300	private var extensionsChanged:Boolean = false;
301
302	/**
303	 *  @private
304	 */
305	public function get extensions():Array /* of String */
306	{
307		return _extensions;
308	}
309
310	/**
311	 *  @private
312	 */
313	public function set extensions(value:Array /* of String */):void
314	{
315		_extensions = value;
316		extensionsChanged = true;
317
318		owner.invalidateProperties();
319	}
320
321    //----------------------------------
322	//  filterFunction
323    //----------------------------------
324
325	/**
326	 *  @private
327	 *  Storage for the filterFunction property.
328	 */
329   	private var _filterFunction:Function;
330
331   	/**
332   	 *  @private
333   	 */
334   	private var filterFunctionChanged:Boolean = false;
335
336	/**
337	 *  @private
338	 */
339	public function get filterFunction():Function
340	{
341		return _filterFunction;
342	}
343
344	/**
345	 *  @private
346	 */
347	public function set filterFunction(value:Function):void
348	{
349		_filterFunction = value;
350		filterFunctionChanged = true;
351
352		owner.invalidateProperties();
353	}
354
355    //----------------------------------
356	//  forwardHistory
357    //----------------------------------
358
359	[Bindable("historyChanged")]
360
361	/**
362	 *  @private
363	 */
364	public function get forwardHistory():Array
365	{
366		return historyIndex < history.length - 1 ?
367			   history.slice(historyIndex + 1) :
368			   [];
369	}
370
371    //----------------------------------
372	//  history
373    //----------------------------------
374
375	/**
376	 *  @private
377	 */
378	public var history:Array;
379
380    //----------------------------------
381	//  historyIndex
382    //----------------------------------
383
384	/**
385	 *  @private
386	 */
387	public var historyIndex:int;
388
389    //----------------------------------
390	//  nativePathToIndexMap
391    //----------------------------------
392
393	/**
394	 *  @private
395	 *  Storage for the nativePathToIndexMap property.
396	 */
397	private var _nativePathToIndexMap:Object;
398
399	/**
400	 *  @private
401	 *  Maps nativePath (String) -> index (int).
402	 *  This map is used to implement findIndex() as a simple lookup,
403	 *  so that multiple finds are fast.
404	 *  It is freed whenever an operation changes which items
405	 *  are displayed in the control, or their order,
406	 *  and rebuilt tne next time it or <code>items</code> is accessed.
407	 */
408	mx_internal function get nativePathToIndexMap():Object
409	{
410		if (!_nativePathToIndexMap)
411			rebuildEnumerationInfo();
412
413		return _nativePathToIndexMap;
414	}
415
416    //----------------------------------
417	//  itemArray
418    //----------------------------------
419
420	/**
421	 *  @private
422	 *  Storage for the itemArray property.
423	 */
424	private var _itemArray:Array /* of File */;
425
426	/**
427	 *  @private
428	 *  An array of all the File items displayed in the control,
429	 *  in the order in which they appear.
430	 *  This array is used together with <code>nativePathToIndexMap</code>
431	 *  to implement findItem() as a simple lookup,
432	 *  so that multiple finds are fast.
433	 *  It is freed whenever an operation changes which items
434	 *  are displayed in the control, or their order,
435	 *  and rebuilt tne next time it
436	 *  or <code>nativePathToIndexMap</code> is accessed.
437	 */
438	mx_internal function get itemArray():Array /* of File */
439	{
440		if (!_itemArray)
441			rebuildEnumerationInfo();
442
443		return _itemArray;
444	}
445
446    //----------------------------------
447	//  nameCompareFunction
448    //----------------------------------
449
450	/**
451	 *  @private
452	 *  Storage for the nameCompareFunction property.
453	 */
454   	private var _nameCompareFunction:Function;
455
456   	/**
457   	 *  @private
458   	 */
459   	private var nameCompareFunctionChanged:Boolean = false;
460
461	/**
462	 *  @private
463	 */
464	public function get nameCompareFunction():Function
465	{
466		return _nameCompareFunction;
467	}
468
469	/**
470	 *  @private
471	 */
472	public function set nameCompareFunction(value:Function):void
473	{
474		_nameCompareFunction = value;
475		nameCompareFunctionChanged = true;
476
477		owner.invalidateProperties();
478	}
479
480    //----------------------------------
481    //  openPaths
482    //----------------------------------
483
484	/**
485	 *  @private
486	 */
487	private var pendingOpenPaths:Array /* of String */;
488
489    /**
490     *  An Array of <code>nativePath</code> Strings for the File items
491	 *  representing the open subdirectories.
492     *  This Array is empty if no subdirectories are open.
493     *
494     *  @default []
495     *
496     *  @langversion 3.0
497     *  @playerversion AIR 1.1
498     *  @productversion Flex 3
499     */
500    public function get openPaths():Array /* of String */
501    {
502        return pendingOpenPaths ?
503        	   pendingOpenPaths :
504        	   getOpenPaths();
505    }
506
507    /**
508     *  @private
509     */
510    public function set openPaths(value:Array /* of String */):void
511    {
512    	pendingOpenPaths = value;
513
514		owner.invalidateProperties();
515    }
516
517    //----------------------------------
518    //  selectedPath
519    //----------------------------------
520
521    /**
522     *  @private
523     */
524    public function get selectedPath():String
525    {
526        return selectedPaths[0];
527    }
528
529    /**
530     *  @private
531     */
532    public function set selectedPath(value:String):void
533    {
534        selectedPaths = [ value ];
535    }
536
537    //----------------------------------
538    //  selectedPaths
539    //----------------------------------
540
541	/**
542	 *  @private
543	 */
544	private var pendingSelectedPaths:Array /* of String */;
545
546    /**
547     *  @private
548     */
549    public function get selectedPaths():Array /* of String */
550    {
551        return pendingSelectedPaths ?
552        	   pendingSelectedPaths :
553        	   getSelectedPaths();
554   }
555
556    /**
557     *  @private
558     */
559    public function set selectedPaths(value:Array /* of String */):void
560    {
561    	pendingSelectedPaths = value;
562
563		owner.invalidateProperties();
564     }
565
566    //----------------------------------
567	//  showExtensions
568    //----------------------------------
569
570	/**
571	 *  @private
572	 *  Storage for the showExtensions property.
573	 */
574	private var _showExtensions:Boolean = true;
575
576	/**
577	 *  @private
578	 */
579	public function get showExtensions():Boolean
580	{
581		return _showExtensions;
582	}
583
584	/**
585	 *  @private
586	 */
587	public function set showExtensions(value:Boolean):void
588	{
589		_showExtensions = value;
590
591		owner.invalidateList();
592	}
593
594    //----------------------------------
595	//  showHidden
596    //----------------------------------
597
598	/**
599	 *  @private
600	 *  Storage for the showHidden property.
601	 */
602 	private var _showHidden:Boolean = false;
603
604	/**
605	 *  @private
606	 */
607 	private var showHiddenChanged:Boolean = false;
608
609	/**
610	 *  @private
611	 */
612	public function get showHidden():Boolean
613	{
614		return _showHidden;
615	}
616
617	/**
618	 *  @private
619	 */
620	public function set showHidden(value:Boolean):void
621	{
622		_showHidden = value;
623		showHiddenChanged = true;
624
625		owner.invalidateProperties();
626	}
627
628    //----------------------------------
629	//  showIcons
630    //----------------------------------
631
632	/**
633	 *  @private
634	 *  Storage for the showIcons property.
635	 */
636 	private var _showIcons:Boolean = true;
637
638	/**
639	 *  @private
640	 */
641	public function get showIcons():Boolean
642	{
643		return _showIcons;
644	}
645
646	/**
647	 *  @private
648	 */
649	public function set showIcons(value:Boolean):void
650	{
651		_showIcons = value;
652
653		owner.invalidateList();
654	}
655
656    //--------------------------------------------------------------------------
657    //
658    //  Methods
659    //
660    //--------------------------------------------------------------------------
661
662	/**
663	 *  @private
664	 */
665	public function commitProperties():void
666	{
667		if (enumerationModeChanged ||
668			extensionsChanged ||
669			filterFunctionChanged ||
670			nameCompareFunctionChanged ||
671			showHiddenChanged)
672		{
673			directoryEnumeration.enumerationMode = enumerationMode;
674			directoryEnumeration.extensions = extensions;
675			directoryEnumeration.filterFunction = filterFunction;
676			directoryEnumeration.nameCompareFunction = nameCompareFunction;
677			directoryEnumeration.showHidden = showHidden;
678			directoryEnumeration.refresh();
679
680			// For a List or DataGrid, refreshing its collection
681			// (which is what directoryEnumeration.refresh() does)
682			// is enough to make the control update properly
683			// with the newly filtered/sorted collection.
684			// But a Tree doesn't properly handle having its
685			// collection refreshed; for example, if the new
686			// filter reduces the number of items, the Tree
687			// can display blank renderers.
688			// So instead we simply reset the dataProvider.
689			owner.dataProvider = directoryEnumeration.collection;
690
691			itemsChanged();
692
693			extensionsChanged = false;
694			enumerationModeChanged = false;
695			filterFunctionChanged = false;
696			nameCompareFunctionChanged = false;
697			showHiddenChanged = false;
698		}
699
700		if (directoryChanged)
701		{
702			fill();
703
704			var event:FileEvent = new FileEvent(FileEvent.DIRECTORY_CHANGE);
705			event.file = directory;
706			owner.dispatchEvent(event);
707
708			directoryChanged = false;
709		}
710	}
711
712	/**
713	 *  Fills the list by enumerating the current directory
714	 *  and setting the dataProvider.
715	 *
716	 *  @langversion 3.0
717	 *  @playerversion AIR 1.1
718	 *  @productversion Flex 3
719	 */
720	mx_internal function fill():void
721	{
722		setDataProvider(isComputer(directory) ?
723						getRootDirectories() :
724						directory.getDirectoryListing());
725	}
726
727	/**
728	 *  @private
729	 */
730	public function styleChanged(styleProp:String):void
731	{
732		if (styleProp == "fileIcon" || styleProp == "directoryIcon")
733			owner.invalidateList();
734	}
735
736	/**
737	 *  @private
738	 */
739	mx_internal function setDirectory(value:File):void
740	{
741		_directory = value;
742		directoryChanged = true;
743
744		// Clear the now-stale contents of the list.
745		// The list will repopulate after the new directory
746		// is enumerated.
747		owner.dataProvider = null;
748
749		if (hierarchical)
750			owner.dataDescriptor.reset();
751
752		owner.invalidateProperties();
753
754		// Trigger databindings.
755		owner.dispatchEvent(new Event("directoryChanged"));
756	}
757
758	/**
759	 *  @private
760	 */
761	mx_internal function setDataProvider(value:Array):void
762	{
763		directoryEnumeration.enumerationMode = enumerationMode;
764		directoryEnumeration.extensions = extensions;
765		directoryEnumeration.filterFunction = filterFunction;
766		directoryEnumeration.nameCompareFunction = nameCompareFunction;
767		directoryEnumeration.showHidden = showHidden;
768
769		directoryEnumeration.source = value;
770
771		owner.dataProvider = directoryEnumeration.collection;
772
773		itemsChanged();
774	}
775
776	/**
777	 *  @private
778	 */
779	public function itemToUID(data:Object):String
780	{
781		return data ? File(data).nativePath : "null";
782	}
783
784	/**
785	 *  @private
786	 */
787	public function isComputer(f:File):Boolean
788	{
789		 if (Capabilities.os.substr(0, 3) =="Win")
790		 	return f.nativePath.substring(0, 6) == "root$:";
791		 return f.nativePath == "/Computer";
792	}
793
794	/**
795	 *  @private
796	 */
797	private function getRootDirectories():Array
798	{
799		var a:Array = [];
800
801		for each (var f:File in File.getRootDirectories())
802		{
803			if (f.isDirectory)
804				a.push(f);
805		}
806
807		return a;
808	}
809
810 	/**
811	 *  @private
812	 */
813	public function fileIconFunction(item:File):Class
814	{
815		if (!showIcons)
816			return null;
817
818		return owner.getStyle(item.isDirectory ? "directoryIcon" : "fileIcon");
819	}
820
821	/**
822	 *  @private
823	 */
824	public function fileLabelFunction(item:File,
825									  column:DataGridColumn = null):String
826	{
827		if (isComputer(item))
828		{
829			return resourceManager.getString(
830				"aircontrols", "computer");
831		}
832
833		var label:String = item.name;
834
835		// The name of the / directory on Mac is the empty string.
836		// In this case, display the nativePath, which will be "/".
837		if (label == "")
838			label = item.nativePath;
839
840		if (!item.isDirectory && !showExtensions)
841		{
842			var index:int = label.lastIndexOf(".");
843			if (index != -1)
844				label = label.substring(0, index);
845		}
846
847		return label;
848	}
849
850	/**
851	 *  @private
852	 */
853	public function findIndex(nativePath:String):int
854	{
855		if (!nativePath)
856			return -1;
857
858		if (fileSystemIsCaseInsensitive())
859			nativePath = nativePath.toLowerCase();
860
861		var value:* = nativePathToIndexMap[nativePath];
862		return value === undefined ? -1 : int(value);
863	}
864
865	/**
866	 *  @private
867	 */
868	public function findItem(nativePath:String):File
869	{
870		var index:int = findIndex(nativePath);
871		if (index == -1)
872			return null;
873
874		return itemArray[index];
875	}
876
877	/**
878	 *  @private
879	 *  This method is called whenever something happens
880	 *  that affects which items are displayed by the
881	 *  control, or the order in which they are displayed.
882	 */
883	mx_internal function itemsChanged():void
884	{
885		// These two data structures are now invalid, so free them.
886		// They will be rebuilt the next time they are needed.
887		_itemArray = null;
888		_nativePathToIndexMap = null;
889	}
890
891	/**
892	 *  @private
893	 */
894	private function rebuildEnumerationInfo():void
895	{
896		_itemArray = [];
897		_nativePathToIndexMap = {};
898
899		enumerateItems(addItemToEnumerationInfo);
900	}
901
902	/**
903	 *  @private
904	 */
905	private function addItemToEnumerationInfo(index:int, item:File):void
906	{
907		var nativePath:String = item.nativePath;
908
909		if (fileSystemIsCaseInsensitive())
910			nativePath = nativePath.toLowerCase();
911
912		_itemArray.push(item);
913		_nativePathToIndexMap[nativePath] = index;
914	}
915
916	/**
917	 *  @private
918	 */
919	private function enumerateItems(itemCallback:Function):int
920	{
921		return enumerate(ArrayCollection(owner.dataProvider),
922						 0, itemCallback);
923	}
924
925	/**
926	 *  @private
927	 */
928	private function enumerate(items:ArrayCollection, index:int,
929							   itemCallback:Function):int
930	{
931		var n:int = items.length;
932		for (var i:int = 0; i < n; i++)
933		{
934			var item:File = File(items.getItemAt(i));
935			itemCallback(index, item);
936			index++;
937
938			if (hierarchical && item.isDirectory && owner.isItemOpen(item))
939			{
940				var childItems:ArrayCollection =
941					owner.dataDescriptor.getChildren(item);
942
943				index = enumerate(childItems, index, itemCallback);
944			}
945		}
946		return index;
947	}
948
949	/**
950	 *  @private
951	 */
952	public function navigateDown():void
953	{
954		if (canNavigateDown)
955			navigateTo(File(owner.selectedItem));
956	}
957
958	/**
959	 *  @private
960	 */
961	public function navigateUp():void
962	{
963		if (canNavigateUp)
964			navigateTo(directory.parent ? directory.parent : COMPUTER);
965	}
966
967	/**
968	 *  @private
969	 */
970	public function navigateBack(index:int = 0):void
971	{
972		if (canNavigateBack)
973			navigateBy(-1 - index);
974	}
975
976	/**
977	 *  @private
978	 */
979	public function navigateForward(index:int = 0):void
980	{
981		if (canNavigateForward)
982			navigateBy(1 + index)
983	}
984
985	/**
986	 *  @private
987	 */
988	private function navigateBy(n:int):void
989	{
990		historyIndex += n;
991
992		if (historyIndex < 0)
993			historyIndex = 0;
994		else if (historyIndex > history.length - 1)
995			historyIndex = history.length - 1;
996
997		setDirectory(history[historyIndex]);
998
999		owner.dispatchEvent(new Event("historyChanged"));
1000	}
1001
1002	/**
1003	 *  @private
1004	 */
1005	public function navigateTo(directory:File):void
1006	{
1007		setDirectory(directory);
1008
1009		pushHistory(directory);
1010	}
1011
1012	/**
1013	 *  @private
1014	 */
1015	public function refresh():void
1016	{
1017		var openPaths:Array /* of String */
1018		var selectedPaths:Array /* of String */;
1019		var firstVisiblePath:String;
1020		var oldHorizontalScrollPosition:int;
1021
1022		if (hierarchical)
1023			openPaths = getOpenPaths();
1024		selectedPaths = getSelectedPaths();
1025		firstVisiblePath = getFirstVisiblePath();
1026		oldHorizontalScrollPosition = owner.horizontalScrollPosition;
1027
1028		fill();
1029
1030		// Tree must be revalidated after its dataProvider
1031		// changes for expandItem() to work.
1032		if (hierarchical)
1033			owner.validateNow();
1034
1035		if (hierarchical)
1036			setOpenPaths(openPaths);
1037		setSelectedPaths(selectedPaths);
1038		if (setFirstVisiblePath(firstVisiblePath))
1039			owner.horizontalScrollPosition = oldHorizontalScrollPosition;
1040	}
1041
1042	/**
1043	 *  @private
1044	 */
1045	private function getOpenPaths():Array /* of String */
1046	{
1047		var openPaths:Array /* of String */ = [];
1048		var n:int = owner.openItems.length;
1049		for (var i:int = 0; i < n; i++)
1050		{
1051			openPaths.push(File(owner.openItems[i]).nativePath);
1052		}
1053		return openPaths;
1054	}
1055
1056	/**
1057	 *  @private
1058	 *  Returns an Array of nativePath Strings for the selected items.
1059	 *  This method is called by refresh() before repopulating the control.
1060	 */
1061	private function getSelectedPaths():Array /* of String */
1062	{
1063		var selectedPaths:Array /* of String */ = [];
1064		var n:int = owner.selectedItems.length;
1065		for (var i:int = 0; i < n; i++)
1066		{
1067			selectedPaths.push(File(owner.selectedItems[i]).nativePath);
1068		}
1069		return selectedPaths;
1070	}
1071
1072	/**
1073	 *  @private
1074	 *  Returns the nativePath of the first visible item.
1075	 *  This method is called by refresh() before repopulating the control.
1076	 */
1077	private function getFirstVisiblePath():String
1078	{
1079		if (owner.dataProvider == null || owner.dataProvider.length == 0)
1080			return null;
1081
1082		var index:int = owner.verticalScrollPosition;
1083		var item:File = itemArray[index];
1084		return item ? item.nativePath : null;
1085	}
1086
1087	/**
1088	 *  @private
1089	 */
1090	private function setOpenPaths(openPaths:Array /* of String */):void
1091	{
1092		var n:int = openPaths.length;
1093		for (var i:int = 0; i < n; i++)
1094		{
1095			owner.openSubdirectory(openPaths[i]);
1096		}
1097	}
1098
1099	/**
1100	 *  @private
1101	 *  Selects items whose nativePaths are in the specified Array.
1102	 *  This method is called by refresh() after repopulating the control.
1103	 */
1104	private function setSelectedPaths(selectedPaths:Array /* of String */):void
1105	{
1106		var indices:Array /* of int */ = [];
1107
1108		var n:int = selectedPaths.length;
1109		for (var i:int = 0; i < n; i++)
1110		{
1111			var path:String = selectedPaths[i];
1112			var index:int = findIndex(path);
1113			if (index != -1)
1114				indices.push(index);
1115		}
1116
1117		owner.selectedIndices = indices;
1118	}
1119
1120	/**
1121	 *  @private
1122	 *  Scrolls the list to the item with the specified nativePath.
1123	 *  This method is by refresh() after repopulating the control.
1124	 */
1125	private function setFirstVisiblePath(path:String):Boolean
1126	{
1127		if (path == null)
1128			return false;
1129
1130		var index:int = findIndex(path);
1131		if (index == -1)
1132			return false;
1133
1134		owner.verticalScrollPosition = index;
1135		return true;
1136	}
1137
1138	/**
1139	 *  @private
1140	 */
1141	public function clear():void
1142	{
1143		owner.dataProvider = null;
1144
1145		itemsChanged();
1146	}
1147
1148	/**
1149	 *  @private
1150	 */
1151	public function resetHistory(directory:File):void
1152	{
1153		history = [ directory ];
1154		historyIndex = 0;
1155
1156		owner.dispatchEvent(new Event("historyChanged"));
1157	}
1158
1159	/**
1160	 *  @private
1161	 */
1162	private function pushHistory(directory:File):void
1163	{
1164		historyIndex++;
1165		history.splice(historyIndex);
1166		history.push(directory);
1167
1168		owner.dispatchEvent(new Event("historyChanged"));
1169	}
1170
1171	/**
1172	 *  @private
1173	 *  Returns an Array of File objects
1174	 *  representing the path to the specified directory.
1175	 *  The first File represents a root directory.
1176	 *  The last File represents the specified file's parent directory.
1177	 */
1178	public function getParentChain(file:File):Array
1179	{
1180		if (!file)
1181			return [];
1182
1183		var a:Array = [];
1184
1185		for (var f:File = file; f != null; f = f.parent)
1186		{
1187			a.unshift(f);
1188		}
1189
1190		return a;
1191	}
1192
1193	/**
1194	 *  @private
1195	 *  Dispatches a cancelable "directoryChanging" event
1196	 *  and returns true if it wasn't canceled.
1197	 */
1198	mx_internal function dispatchDirectoryChangingEvent(newDirectory:File):Boolean
1199	{
1200		var event:FileEvent =
1201			new FileEvent(FileEvent.DIRECTORY_CHANGING, false, true);
1202		event.file = newDirectory;
1203		owner.dispatchEvent(event);
1204
1205		return !event.isDefaultPrevented();
1206	}
1207
1208	/**
1209	 *  @private
1210	 *  Dispatches a "fileChoose" event.
1211	 */
1212	mx_internal function dispatchFileChooseEvent(file:File):void
1213	{
1214		var event:FileEvent = new FileEvent(FileEvent.FILE_CHOOSE);
1215		event.file = file;
1216		owner.dispatchEvent(event);
1217	}
1218
1219 	/**
1220	 *  @private
1221	 */
1222	private function getBackDirectory():File
1223	{
1224		return historyIndex == 0 ?
1225			   null :
1226			   history[historyIndex - 1];
1227	}
1228
1229	/**
1230	 *  @private
1231	 */
1232	private function getForwardDirectory():File
1233	{
1234		return historyIndex == history.length - 1 ?
1235			   null :
1236			   history[historyIndex + 1];
1237	}
1238
1239    //--------------------------------------------------------------------------
1240    //
1241    //  Event handlers
1242    //
1243    //--------------------------------------------------------------------------
1244
1245	/**
1246	 *  @private
1247	 */
1248	private function updateCompleteHandler(event:FlexEvent):void
1249	{
1250		if (pendingOpenPaths != null)
1251		{
1252			setOpenPaths(pendingOpenPaths);
1253
1254			pendingOpenPaths = null;
1255		}
1256
1257		if (pendingSelectedPaths != null)
1258		{
1259			setSelectedPaths(pendingSelectedPaths);
1260
1261			pendingSelectedPaths = null;
1262		}
1263	}
1264
1265	/**
1266	 *  @private
1267	 */
1268   	public function itemDoubleClickHandler(event:ListEvent):void
1269	{
1270		var selectedFile:File = File(owner.selectedItem);
1271
1272		if (selectedFile.isDirectory)
1273		{
1274			if (dispatchDirectoryChangingEvent(selectedFile))
1275				navigateDown();
1276		}
1277		else
1278		{
1279			dispatchFileChooseEvent(selectedFile);
1280		}
1281	}
1282
1283	/**
1284	 *  @private
1285	 */
1286	public function handleKeyDown(event:KeyboardEvent):Boolean
1287	{
1288		switch (event.keyCode)
1289		{
1290			case Keyboard.ENTER:
1291			{
1292				var selectedFile:File = File(owner.selectedItem);
1293
1294				if (canNavigateDown &&
1295					dispatchDirectoryChangingEvent(selectedFile))
1296				{
1297					navigateDown();
1298				}
1299				else
1300				{
1301					dispatchFileChooseEvent(selectedFile);
1302				}
1303				return true;
1304			}
1305
1306			case Keyboard.BACKSPACE:
1307			{
1308				if (canNavigateUp &&
1309					dispatchDirectoryChangingEvent(directory.parent))
1310				{
1311					navigateUp();
1312				}
1313				return true;
1314			}
1315		}
1316
1317		return false;
1318	}
1319}
1320
1321}
1322