//////////////////////////////////////////////////////////////////////////////// // // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// package mx.controls { import flash.events.FileListEvent; import flash.events.KeyboardEvent; import flash.filesystem.File; import flash.ui.Keyboard; import mx.collections.ArrayCollection; import mx.collections.Sort; import mx.controls.fileSystemClasses.FileSystemControlHelper; import mx.controls.fileSystemClasses.FileSystemTreeDataDescriptor; import mx.controls.Tree; import mx.core.mx_internal; import mx.core.ScrollPolicy; import mx.events.FileEvent; import mx.events.ListEvent; import mx.events.TreeEvent; import mx.styles.StyleManager; import mx.styles.CSSStyleDeclaration; use namespace mx_internal; //-------------------------------------- // Events //-------------------------------------- /** * Dispatched whenever the directory property changes * for any reason. * * @eventType mx.events.FileEvent.DIRECTORY_CHANGE * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ [Event(name="directoryChange", type="mx.events.FileEvent")] /** * Dispatched when the user closes an open directory node * using the mouse of keyboard. * * @eventType mx.events.FileEvent.DIRECTORY_CLOSING * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ [Event(name="directoryClosing", type="mx.events.FileEvent")] /** * Dispatched when the user opens a directory node * using the mouse or keyboard. * *

This is a cancelable event. * If you call event.preventDefault(), * this control continues to display the current directory * rather than changing to display the subdirectory which was * double-clicked.

* * @eventType mx.events.FileEvent.DIRECTORY_OPENING * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ [Event(name="directoryOpening", type="mx.events.FileEvent")] /** * Dispatched when the user chooses a file by double-clicking it * or by selecting it and pressing Enter. * * @eventType mx.events.FileEvent.FILE_CHOOSE * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ [Event(name="fileChoose", type="mx.events.FileEvent")] //-------------------------------------- // Other metadata //-------------------------------------- [IconFile("FileSystemTree.png")] [ResourceBundle("aircontrols")] /** * The FileSystemTree control displays the contents of a * file system directory as a tree. * *

You specify the directory whose content is displayed by setting * the directory property to an instance * of the flash.filesystem.File class. * (File instances can represent directories as well as files.) * Whenever this property changes for any reason, the control * dispatches a directoryChange event.

* *

You can set the enumerationMode property to specify * whether to show this directory's files, its subdirectories, or both. * There are three ways to show both files and subdirectories within * each tree node: directories first, files first, or intermixed.

* *

You can set the extensions property to filter the list * so that only files with the specified extensions are displayed. * (Extensions on directories are ignored.) * You can also specify an additional filtering function of your own * by setting the filterFunction property.

* *

You can use the showExtensions property to show * or hide file extensions, and the showIcons property * to show or hide icons.

* *

You can do custom-sorting within each tree node by setting * the nameCompareFunction property to a function * that compares two file or directory names.

* *

If the user double-clicks a closed directory node, * or clicks its disclosure icon, * this control dispatches a directoryOpening event. * If the user double-clicks an open directory node, * or clicks its disclosure icon, * this control dispatches a directoryClosing event. * A handler can cancel either event by calling * event.preventDefault() in which case the node doesn't open.

* *

If the user double-clicks a file node, * this control dispatches a select event.

* * @mxml * *

The <mx:FileSystemTree> tag inherits all of the tag * attributes of its superclass and adds the following tag attributes:

* *
 *  <mx:FileSystemTree
 *    Properties
 *    directory="null"
 *    enumerationMode="directoriesFirst"
 *    extensions="null"
 *    filterFunction="null"
 *    nameCompareFunction="null"
 *    openPaths="null"
 *    selectedPath="null"
 *    selectedPaths="null"
 *    showExtensions="true"
 *    showHidden="false"
 *    showIcons="true"
 * 
 *    Events
 *    directoryChange="No default"
 *    directoryClosing="No default"
 *    directoryOpening="No default"
 *    fileChoose="No default"
 *  />
 *  
* * @see flash.filesystem.File * * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public class FileSystemTree extends Tree { include "../core/Version.as"; //-------------------------------------------------------------------------- // // Class constants // //-------------------------------------------------------------------------- /** * @copy mx.controls.FileSystemList#COMPUTER * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static const COMPUTER:File = FileSystemControlHelper.COMPUTER; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function FileSystemTree() { super(); helper = new FileSystemControlHelper(this, true); dataDescriptor = new FileSystemTreeDataDescriptor(); doubleClickEnabled = true; horizontalScrollPolicy = ScrollPolicy.AUTO; iconFunction = helper.fileIconFunction; labelFunction = helper.fileLabelFunction; addEventListener(TreeEvent.ITEM_OPENING, itemOpeningHandler); addEventListener(ListEvent.ITEM_DOUBLE_CLICK, itemDoubleClickHandler); // Set the initial dataProvider by enumerating the root directories. directory = COMPUTER; } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- /** * @private */ mx_internal var helper:FileSystemControlHelper; //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // directory //---------------------------------- [Bindable("directoryChanged")] /** * The directory whose contents this control displays. * *

