keep keep keep keep keep replace keep keep keep keep

/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, $, brackets, window */

/**
 * ProjectManager glues together the project model and file tree view and integrates as needed with other parts
 * of Brackets. It is responsible for creating and updating the project tree when projects are opened
 * and when changes occur to the file tree.
 *
 * This module dispatches these events:
 *    - beforeProjectClose -- before `_projectRoot` changes, but working set files still open
 *    - projectClose       -- *just* before `_projectRoot` changes; working set already cleared
 *      & project root unwatched
 *    - beforeAppClose     -- before Brackets quits entirely
 *    - projectOpen        -- after `_projectRoot` changes and the tree is re-rendered
 *    - projectRefresh     -- when project tree is re-rendered for a reason other than
 *      a project being opened (e.g. from the Refresh command)
 *
 * These are jQuery events, so to listen for them you do something like this:
 *    $(ProjectManager).on("eventname", handler);
 */
define(function (require, exports, module) {
    "use strict";

    require("utils/Global");

    var _ = require("thirdparty/lodash");

    // Load dependent modules
    var AppInit             = require("utils/AppInit"),
        PreferencesDialogs  = require("preferences/PreferencesDialogs"),
        PreferencesManager  = require("preferences/PreferencesManager"),
        DocumentManager     = require("document/DocumentManager"),
        MainViewManager     = require("view/MainViewManager"),
        CommandManager      = require("command/CommandManager"),
        Commands            = require("command/Commands"),
        Dialogs             = require("widgets/Dialogs"),
        DefaultDialogs      = require("widgets/DefaultDialogs"),
        DeprecationWarning  = require("utils/DeprecationWarning"),
        LanguageManager     = require("language/LanguageManager"),
        Menus               = require("command/Menus"),
        StringUtils         = require("utils/StringUtils"),
        Strings             = require("strings"),
        FileSystem          = require("filesystem/FileSystem"),
        FileViewController  = require("project/FileViewController"),
        PerfUtils           = require("utils/PerfUtils"),
        FileUtils           = require("file/FileUtils"),
        FileSystemError     = require("filesystem/FileSystemError"),
        Urls                = require("i18n!nls/urls"),
        FileSyncManager     = require("project/FileSyncManager"),
        ProjectModel        = require("project/ProjectModel"),
        FileTreeView        = require("project/FileTreeView");

    /**
     * @private
     * Filename to use for project settings files.
     * @type {string}
     */
    var SETTINGS_FILENAME = "." + PreferencesManager.SETTINGS_FILENAME;

    /**
     * Name of the preferences for sorting directories first
     *
     * @type {string}
     */
    var SORT_DIRECTORIES_FIRST = "sortDirectoriesFirst";

    /**
     * @private
     * Forward declarations to make JSLint happy.
     */
    var _fileSystemChange,
        _fileSystemRename,
        _showErrorDialog,
        _saveTreeState,
        _renderTree;

    /**
     * @const
     * @private
     * Error context to show the correct error message
     * @type {int}
     */
    var ERR_TYPE_CREATE                 = 1,
        ERR_TYPE_CREATE_EXISTS          = 2,
        ERR_TYPE_RENAME                 = 3,
        ERR_TYPE_DELETE                 = 4,
        ERR_TYPE_LOADING_PROJECT        = 5,
        ERR_TYPE_LOADING_PROJECT_NATIVE = 6,
        ERR_TYPE_MAX_FILES              = 7,
        ERR_TYPE_OPEN_DIALOG            = 8,
        ERR_TYPE_INVALID_FILENAME       = 9;

    /**
     * @private
     * Reference to the tree control container div. Initialized by
     * htmlReady handler
     * @type {jQueryObject}
     */
    var $projectTreeContainer;

    /**
     * @private
     *
     * Does the file tree currently have the focus?
     *
     * @return {boolean} `true` if the file tree has the focus
     */
    function _hasFileSelectionFocus() {
        return FileViewController.getFileSelectionFocus() === FileViewController.PROJECT_MANAGER;
    }

    /**
     * @private
     * Singleton ProjectModel object.
     * @type {ProjectModel.ProjectModel}
     */
    var model = new ProjectModel.ProjectModel({
        focused: _hasFileSelectionFocus()
    });

    /**
     * @private
     * @type {boolean}
     * A flag to remember when user has been warned about too many files, so they
     * are only warned once per project/session.
     */
    var _projectWarnedForTooManyFiles = false;

    /**
     * @private
     *
     * Event handler which displays an error based on a problem creating a file.
     *
     * @param {$.Event} e jQuery event object
     * @param {{type:any,isFolder:boolean}} errorInfo Information passed in the error events
     */
    function _displayCreationError(e, errorInfo) {
        window.setTimeout(function () {
            var error = errorInfo.type,
                isFolder = errorInfo.isFolder,
                name = errorInfo.name;

            if (error === FileSystemError.ALREADY_EXISTS) {
                _showErrorDialog(ERR_TYPE_CREATE_EXISTS, isFolder, null, name);
            } else if (error === ProjectModel.ERROR_INVALID_FILENAME) {
                _showErrorDialog(ERR_TYPE_INVALID_FILENAME, isFolder, ProjectModel._invalidChars);
            } else {
                var errString = error === FileSystemError.NOT_WRITABLE ?
                        Strings.NO_MODIFICATION_ALLOWED_ERR :
                        StringUtils.format(Strings.GENERIC_ERROR, error);

                _showErrorDialog(ERR_TYPE_CREATE, isFolder, errString, name).getPromise();
            }
        }, 10);
    }

    /**
     * @constructor
     * @private
     *
     * Manages the interaction between the view and the model. This is loosely structured in
     * the style of [Flux](https://github.com/facebook/flux), but the initial implementation did
     * not need all of the parts of Flux yet. This ActionCreator could be replaced later with
     * a real ActionCreator that talks to a Dispatcher.
     *
     * Most of the methods just delegate to the ProjectModel. Some are responsible for integration
     * with other parts of Brackets.
     *
     * @param {ProjectModel} model store (in Flux terminology) with the project data
     */
    function ActionCreator(model) {
        this.model = model;
        this._bindEvents();
    }

    /**
     * @private
     *
     * Listen to events on the ProjectModel and cause the appropriate behavior within the rest of the system.
     */
    ActionCreator.prototype._bindEvents = function () {

        // Change events are the standard Flux signal to rerender the view. Note that
        // current Flux style is to have the view itself listen to the Store for change events
        // and re-render itself.
        this.model.on(ProjectModel.EVENT_CHANGE, function () {
            _renderTree();
        });

        // The "should select" event signals that we need to open the document based on file tree
        // activity.
        this.model.on(ProjectModel.EVENT_SHOULD_SELECT, function (e, data) {
            if (data.add) {
                FileViewController.openFileAndAddToWorkingSet(data.path);
            } else {
                FileViewController.openAndSelectDocument(data.path, FileViewController.PROJECT_MANAGER);
            }
        });

        this.model.on(ProjectModel.EVENT_SHOULD_FOCUS, function () {
            FileViewController.setFileViewFocus(FileViewController.PROJECT_MANAGER);
        });

        this.model.on(ProjectModel.ERROR_CREATION, _displayCreationError);
    };

    /**
     * Sets the directory at the given path to open in the tree and saves the open nodes to view state.
     *
     * See `ProjectModel.setDirectoryOpen`
     */
    ActionCreator.prototype.setDirectoryOpen = function (path, open) {
        this.model.setDirectoryOpen(path, open).then(_saveTreeState);
    };

    /**
     * See `ProjectModel.setSelected`
     */
    ActionCreator.prototype.setSelected = function (path, doNotOpen) {
        this.model.setSelected(path, doNotOpen);
    };

    /**
     * See `ProjectModel.selectInWorkingSet`
     */
    ActionCreator.prototype.selectInWorkingSet = function (path) {
        this.model.selectInWorkingSet(path);
    };

    /**
     * See `ProjectModel.setContext`
     */
    ActionCreator.prototype.setContext = function (path) {
        this.model.setContext(path);
    };

    /**
     * See `ProjectModel.restoreContext`
     */
    ActionCreator.prototype.restoreContext = function () {
        this.model.restoreContext();
    };

    /**
     * See `ProjectModel.startRename`
     */
    ActionCreator.prototype.startRename = function (path) {
        return this.model.startRename(path);
    };

    /**
     * See `ProjectModel.setRenameValue`
     */
    ActionCreator.prototype.setRenameValue = function (path) {
        this.model.setRenameValue(path);
    };

    /**
     * See `ProjectModel.cancelRename`
     */
    ActionCreator.prototype.cancelRename = function () {
        this.model.cancelRename();
    };

    /**
     * See `ProjectModel.performRename`
     */
    ActionCreator.prototype.performRename = function () {
        return this.model.performRename();
    };

    /**
     * See `ProjectModel.startCreating`
     */
    ActionCreator.prototype.startCreating = function (basedir, newName, isFolder) {
        return this.model.startCreating(basedir, newName, isFolder);
    };

    /**
     * See `ProjectModel.setSortDirectoriesFirst`
     */
    ActionCreator.prototype.setSortDirectoriesFirst = function (sortDirectoriesFirst) {
        this.model.setSortDirectoriesFirst(sortDirectoriesFirst);
    };

    /**
     * See `ProjectModel.setFocused`
     */
    ActionCreator.prototype.setFocused = function (focused) {
        this.model.setFocused(focused);
    };

    /**
     * See `ProjectModel.setCurrentFile`
     */
    ActionCreator.prototype.setCurrentFile = function (curFile) {
        this.model.setCurrentFile(curFile);
    };

    /**
     * See `ProjectModel.toggleSubdirectories`
     */
    ActionCreator.prototype.toggleSubdirectories = function (path, openOrClose) {
        this.model.toggleSubdirectories(path, openOrClose);
    };

    /**
     * See `ProjectModel.closeSubtree`
     */
    ActionCreator.prototype.closeSubtree = function (path) {
        this.model.closeSubtree(path);
    };

    /**
     * See `ProjectModel.refresh`
     */
    ActionCreator.prototype.refresh = function () {
        this.model.refresh();
    };

    /**
     * @private
     * @type {ActionCreator}
     *
     * Singleton actionCreator that is used for dispatching changes to the ProjectModel.
     */
    var actionCreator = new ActionCreator(model);

    /**
     * Returns the File or Directory corresponding to the item selected in the sidebar panel, whether in
     * the file tree OR in the working set; or null if no item is selected anywhere in the sidebar.
     * May NOT be identical to the current Document - a folder may be selected in the sidebar, or the sidebar may not
     * have the current document visible in the tree & working set.
     * @return {?(File|Directory)}
     */
    function getSelectedItem() {
        // Prefer file tree context, then selection, else use working set
        var selectedEntry = model.getContext();
        if (!selectedEntry) {
            selectedEntry = model.getSelected();
        }
        if (!selectedEntry) {
            selectedEntry = MainViewManager.getCurrentlyViewedFile();
        }
        return selectedEntry;
    }

    /**
     * @private
     *
     * Handler for changes in document selection.
     */
    function _documentSelectionFocusChange() {
        var curFullPath = MainViewManager.getCurrentlyViewedPath(MainViewManager.ACTIVE_PANE);
        if (curFullPath && _hasFileSelectionFocus()) {
            actionCreator.setSelected(curFullPath, true);
            actionCreator.setFocused(true);
        } else {
            actionCreator.setSelected(null);
            actionCreator.setFocused(false);
        }
    }

    /**
     * @private
     *
     * Handler for changes in the focus between working set and file tree view.
     */
    function _fileViewControllerChange() {
        actionCreator.setFocused(_hasFileSelectionFocus());
    }

    /**
     * @private
     *
     * Handler for changes to which file is currently viewed.
     *
     * @param {Object} e jQuery event object
     * @param {File} curFile Currently viewed file.
     */
    function _currentFileChange(e, curFile) {
        actionCreator.setCurrentFile(curFile);
    }

    /**
     * @private
     *
     * Creates a context object for doing project view state lookups.
     */
    function _getProjectViewStateContext() {
        return { location : { scope: "user",
                             layer: "project",
                             layerID: model.projectRoot.fullPath } };
    }

    /**
     * Returns the encoded Base URL of the currently loaded project, or empty string if no project
     * is open (during startup, or running outside of app shell).
     * @return {String}
     */
    function getBaseUrl() {
        return model.getBaseUrl();
    }

    /**
     * Sets the encoded Base URL of the currently loaded project.
     * @param {String}
     */
    function setBaseUrl(projectBaseUrl) {
        var context = _getProjectViewStateContext();

        projectBaseUrl = model.setBaseUrl(projectBaseUrl);

        PreferencesManager.setViewState("project.baseUrl", projectBaseUrl, context);
    }

    /**
     * Returns true if absPath lies within the project, false otherwise.
     * Does not support paths containing ".."
     * @param {string|FileSystemEntry} absPathOrEntry
     * @return {boolean}
     */
    function isWithinProject(absPathOrEntry) {
        return model.isWithinProject(absPathOrEntry);
    }

    /**
     * If absPath lies within the project, returns a project-relative path. Else returns absPath
     * unmodified.
     * Does not support paths containing ".."
     * @param {!string} absPath
     * @return {!string}
     */
    function makeProjectRelativeIfPossible(absPath) {
        return model.makeProjectRelativeIfPossible(absPath);
    }

    /**
     * Returns the root folder of the currently loaded project, or null if no project is open (during
     * startup, or running outside of app shell).
     * @return {Directory}
     */
    function getProjectRoot() {
        return model.projectRoot;
    }

    /**
     * @private
     *
     * Sets the project root to the given directory, resetting the ProjectModel and file tree in the process.
     *
     * @param {Directory} rootEntry directory object for the project root
     * @return {$.Promise} resolved when the project is done setting up
     */
    function _setProjectRoot(rootEntry) {
        var d = new $.Deferred();
        model.setProjectRoot(rootEntry).then(function () {
            d.resolve();
            model.reopenNodes(PreferencesManager.getViewState("project.treeState", _getProjectViewStateContext()));
        });
        return d.promise();
    }

    /**
     * @private
     *
     * Saves the project path.
     */
    var _saveProjectPath = function () {
        // save the current project
        PreferencesManager.setViewState("projectPath", model.projectRoot.fullPath);
    };

    /**
     * @private
     * Save tree state.
     */
    _saveTreeState = function () {
        var context = _getProjectViewStateContext(),
            openNodes = model.getOpenNodes();

        // Store the open nodes by their full path and persist to storage
        PreferencesManager.setViewState("project.treeState", openNodes, context);
    };

    /**
     * @private
     *
     * Displays an error dialog for problems when working with files in the file tree.
     *
     * @param {number} errType type of error that occurred
     * @param {boolean} isFolder did the error occur because of a folder operation?
     * @param {string} error message with detail about the error
     * @param {string} path path to file or folder that had the error
     * @return {Dialog|null} Dialog if the error message was created
     */
    _showErrorDialog = function (errType, isFolder, error, path) {
        var titleType = isFolder ? Strings.DIRECTORY_TITLE : Strings.FILE_TITLE,
            entryType = isFolder ? Strings.DIRECTORY : Strings.FILE,
            title,
            message;
        path = StringUtils.breakableUrl(path);

        switch (errType) {
        case ERR_TYPE_CREATE:
            title = StringUtils.format(Strings.ERROR_CREATING_FILE_TITLE, titleType);
            message = StringUtils.format(Strings.ERROR_CREATING_FILE, entryType, path, error);
            break;
        case ERR_TYPE_CREATE_EXISTS:
            title = StringUtils.format(Strings.INVALID_FILENAME_TITLE, titleType);
            message = StringUtils.format(Strings.ENTRY_WITH_SAME_NAME_EXISTS, path);
            break;
        case ERR_TYPE_RENAME:
            title = StringUtils.format(Strings.ERROR_RENAMING_FILE_TITLE, titleType);
            message = StringUtils.format(Strings.ERROR_RENAMING_FILE, path, error, entryType);
            break;
        case ERR_TYPE_DELETE:
            title = StringUtils.format(Strings.ERROR_DELETING_FILE_TITLE, titleType);
            message = StringUtils.format(Strings.ERROR_DELETING_FILE, path, error, entryType);
            break;
        case ERR_TYPE_LOADING_PROJECT:
            title = Strings.ERROR_LOADING_PROJECT;
            message = StringUtils.format(Strings.READ_DIRECTORY_ENTRIES_ERROR, path, error);
            break;
        case ERR_TYPE_LOADING_PROJECT_NATIVE:
            title = Strings.ERROR_LOADING_PROJECT;
            message = StringUtils.format(Strings.REQUEST_NATIVE_FILE_SYSTEM_ERROR, path, error);
            break;
        case ERR_TYPE_MAX_FILES:
            title = Strings.ERROR_MAX_FILES_TITLE;
            message = Strings.ERROR_MAX_FILES;
            break;
        case ERR_TYPE_OPEN_DIALOG:
            title = Strings.ERROR_LOADING_PROJECT;
            message = StringUtils.format(Strings.OPEN_DIALOG_ERROR, error);
            break;
        case ERR_TYPE_INVALID_FILENAME:
            title = StringUtils.format(Strings.INVALID_FILENAME_TITLE, isFolder ? Strings.DIRECTORY_NAME : Strings.FILENAME);
            message = StringUtils.format(Strings.INVALID_FILENAME_MESSAGE, isFolder ? Strings.DIRECTORY_NAMES_LEDE : Strings.FILENAMES_LEDE, error);
            break;
        }

        if (title && message) {
            return Dialogs.showModalDialog(
                DefaultDialogs.DIALOG_ID_ERROR,
                title,
                message
            );
        }
        return null;
    };

    /**
     * @deprecated Use LanguageManager.getLanguageForPath(fullPath).isBinary()
     * Returns true if fileName's extension doesn't belong to binary (e.g. archived)
     * @param {string} fileName
     * @return {boolean}
     */
    function isBinaryFile(fileName) {
        DeprecationWarning.deprecationWarning("ProjectManager.isBinaryFile() called for " + fileName + ". Use LanguageManager.getLanguageForPath(fileName).isBinary() instead.");
        return LanguageManager.getLanguageForPath(fileName).isBinary();
    }

    /**
     * @private
     *
     * Rerender the file tree view.
     *
     * @param {boolean} forceRender Force the tree to rerender. Should only be needed by extensions that call rerenderTree.
     */
    _renderTree = function (forceRender) {
        var projectRoot = getProjectRoot();
        if (!projectRoot) {
            return;
        }
        FileTreeView.render($projectTreeContainer[0], model._viewModel, projectRoot, actionCreator, forceRender);
    };

    /**
     * @private
     *
     * Returns the full path to the welcome project, which we open on first launch.
     *
     * @param {string} sampleUrl URL for getting started project
     * @param {string} initialPath Path to Brackets directory (see FileUtils.getNativeBracketsDirectoryPath())
     * @return {!string} fullPath reference
     */
    function _getWelcomeProjectPath() {
        return ProjectModel._getWelcomeProjectPath(Urls.GETTING_STARTED, FileUtils.getNativeBracketsDirectoryPath());
    }

    /**
     * Adds the path to the list of welcome projects we've ever seen, if not on the list already.
     *
     * @param {string} path Path to possibly add
     */
    function addWelcomeProjectPath(path) {
        var welcomeProjects = ProjectModel._addWelcomeProjectPath(path,
                                                                 PreferencesManager.getViewState("welcomeProjects"));
        PreferencesManager.setViewState("welcomeProjects", welcomeProjects);
    }

    /**
     * Returns true if the given path is the same as one of the welcome projects we've previously opened,
     * or the one for the current build.
     *
     * @param {string} path Path to check to see if it's a welcome project path
     * @return {boolean} true if this is a welcome project path
     */
    function isWelcomeProjectPath(path) {
        return ProjectModel._isWelcomeProjectPath(path, _getWelcomeProjectPath(), PreferencesManager.getViewState("welcomeProjects"));
    }

    /**
     * If the provided path is to an old welcome project, returns the current one instead.
     */
    function updateWelcomeProjectPath(path) {
        if (isWelcomeProjectPath(path)) {
            return _getWelcomeProjectPath();
        } else {
            return path;
        }
    }

    /**
     * Initial project path is stored in prefs, which defaults to the welcome project on
     * first launch.
     */
    function getInitialProjectPath() {
        return updateWelcomeProjectPath(PreferencesManager.getViewState("projectPath"));
    }

    /**
     * @private
     *
     * Watches the project for filesystem changes so that the tree can be updated.
     */
    function _watchProjectRoot(rootPath) {
        FileSystem.on("change", _fileSystemChange);
        FileSystem.on("rename", _fileSystemRename);

        FileSystem.watch(FileSystem.getDirectoryForPath(rootPath), ProjectModel._shouldShowName, function (err) {
            if (err === FileSystemError.TOO_MANY_ENTRIES) {
                if (!_projectWarnedForTooManyFiles) {
                    _showErrorDialog(ERR_TYPE_MAX_FILES);
                    _projectWarnedForTooManyFiles = true;
                }
            } else if (err) {
                console.error("Error watching project root: ", rootPath, err);
            }
        });

        // Reset allFiles cache
        model._resetCache();
    }

    /**
     * @private
     * Close the file system and remove listeners.
     * @return {$.Promise} A promise that's resolved when the root is unwatched. Rejected if
     *     there is no project root or if the unwatch fails.
     */
    function _unwatchProjectRoot() {
        var result = new $.Deferred();
        if (!model.projectRoot) {
            result.reject();
        } else {
            FileSystem.off("change", _fileSystemChange);
            FileSystem.off("rename", _fileSystemRename);

            FileSystem.unwatch(model.projectRoot, function (err) {
                if (err) {
                    console.error("Error unwatching project root: ", model.projectRoot.fullPath, err);
                    result.reject(err);
                } else {
                    result.resolve();
                }
            });

            // Reset allFiles cache
            model._resetCache();
        }

        return result.promise();
    }

    /**
     * @private
     * Reloads the project preferences.
     */
    function _reloadProjectPreferencesScope() {
        var root = getProjectRoot();
        if (root) {
            // Alias the "project" Scope to the path Scope for the project-level settings file
            PreferencesManager._setProjectSettingsFile(root.fullPath + SETTINGS_FILENAME);
        } else {
            PreferencesManager._setProjectSettingsFile();
        }
    }

    /**
     * Loads the given folder as a project. Does NOT prompt about any unsaved changes - use openProject()
     * instead to check for unsaved changes and (optionally) let the user choose the folder to open.
     *
     * @param {!string} rootPath  Absolute path to the root folder of the project.
     *  A trailing "/" on the path is optional (unlike many Brackets APIs that assume a trailing "/").
     * @param {boolean=} isUpdating  If true, indicates we're just updating the tree;
     *  if false, a different project is being loaded.
     * @return {$.Promise} A promise object that will be resolved when the
     *  project is loaded and tree is rendered, or rejected if the project path
     *  fails to load.
     */
    function _loadProject(rootPath, isUpdating) {
        var result = new $.Deferred(),
            startLoad = new $.Deferred();

        // Some legacy code calls this API with a non-canonical path
        rootPath = ProjectModel._ensureTrailingSlash(rootPath);

        if (isUpdating) {
            // We're just refreshing. Don't need to unwatch the project root, so we can start loading immediately.
            startLoad.resolve();
        } else {
            if (model.projectRoot && model.projectRoot.fullPath === rootPath) {
                return (new $.Deferred()).resolve().promise();
            }

            // About to close current project (if any)
            if (model.projectRoot) {
                $(exports).triggerHandler("beforeProjectClose", model.projectRoot);
            }

            // close all the old files
            MainViewManager._closeAll(MainViewManager.ALL_PANES);

            _unwatchProjectRoot().always(function () {
                // Done closing old project (if any)
                if (model.projectRoot) {
                    LanguageManager._resetPathLanguageOverrides();
                    PreferencesManager._reloadUserPrefs(model.projectRoot);
                    $(exports).triggerHandler("projectClose", model.projectRoot);
                }

                startLoad.resolve();
            });
        }

        startLoad.done(function () {
            var context = { location : { scope: "user",
                                         layer: "project" } };

            // Clear project path map
            if (!isUpdating) {
                PreferencesManager._stateProjectLayer.setProjectPath(rootPath);
            }

            // Populate file tree as long as we aren't running in the browser
            if (!brackets.inBrowser) {
                if (!isUpdating) {
                    _watchProjectRoot(rootPath);
                }
                // Point at a real folder structure on local disk
                var rootEntry = FileSystem.getDirectoryForPath(rootPath);
                rootEntry.exists(function (err, exists) {
                    if (exists) {
                        var projectRootChanged = (!model.projectRoot || !rootEntry) ||
                            model.projectRoot.fullPath !== rootEntry.fullPath;

                        // Success!
                        var perfTimerName = PerfUtils.markStart("Load Project: " + rootPath);

                        _projectWarnedForTooManyFiles = false;

                        _setProjectRoot(rootEntry).always(function () {
                            model.setBaseUrl(PreferencesManager.getViewState("project.baseUrl", context) || "");

                            if (projectRootChanged) {
                                _reloadProjectPreferencesScope();
                                PreferencesManager._setCurrentEditingFile(rootPath);
                            }

                            // If this is the most current welcome project, record it. In future launches, we want
                            // to substitute the latest welcome project from the current build instead of using an
                            // outdated one (when loading recent projects or the last opened project).
                            if (rootPath === _getWelcomeProjectPath()) {
                                addWelcomeProjectPath(rootPath);
                            }

                            if (projectRootChanged) {
                                // Allow asynchronous event handlers to finish before resolving result by collecting promises from them
                                var promises = [];
                                $(exports).triggerHandler({ type: "projectOpen", promises: promises }, [model.projectRoot]);
                                $.when.apply($, promises).then(result.resolve, result.reject);
                            } else {
                                $(exports).triggerHandler("projectRefresh", model.projectRoot);
                                result.resolve();
                            }
                            PerfUtils.addMeasurement(perfTimerName);
                        });
                    } else {
                        console.log("error loading project");
                        _showErrorDialog(ERR_TYPE_LOADING_PROJECT_NATIVE, null, rootPath, err || FileSystemError.NOT_FOUND)
                            .done(function () {
                                // Reset _projectRoot to null so that the following _loadProject call won't
                                // run the 'beforeProjectClose' event a second time on the original project,
                                // which is now partially torn down (see #6574).
                                model.projectRoot = null;

                                // The project folder stored in preference doesn't exist, so load the default
                                // project directory.
                                // TODO (issue #267): When Brackets supports having no project directory
                                // defined this code will need to change
                                _loadProject(_getWelcomeProjectPath()).always(function () {
                                    // Make sure not to reject the original deferred until the fallback
                                    // project is loaded, so we don't violate expectations that there is always
                                    // a current project before continuing after _loadProject().
                                    result.reject();
                                });
                            });
                    }
                });
            }
        });

        return result.promise();
    }

    /**
     * @private
     * @type {?$.Promise} Resolves when the currently running instance of
     *      _refreshFileTreeInternal completes, or null if there is no currently
     *      running instance.
     */
    var _refreshFileTreePromise = null;

    /**
     * @type {boolean} If refreshFileTree is called before _refreshFileTreePromise
     *      has resolved then _refreshPending is set, which indicates that
     *      refreshFileTree should be called again once the promise resolves.
     */
    var _refreshPending = false;

    /**
     * @const
     * @private
     * @type {number} Minimum delay in milliseconds between calls to refreshFileTree
     */
    var _refreshDelay = 1000;

    /**
     * Refresh the project's file tree, maintaining the current selection.
     *
     * @return {$.Promise} A promise object that will be resolved when the
     *  project tree is reloaded, or rejected if the project path
     *  fails to reload. If the previous selected entry is not found,
     *  the promise is still resolved.
     */
    function refreshFileTree() {
        if (!_refreshFileTreePromise) {
            var internalRefreshPromise  = model.refresh(),
                deferred                = new $.Deferred();

            _refreshFileTreePromise = deferred.promise();

            _refreshFileTreePromise.always(function () {
                _refreshFileTreePromise = null;

                if (_refreshPending) {
                    _refreshPending = false;
                    refreshFileTree();
                }
            });

            // Wait at least one second before resolving the promise
            window.setTimeout(function () {
                internalRefreshPromise.then(deferred.resolve, deferred.reject);
            }, _refreshDelay);
        } else {
            _refreshPending = true;
        }

        return _refreshFileTreePromise;
    }

    /**
     * Expands tree nodes to show the given file or folder and selects it. Silently no-ops if the
     * path lies outside the project, or if it doesn't exist.
     *
     * @param {!(File|Directory)} entry File or Directory to show
     * @return {$.Promise} Resolved when done; or rejected if not found
     */
    function showInTree(entry) {
        return model.showInTree(entry);
    }

    /**
     * Open a new project. Currently, Brackets must always have a project open, so
     * this method handles both closing the current project and opening a new project.
     *
     * @param {string=} path Optional absolute path to the root folder of the project.
     *  If path is undefined or null, displays a dialog where the user can choose a
     *  folder to load. If the user cancels the dialog, nothing more happens.
     * @return {$.Promise} A promise object that will be resolved when the
     *  project is loaded and tree is rendered, or rejected if the project path
     *  fails to load.
     */
    function openProject(path) {

        var result = new $.Deferred();

        // Confirm any unsaved changes first. We run the command in "prompt-only" mode, meaning it won't
        // actually close any documents even on success; we'll do that manually after the user also oks
        // the folder-browse dialog.
        CommandManager.execute(Commands.FILE_CLOSE_ALL, { promptOnly: true })
            .done(function () {
                if (path) {
                    // use specified path
                    _loadProject(path, false).then(result.resolve, result.reject);
                } else {
                    // Pop up a folder browse dialog
                    FileSystem.showOpenDialog(false, true, Strings.CHOOSE_FOLDER, model.projectRoot.fullPath, null, function (err, files) {
                        if (!err) {
                            // If length == 0, user canceled the dialog; length should never be > 1
                            if (files.length > 0) {
                                // Load the new project into the folder tree
                                _loadProject(files[0]).then(result.resolve, result.reject);
                            } else {
                                result.reject();
                            }
                        } else {
                            _showErrorDialog(ERR_TYPE_OPEN_DIALOG, null, err);
                            result.reject();
                        }
                    });
                }
            })
            .fail(function () {
                result.reject();
            });

        // if fail, don't open new project: user canceled (or we failed to save its unsaved changes)
        return result.promise();
    }

    /**
     * Invoke project settings dialog.
     * @return {$.Promise}
     */
    function _projectSettings() {
        return PreferencesDialogs.showProjectPreferencesDialog(getBaseUrl()).getPromise();
    }

    /**
     * Create a new item in the current project.
     *
     * @param baseDir {string|Directory} Full path of the directory where the item should go.
     *   Defaults to the project root if the entry is not valid or not within the project.
     * @param initialName {string} Initial name for the item
     * @param skipRename {boolean} If true, don't allow the user to rename the item
     * @param isFolder {boolean} If true, create a folder instead of a file
     * @return {$.Promise} A promise object that will be resolved with the File
     *  of the created object, or rejected if the user cancelled or entered an illegal
     *  filename.
     */
    function createNewItem(baseDir, initialName, skipRename, isFolder) {
        if (typeof baseDir === "string") {
            if (_.last(baseDir) !== "/") {
                baseDir += "/";
            }
        } else {
            baseDir = baseDir.fullPath;
        }

        if (skipRename) {
            return model.createAtPath(baseDir + initialName, isFolder);
        }
        return actionCreator.startCreating(baseDir, initialName, isFolder);
    }

    /**
     * Delete file or directore from project
     * @param {!(File|Directory)} entry File or Directory to delete
     */
    function deleteItem(entry) {
        var result = new $.Deferred();

        entry.moveToTrash(function (err) {
            if (!err) {
                DocumentManager.notifyPathDeleted(entry.fullPath);
                result.resolve();
            } else {
                _showErrorDialog(ERR_TYPE_DELETE, entry.isDirectory, FileUtils.getFileErrorString(err), entry.fullPath);

                result.reject(err);
            }
        });

        return result.promise();
    }

    /**
     * Returns a filter for use with getAllFiles() that filters files based on LanguageManager language id
     * @param {!(string|Array.<string>)} languageId a single string of a language id or an array of language ids
     * @return {!function(File):boolean}
     */
    function getLanguageFilter(languageId) {
        return function languageFilter(file) {
            var id = LanguageManager.getLanguageForPath(file.fullPath).getId();
            if (typeof languageId === "string") {
                return (id === languageId);
            } else {
                return (languageId.indexOf(id) !== -1);
            }
        };
    }

    /**
     * @private
     *
     * Respond to a FileSystem change event. Note that if renames are initiated
     * externally, they may be reported as a separate removal and addition. In
     * this case, the editor state isn't currently preserved.
     *
     * @param {$.Event} event
     * @param {?(File|Directory)} entry File or Directory changed
     * @param {Array.<FileSystemEntry>=} added If entry is a Directory, contains zero or more added children
     * @param {Array.<FileSystemEntry>=} removed If entry is a Directory, contains zero or more removed children
     */
    _fileSystemChange = function (event, entry, added, removed) {
        FileSyncManager.syncOpenDocuments();

        model.handleFSEvent(entry, added, removed);
    };

    /**
     * @private
     * Respond to a FileSystem rename event.
     */
    _fileSystemRename = function (event, oldName, newName) {
        // Tell the document manager about the name change. This will update
        // all of the model information and send notification to all views
        DocumentManager.notifyPathNameChanged(oldName, newName);
    };

    /**
     * Causes the rename operation that's in progress to complete.
     */
    function forceFinishRename() {
        actionCreator.performRename();
    }

    /**
     * @private
     *
     * Sets the width of the selection bar in the file tree.
     *
     * @param {int} width New width value
     */
    function _setFileTreeSelectionWidth(width) {
        model.setSelectionWidth(width);
        _renderTree();
    }

    /**
     * @private
     *
     * Updates the scroller positioning on scroll or sidebar changes.
     */
    function _updateScrollerInfo() {
        model.setScrollerInfo($projectTreeContainer.scrollTop(), $projectTreeContainer.scrollLeft(), $projectTreeContainer.offset().top);
    }

    // Initialize variables and listeners that depend on the HTML DOM
    AppInit.htmlReady(function () {
        $projectTreeContainer = $("#project-files-container");
        $projectTreeContainer.addClass("jstree jstree-brackets");
        $projectTreeContainer.css("overflow", "auto");
        $projectTreeContainer.css("position", "relative");

        model.setSelectionWidth($projectTreeContainer.width());

        $(".main-view").click(function (jqEvent) {
            if (jqEvent.target.className !== "rename-input") {
                forceFinishRename();
                actionCreator.setContext(null);
            }
        });

        $(Menus.getContextMenu(Menus.ContextMenuIds.PROJECT_MENU)).on("beforeContextMenuOpen", function () {
            actionCreator.restoreContext();
        });

        $(Menus.getContextMenu(Menus.ContextMenuIds.PROJECT_MENU)).on("beforeContextMenuClose", function () {
            actionCreator.setContext(null);
        });

        $projectTreeContainer.on("contextmenu", function () {
            forceFinishRename();
        });

        $projectTreeContainer.on("scroll", function () {
            // Close open menus on scroll and clear the context, but only if there's a menu open.
            if ($(".dropdown.open").length > 0) {
                Menus.closeAll();
                actionCreator.setContext(null);
            }
            _updateScrollerInfo();
        });

        _renderTree();
    });

    /**
     * @private
     * Examine each preference key for migration of project tree states.
     * If the key has a prefix of "projectTreeState_/", then it is a project tree states
     * preference from old preference model.
     *
     * @param {string} key The key of the preference to be examined
     *      for migration of project tree states.
     * @return {?string} - the scope to which the preference is to be migrated
     */
    function _checkPreferencePrefix(key) {
        var pathPrefix = "projectTreeState_",
            projectPath;
        if (key.indexOf(pathPrefix) === 0) {
            // Get the project path from the old preference key by stripping "projectTreeState_".
            projectPath = key.substr(pathPrefix.length);
            return "user project.treeState " + projectPath;
        }

        pathPrefix = "projectBaseUrl_";
        if (key.indexOf(pathPrefix) === 0) {
            // Get the project path from the old preference key by stripping "projectBaseUrl_[Directory "
            // and "]".
            projectPath = key.substr(key.indexOf(" ") + 1);
            projectPath = projectPath.substr(0, projectPath.length - 1);
            return "user project.baseUrl " + projectPath;
        }

        return null;
    }

    // Init default project path to welcome project
    PreferencesManager.stateManager.definePreference("projectPath", "string", _getWelcomeProjectPath());

    PreferencesManager.convertPreferences(module, {
        "projectPath": "user",
        "projectTreeState_": "user",
        "welcomeProjects": "user",
        "projectBaseUrl_": "user"
    }, true, _checkPreferencePrefix);

    $(exports).on("projectOpen", _reloadProjectPreferencesScope);
    $(exports).on("projectOpen", _saveProjectPath);

    // Event Handlers
    $(FileViewController).on("documentSelectionFocusChange", _documentSelectionFocusChange);
    $(FileViewController).on("fileViewFocusChange", _fileViewControllerChange);
    $(MainViewManager).on("currentFileChange", _currentFileChange);
    $(exports).on("beforeAppClose", _unwatchProjectRoot);

    // Commands
    CommandManager.register(Strings.CMD_OPEN_FOLDER,      Commands.FILE_OPEN_FOLDER,      openProject);
    CommandManager.register(Strings.CMD_PROJECT_SETTINGS, Commands.FILE_PROJECT_SETTINGS, _projectSettings);
    CommandManager.register(Strings.CMD_FILE_REFRESH,     Commands.FILE_REFRESH,          refreshFileTree);

    // Define the preference to decide how to sort the Project Tree files
    PreferencesManager.definePreference(SORT_DIRECTORIES_FIRST, "boolean", brackets.platform !== "mac")
        .on("change", function () {
            actionCreator.setSortDirectoriesFirst(PreferencesManager.get(SORT_DIRECTORIES_FIRST));
        });

    actionCreator.setSortDirectoriesFirst(PreferencesManager.get(SORT_DIRECTORIES_FIRST));

    /**
     * Gets the filesystem object for the current context in the file tree.
     */
    function getContext() {
        return model.getContext();
    }

    /**
     * Starts a rename operation, completing the current operation if there is one.
     *
     * The Promise returned is resolved with an object with a `newPath` property with the renamed path. If the user cancels the operation, the promise is resolved with the value RENAME_CANCELLED.
     *
     * @param {FileSystemEntry} entry file or directory filesystem object to rename
     * @return {$.Promise} a promise resolved when the rename is done.
     */
    function renameItemInline(entry) {
        return actionCreator.startRename(entry);
    }

    /**
     * Returns an Array of all files for this project, optionally including
     * files in the working set that are *not* under the project root. Files are
     * filtered out by ProjectModel.shouldShow().
     *
     * @param {function (File, number):boolean=} filter Optional function to filter
     *          the file list (does not filter directory traversal). API matches Array.filter().
     * @param {boolean=} includeWorkingSet If true, include files in the working set
     *          that are not under the project root (*except* for untitled documents).
     *
     * @return {$.Promise} Promise that is resolved with an Array of File objects.
     */
    function getAllFiles(filter, includeWorkingSet) {
        // The filter and includeWorkingSet params are both optional.
        // Handle the case where filter is omitted but includeWorkingSet is
        // specified.
        if (includeWorkingSet === undefined && typeof (filter) !== "function") {
            includeWorkingSet = filter;
            filter = null;
        }

        var viewFiles;
        if (includeWorkingSet) {
            viewFiles = MainViewManager.getWorkingSet(MainViewManager.ALL_PANES);
        }

        return model.getAllFiles(filter, viewFiles).fail(function (err) {
            if (err === FileSystemError.TOO_MANY_ENTRIES && !_projectWarnedForTooManyFiles) {
                _showErrorDialog(ERR_TYPE_MAX_FILES);
                _projectWarnedForTooManyFiles = true;
            }
            return err;
        });
    }

    /**
     * Adds an icon provider. The icon provider is a function which takes a data object and
     * returns a React.DOM.ins instance, a string, a DOM node or a jQuery instance
     * for the icons within the tree.
     *
     * The data object contains:
     *
     * * `name`: the file or directory name
     * * `fullPath`: full path to the file or directory
     * * `isFile`: true if it's a file, false if it's a directory
     */
    function addIconProvider(callback) {
        return FileTreeView.addIconProvider(callback);
    }

    /**
     * Adds an additional classes provider which can return classes that should be added to a
     * given file or directory in the tree.
     *
     * The data object contains:
     *
     * * `name`: the file or directory name
     * * `fullPath`: full path to the file or directory
     * * `isFile`: true if it's a file, false if it's a directory
     */
    function addClassesProvider(callback) {
        return FileTreeView.addClassesProvider(callback);
    }

    /**
     * Forces the file tree to rerender. Typically, the tree only rerenders the portions of the
     * tree that have changed data. If an extension that augments the tree has changes that it
     * needs to display, calling rerenderTree will cause the components for the whole tree to
     * be rerendered.
     */
    function rerenderTree() {
        _renderTree(true);
    }

    // Private API helpful in testing
    exports._actionCreator                 = actionCreator;

    // Private API for use with SidebarView
    exports._setFileTreeSelectionWidth    = _setFileTreeSelectionWidth;

    // Define public API
    exports.getProjectRoot                = getProjectRoot;
    exports.getBaseUrl                    = getBaseUrl;
    exports.setBaseUrl                    = setBaseUrl;
    exports.isWithinProject               = isWithinProject;
    exports.makeProjectRelativeIfPossible = makeProjectRelativeIfPossible;
    exports.shouldShow                    = ProjectModel.shouldShow;
    exports.isBinaryFile                  = isBinaryFile;
    exports.openProject                   = openProject;
    exports.getSelectedItem               = getSelectedItem;
    exports.getContext                    = getContext;
    exports.getInitialProjectPath         = getInitialProjectPath;
    exports.isWelcomeProjectPath          = isWelcomeProjectPath;
    exports.updateWelcomeProjectPath      = updateWelcomeProjectPath;
    exports.createNewItem                 = createNewItem;
    exports.renameItemInline              = renameItemInline;
    exports.deleteItem                    = deleteItem;
    exports.forceFinishRename             = forceFinishRename;
    exports.showInTree                    = showInTree;
    exports.refreshFileTree               = refreshFileTree;
    exports.getAllFiles                   = getAllFiles;
    exports.getLanguageFilter             = getLanguageFilter;
    exports.addIconProvider               = addIconProvider;
    exports.addClassesProvider            = addClassesProvider;
    exports.rerenderTree                  = rerenderTree;
});


