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