If you set this property to a File object representing * an existing directory, the dataProvider * immediately becomes null. * Later, when this control is revalidated by the LayoutManager, * it performs a synchronous enumeration of that directory's * contents and populates the dataProvider property * with an ArrayCollection of the resulting File objects * for the directory's files and subdirectories.

* *

Setting this to a File which does not represent * an existing directory is an error. * Setting this to COMPUTER synchronously displays * the root directories, such as C: and D: on Windows.

* * @default COMPUTER * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get directory():File { return helper.directory; } /** * @private */ public function set directory(value:File):void { helper.directory = value; } //---------------------------------- // enumerationMode //---------------------------------- /** * @copy mx.controls.FileSystemList#enumerationMode * * @default FileSystemEnumerationMode.DIRECTORIES_FIRST * * @see mx.controls.FileSystemEnumerationMode * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get enumerationMode():String { return helper.enumerationMode; } /** * @private */ public function set enumerationMode(value:String):void { helper.enumerationMode = value; } //---------------------------------- // extensions //---------------------------------- /** * @copy mx.controls.FileSystemList#extensions * * @default null * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get extensions():Array /* of String */ { return helper.extensions; } /** * @private */ public function set extensions(value:Array /* of String */):void { helper.extensions = value; } //---------------------------------- // filterFunction //---------------------------------- /** * @copy mx.controls.FileSystemList#filterFunction * * @default null * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get filterFunction():Function { return helper.filterFunction; } /** * @private */ public function set filterFunction(value:Function):void { helper.filterFunction = value; } //---------------------------------- // nameCompareFunction //---------------------------------- /** * @copy mx.controls.FileSystemList#nameCompareFunction * * @default null * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get nameCompareFunction():Function { return helper.nameCompareFunction; } /** * @private */ public function set nameCompareFunction(value:Function):void { helper.nameCompareFunction = value; } //---------------------------------- // openPaths //---------------------------------- /** * An Array of nativePath Strings for the File items * representing the open subdirectories. * This Array is empty if no subdirectories are open. * * @default [] * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get openPaths():Array /* of String */ { return helper.openPaths; } /** * @private */ public function set openPaths(value:Array /* of String */):void { helper.openPaths = value; } //---------------------------------- // selectedPath //---------------------------------- [Bindable("change")] [Bindable("directoryChanged")] /** * @copy mx.controls.FileSystemList#selectedPath * * @default null * * @see mx.controls.listClasses.ListBase#selectedIndex * @see mx.controls.listClasses.ListBase#selectedItem * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get selectedPath():String { return helper.selectedPath; } /** * @private */ public function set selectedPath(value:String):void { helper.selectedPath = value; } //---------------------------------- // selectedPaths //---------------------------------- [Bindable("change")] [Bindable("directoryChanged")] /** * @copy mx.controls.FileSystemList#selectedPaths * * @default [] * * @see mx.controls.listClasses.ListBase#selectedIndex * @see mx.controls.listClasses.ListBase#selectedItem * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get selectedPaths():Array /* of String */ { return helper.selectedPaths; } /** * @private */ public function set selectedPaths(value:Array /* of String */):void { helper.selectedPaths = value; } //---------------------------------- // showExtensions //---------------------------------- /** * @copy mx.controls.FileSystemList#showExtensions * * @default true * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get showExtensions():Boolean { return helper.showExtensions; } /** * @private */ public function set showExtensions(value:Boolean):void { helper.showExtensions = value; } //---------------------------------- // showHidden //---------------------------------- /** * @copy mx.controls.FileSystemList#showHidden * * @default false * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get showHidden():Boolean { return helper.showHidden; } /** * @private */ public function set showHidden(value:Boolean):void { helper.showHidden = value; } //---------------------------------- // showIcons //---------------------------------- /** * @copy mx.controls.FileSystemList#showIcons * * @default true * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get showIcons():Boolean { return helper.showIcons; } /** * @private */ public function set showIcons(value:Boolean):void { helper.showIcons = value; } //-------------------------------------------------------------------------- // // Overridden methods // //-------------------------------------------------------------------------- /** * @private */ override protected function commitProperties():void { helper.commitProperties(); super.commitProperties(); } /** * @private */ override protected function measure():void { super.measure(); var text:String = resourceManager.getString( "aircontrols", "fileSystemTree_measuredText"); measuredWidth = measureText(text).width; } /** * @private * The FileSystemControlHelper calls getStyle("directoryIcon") * and getStyle("fileIcon") because these are the style names * that FileSystemList and FileSystemDataGrid declare for their icons. * But FileSystemTree extends Tree and Tree already declares * the styles "folderClosedIcon" and "defaultLeafIcon" * for these icons, so it doesn't declare "directoryIcon" * and fileIcon. * Therefore, we map the names here. */ override public function getStyle(styleProp:String):* { if (styleProp == "directoryIcon") styleProp = "folderClosedIcon"; else if (styleProp == "fileIcon") styleProp = "defaultLeafIcon"; return super.getStyle(styleProp); } /** * @private */ override public function styleChanged(styleProp:String):void { super.styleChanged(styleProp); helper.styleChanged(styleProp); } /** * @private */ override protected function itemToUID(data:Object):String { return helper.itemToUID(data); } //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- /** * @copy mx.controls.FileSystemList#findIndex() * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function findIndex(nativePath:String):int { return helper.findIndex(nativePath); } /** * @copy mx.controls.FileSystemList#findItem() * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function findItem(nativePath:String):File { return helper.findItem(nativePath); } /** * Re-enumerates the current directory being displayed by this control. * *

