////////////////////////////////////////////////////////////////////////////////
//
// 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.
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.
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.
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.
This method sets the dataProvider
to null
* but leaves the directory
property unchanged.
* You can call refresh
to populate the list again.
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.
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.
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);
}
}
}
}