<!doctype html>

	<meta charset="utf-8">
	<title>jQuery UI Droppable Test Suite</title>

	<link rel="stylesheet" href="../../../external/qunit.css">

jQuery UI Droppable Test Suite

Draggable

Droppable


/* Indonesian initialisation for the jQuery UI date picker plugin. */
/* Written by Deden Fathurahman (dedenf@gmail.com). */
jQuery(function($){
	$.datepicker.regional['id'] = {
		closeText: 'Tutup',
		prevText: '<mundur',
		nextText: 'maju>',
		currentText: 'hari ini',
		monthNames: ['Januari','Februari','Maret','April','Mei','Juni',
		'Juli','Agustus','September','Oktober','Nopember','Desember'],
		monthNamesShort: ['Jan','Feb','Mar','Apr','Mei','Jun',
		'Jul','Agus','Sep','Okt','Nop','Des'],
		dayNames: ['Minggu','Senin','Selasa','Rabu','Kamis','Jumat','Sabtu'],
		dayNamesShort: ['Min','Sen','Sel','Rab','kam','Jum','Sab'],
		dayNamesMin: ['Mg','Sn','Sl','Rb','Km','jm','Sb'],
		weekHeader: 'Mg',
		dateFormat: 'dd/mm/yy',
		firstDay: 0,
		isRTL: false,
		showMonthAfterYear: false,
		yearSuffix: ''};
	$.datepicker.setDefaults($.datepicker.regional['id']);
});