When this method returns, the directory property * contains the File instance for the same directory as before. * The dataProvider property is temporarily * null until the directory is re-enumerated. * After the enumeration, the dataProvider property * contains an ArrayCollection of File instances * for the directory's contents.

* * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function refresh():void { helper.refresh(); } /** * Clears the list. * *

This method sets the dataProvider to null * but leaves the directory property unchanged. * You can call refresh to populate the list again.

* * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function clear():void { helper.clear(); } /** * Opens a subdirectory specified by a native file system path. * *

This method automatically opens all intervening directories * required to reach the specified directory.

* *

If the nativePath doesn't specify * an existing file system directory, or if that * directory isn't within the directory that this control * is displaying, then this method does nothing.

* * @param file A String specifying the nativePath * of a File item. * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function openSubdirectory(nativePath:String):void { var item:File = new File(nativePath); if (!item.exists || !item.isDirectory) return; // Don't animate the opens, since we're doing // multiple ones synchronously. var savedOpenDuration:Number = getStyle("openDuration"); setStyle("openDuration", 0); var parentPaths:Array = getParentPaths(item); var n:int = parentPaths.length; for (var i:int = 0; i < n; i++) { item = findItem(parentPaths[i]); if (item) openItem(item, false); } setStyle("openDuration", savedOpenDuration); invalidateList(); } /** * @private */ mx_internal function openItem(item:File, async:Boolean = true):void { if (isItemOpen(item)) return; var children:ArrayCollection = ArrayCollection(dataDescriptor.getChildren(item)); if (!children || modificationDateHasChanged(item)) { if (async) { item.addEventListener(FileListEvent.DIRECTORY_LISTING, directoryListingHandler); item.getDirectoryListingAsync(); } else { insertChildItems(item, item.getDirectoryListing()); } } else { expandItem(item, true, true); helper.itemsChanged(); } invalidateList(); } /** * Closes a subdirectory specified by a native file system path. * *

If the nativePath doesn't specify * a directory being displayed within this control, * then this method does nothing.

* * @param file A String specifying the nativePath * of a File item. * * @langversion 3.0 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function closeSubdirectory(nativePath:String):void { var item:File = findItem(nativePath); if (item && item.isDirectory) closeItem(item); } /** * @private */ mx_internal function closeItem(item:File):void { expandItem(item, false, true); fixSelectionAfterClose(item); helper.itemsChanged(); } /** * @private * * If any selected items are inside the closing item, * deselect them and select the closing item in their place. */ mx_internal function fixSelectionAfterClose(item:File):void { var closingPath:String = item.nativePath; var newSelectedItems:Array = [] var n:int = selectedItems.length; for (var i:int = 0; i < n; i++) { var selectedPath:String = selectedItems[i].nativePath; if (selectedPath.indexOf(closingPath) == 0) newSelectedItems.push(item); else newSelectedItems.push(selectedItems[i]); } selectedItems = newSelectedItems; } /** * @private */ private function getParentPaths(file:File):Array /* of String */ { var a:Array = []; for (var f:File = file; f != null; f = f.parent) { if (f.nativePath == directory.nativePath) break; a.unshift(f.nativePath); } return a; } /** * @private */ private function modificationDateHasChanged(item:File):Boolean { var item2:File = new File(item.nativePath); return item.modificationDate != item2.modificationDate; } /** * @private */ mx_internal function insertChildItems(subdirectory:File, childItems:Array):void { var childCollection:ArrayCollection = new ArrayCollection(childItems); childCollection.filterFunction = helper.directoryEnumeration.fileFilterFunction; childCollection.sort = new Sort(); childCollection.sort.compareFunction = helper.directoryEnumeration.fileCompareFunction; childCollection.refresh(); FileSystemTreeDataDescriptor(dataDescriptor).setChildren( subdirectory, childCollection); expandItem(subdirectory, true, true); helper.itemsChanged(); } /** * @private * * Opens the selected node if it is a directory. * This method does nothing if no node is selected, * or if a file node is selected. */ private function openSelectedSubdirectory():void { var item:File = File(selectedItem); if (item && item.exists && item.isDirectory) openItem(item, true); } /** * @private * * Closes the selected node if it is a directory. * This method does nothing if no node is selected, * or if a file node is selected. */ private function closeSelectedSubdirectory():void { var item:File = File(selectedItem); if (item && item.exists && item.isDirectory) closeItem(item); } /** * @private * Dispatches a cancelable "directoryOpening" event * and returns true if it wasn't canceled. */ private function dispatchDirectoryOpeningEvent(directory:File):Boolean { var event:FileEvent = new FileEvent(FileEvent.DIRECTORY_OPENING, false, true); event.file = directory; dispatchEvent(event); return !event.isDefaultPrevented(); } /** * @private * Dispatches a cancelable "directoryClosing" event * and returns true if it wasn't canceled. */ private function dispatchDirectoryClosingEvent(directory:File):Boolean { var event:FileEvent = new FileEvent(FileEvent.DIRECTORY_CLOSING, false, true); event.file = directory; dispatchEvent(event); return !event.isDefaultPrevented(); } //-------------------------------------------------------------------------- // // Overridden event handlers // //-------------------------------------------------------------------------- /** * @private */ override protected function keyDownHandler(event:KeyboardEvent):void { if (event.keyCode == Keyboard.ENTER) { var selectedFile:File = File(selectedItem); if (selectedFile && !selectedFile.isDirectory) helper.dispatchFileChooseEvent(selectedFile); return; } super.keyDownHandler(event); } //-------------------------------------------------------------------------- // // Event handlers // //-------------------------------------------------------------------------- /** * @private */ private function itemOpeningHandler(event:TreeEvent):void { event.preventDefault(); var item:File = File(event.item); if (event.opening) { if (dispatchDirectoryOpeningEvent(item)) openItem(item, true); } else { if (dispatchDirectoryClosingEvent(item)) closeItem(item); } } /** * @private * Completes an async openItem() call. */ private function directoryListingHandler(event:FileListEvent):void { insertChildItems(File(event.target), event.files); } /** * @private */ private function itemDoubleClickHandler(event:ListEvent):void { var item:File = File(selectedItem); if (item.isDirectory) { if (!isItemOpen(item)) { // Dispatch a cancelable "directoryOpening" event. // If the event wasn't canceled, // then open that node and display its children. if (dispatchDirectoryOpeningEvent(item)) openSelectedSubdirectory(); } else { // Dispatch a cancelable "directoryClosing" event. // If the event wasn't canceled, // then close that node. if (dispatchDirectoryClosingEvent(item)) closeSelectedSubdirectory(); } } else { helper.dispatchFileChooseEvent(item); } } } }