module Jasmine
  module Core
    VERSION = "1.3.1"
  end
end


// Initialize variables and listeners that depend on the HTML DOM

keep keep keep keep replace keep keep keep


$(function() {

var duration = 1000,
	wait = 500;

function effect( elem, name, options ) {
	$.extend( options, {
		easing: "easeOutQuint"
	});

	$( elem ).click(function() {
		$( this )
			.addClass( "current" )
			// delaying the initial animation makes sure that the queue stays in tact
			.delay( 10 )
			.hide( name, options, duration )
			.delay( wait )
			.show( name, options, duration, function() {
				$( this ).removeClass( "current" );
			});
	});
}

$( "#hide" ).click(function() {
	$( this )
		.addClass( "current" )
		.hide( duration )
		.delay( wait )
		.show( duration, function() {
			$( this ).removeClass( "current" );
		});
});

effect( "#blindLeft", "blind", { direction: "left" } );
effect( "#blindUp", "blind", { direction: "up" } );
effect( "#blindRight", "blind", { direction: "right" } );
effect( "#blindDown", "blind", { direction: "down" } );

effect( "#bounce3times", "bounce", { times: 3 } );

effect( "#clipHorizontally", "clip", { direction: "horizontal" } );
effect( "#clipVertically", "clip", { direction: "vertical" } );

effect( "#dropDown", "drop", { direction: "down" } );
effect( "#dropUp", "drop", { direction: "up" } );
effect( "#dropLeft", "drop", { direction: "left" } );
effect( "#dropRight", "drop", { direction: "right" } );

effect( "#explode9", "explode", {} );
effect( "#explode36", "explode", { pieces: 36 } );

effect( "#fade", "fade", {} );

effect( "#fold", "fold", { size: 50 } );

effect( "#highlight", "highlight", {} );

effect( "#pulsate", "pulsate", { times: 2 } );

effect( "#puff", "puff", { times: 2 } );
effect( "#scale", "scale", {} );
effect( "#size", "size", {} );
$( "#sizeToggle" ).click(function() {
	var options = { to: { width: 300, height: 300 } };
	$( this )
		.addClass( "current" )
		.toggle( "size", options, duration )
		.delay( wait )
		.toggle( "size", options, duration, function() {
			$( this ).removeClass( "current" );
		});
});

$( "#shake" ).click(function() {
	$( this )
		.addClass( "current" )
		.effect( "shake", {}, 100, function() {
			$( this ).removeClass( "current" );
		});
});

effect( "#slideDown", "slide", { direction: "down" } );
effect( "#slideUp", "slide", { direction: "up" } );
effect( "#slideLeft", "slide", { direction: "left" } );
effect( "#slideRight", "slide", { direction: "right" } );

$( "#transfer" ).click(function() {
	$( this )
		.addClass( "current" )
		.effect( "transfer", { to: "div:eq(0)" }, 1000, function() {
			$( this ).removeClass( "current" );
		});
});

$( "#addClass" ).click(function() {
	$( this ).addClass( "current", duration, function() {
		$( this ).removeClass( "current" );
	});
});
$( "#removeClass" ).click(function() {
	$( this ).addClass( "current" ).removeClass( "current", duration );
});
$( "#toggleClass" ).click(function() {
	$( this ).toggleClass( "current", duration );
});

});


<!doctype html>

	<meta charset="utf-8">
	<title>Compound Visual Test : All Widgets in Dialog</title>
	<link rel="stylesheet" href="../visual.css">
	<link rel="stylesheet" href="../../../themes/base/jquery.ui.all.css">

Accordion Header 1

			Accordion Content 1

Accordion Header 2

			Accordion Content 2

Accordion Header 3

			Accordion Content 3

	<input id="autocomplete">
	<button>A Button</button>
	<input id="datepicker">
	<button>Another button</button>

		Progress: <input title="The progress we made so far" id="progress">

First

Second

Third

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

Phasellus mattis tincidunt nibh.

Nam dui erat, auctor a, dignissim quis, sollicitudin eu, felis. Pellentesque nisi urna, interdum eget, sagittis et, consequat vestibulum, lacus. Mauris porttitor ullamcorper augue.

	Yay, another dialog.