// Urho3D material editor

Window@ materialWindow;
Material@ editMaterial;
XMLFile@ oldMaterialState;

void CreateMaterialEditor()
{
    if (materialWindow !is null)
        return;

    materialWindow = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorMaterialWindow.xml"));
    ui.root.AddChild(materialWindow);
    materialWindow.opacity = uiMaxOpacity;

    RefreshMaterialEditor();

    int height = Min(ui.root.height - 60, 500);
    materialWindow.SetSize(300, height);
    CenterDialog(materialWindow);

    HideMaterialEditor();

    SubscribeToEvent(materialWindow.GetChild("NewButton", true), "Released", "NewMaterial");
    SubscribeToEvent(materialWindow.GetChild("RevertButton", true), "Released", "RevertMaterial");
    SubscribeToEvent(materialWindow.GetChild("SaveButton", true), "Released", "SaveMaterial");
    SubscribeToEvent(materialWindow.GetChild("SaveAsButton", true), "Released", "SaveMaterialAs");
    SubscribeToEvent(materialWindow.GetChild("CloseButton", true), "Released", "HideMaterialEditor");
    SubscribeToEvent(materialWindow.GetChild("NewParameterDropDown", true), "ItemSelected", "CreateShaderParameter");
    SubscribeToEvent(materialWindow.GetChild("DeleteParameterButton", true), "Released", "DeleteShaderParameter");
}

bool ShowMaterialEditor()
{
    materialWindow.visible = true;
    materialWindow.BringToFront();
    return true;
}

void HideMaterialEditor()
{
    materialWindow.visible = false;
}

void EditMaterial(Material@ mat)
{
    editMaterial = mat;
    RefreshMaterialEditor();
    ShowMaterialEditor();
}

void RefreshMaterialEditor()
{
    RefreshMaterialName();
    RefreshMaterialTechniques();
    RefreshMaterialTextures();
    RefreshMaterialShaderParameters();
}

void RefreshMaterialName()
{
    UIElement@ container = materialWindow.GetChild("NameContainer");
    container.RemoveAllChildren();

    LineEdit@ nameEdit = CreateAttributeLineEdit(container, null, 0, 0);
    if (editMaterial !is null)
        nameEdit.text = editMaterial.name;
    SubscribeToEvent(nameEdit, "TextFinished", "EditMaterialName");

    Button@ pickButton = CreateResourcePickerButton(container, null, 0, 0, "Pick");
    SubscribeToEvent(pickButton, "Released", "PickEditMaterial");
}

void RefreshMaterialTechniques()
{
    ListView@ list = materialWindow.GetChild("TechniqueList");
    list.RemoveAllItems();
}

void RefreshMaterialTextures(bool fullUpdate = true)
{
    if (fullUpdate)
    {
        ListView@ list = materialWindow.GetChild("TextureList");
        list.RemoveAllItems();

        for (uint i = 0; i < MAX_MATERIAL_TEXTURE_UNITS; ++i)
        {
            UIElement@ parent = CreateAttributeEditorParentWithSeparatedLabel(list, GetTextureUnitName(TextureUnit(i)), i, 0, false);

            UIElement@ container = UIElement();
            container.SetLayout(LM_HORIZONTAL, 4, IntRect(10, 0, 4, 0));
            container.SetFixedHeight(ATTR_HEIGHT);
            parent.AddChild(container);

            LineEdit@ nameEdit = CreateAttributeLineEdit(container, null, i, 0);
            nameEdit.name = "TextureNameEdit" + String(i);

            Button@ pickButton = CreateResourcePickerButton(container, null, i, 0, "Pick");
            SubscribeToEvent(pickButton, "Released", "PickMaterialTexture");
            Button@ openButton = CreateResourcePickerButton(container, null, i, 0, "Open");
            SubscribeToEvent(openButton, "Released", "OpenResource");

            if (editMaterial !is null)
            {
                Texture@ texture = editMaterial.textures[i];
                if (texture !is null)
                    nameEdit.text = texture.name;
            }

            SubscribeToEvent(nameEdit, "TextFinished", "EditMaterialTexture");
        }
    }
    else
    {
        for (uint i = 0; i < MAX_MATERIAL_TEXTURE_UNITS; ++i)
        {
            LineEdit@ nameEdit = materialWindow.GetChild("TextureNameEdit" + String(i), true);
            if (nameEdit is null)
                continue;

            String textureName;
            if (editMaterial !is null)
            {
                Texture@ texture = editMaterial.textures[i];
                if (texture !is null)
                    textureName = texture.name;
            }

            nameEdit.text = textureName;
        }
    }
}

void RefreshMaterialShaderParameters()
{
    ListView@ list = materialWindow.GetChild("ShaderParameterList");
    list.RemoveAllItems();
    if (editMaterial is null)
        return;

    Array<String>@ parameterNames = editMaterial.shaderParameterNames;

    for (uint i = 0; i < parameterNames.length; ++i)
    {
        VariantType type = editMaterial.shaderParameters[parameterNames[i]].type;
        Variant value = editMaterial.shaderParameters[parameterNames[i]];
        UIElement@ parent = CreateAttributeEditorParent(list, parameterNames[i], 0, 0);
        uint numCoords = type - VAR_FLOAT + 1;

        Array<String> coordValues = value.ToString().Split(' ');

        for (uint j = 0; j < numCoords; ++j)
        {
            LineEdit@ attrEdit = CreateAttributeLineEdit(parent, null, 0, 0);
            attrEdit.vars["Coordinate"] = j;
            attrEdit.vars["Name"] = parameterNames[i];
            attrEdit.text = coordValues[j];
            SubscribeToEvent(attrEdit, "TextChanged", "EditShaderParameter");
            SubscribeToEvent(attrEdit, "TextFinished", "EditShaderParameter");
        }
    }
}

void EditMaterialName(StringHash eventType, VariantMap& eventData)
{
    LineEdit@ nameEdit = eventData["Element"].GetUIElement();
    String newMaterialName = nameEdit.text.Trimmed();
    if (!newMaterialName.empty)
    {
        Material@ newMaterial = cache.GetResource("Material", newMaterialName);
        if (newMaterial !is null)
            EditMaterial(newMaterial);
    }
}

void PickEditMaterial()
{
    @resourcePicker = GetResourcePicker(ShortStringHash("Material"));
    if (resourcePicker is null)
        return;

    String lastPath = resourcePicker.lastPath;
    if (lastPath.empty)
        lastPath = sceneResourcePath;
    CreateFileSelector("Pick " + resourcePicker.typeName, "OK", "Cancel", lastPath, resourcePicker.filters, resourcePicker.lastFilter);
    SubscribeToEvent(uiFileSelector, "FileSelected", "PickEditMaterialDone");
}

void PickEditMaterialDone(StringHash eventType, VariantMap& eventData)
{
    StoreResourcePickerPath();
    CloseFileSelector();

    if (!eventData["OK"].GetBool())
    {
        @resourcePicker = null;
        return;
    }

    String resourceName = eventData["FileName"].GetString();
    Resource@ res = GetPickedResource(resourceName);

    if (res !is null)
        EditMaterial(cast<Material>(res));

    @resourcePicker = null;
}

void NewMaterial()
{
    EditMaterial(Material());
}

void RevertMaterial()
{
    if (editMaterial is null)
        return;

    cache.ReloadResource(editMaterial);
    RefreshMaterialEditor();
}

void SaveMaterial()
{
    if (editMaterial is null || editMaterial.name.empty)
        return;

    String fullName = cache.GetResourceFileName(editMaterial.name);
    if (fullName.empty)
        return;

    File saveFile(fullName, FILE_WRITE);
    editMaterial.Save(saveFile);
}

void SaveMaterialAs()
{
    if (editMaterial is null)
        return;

    @resourcePicker = GetResourcePicker(ShortStringHash("Material"));
    if (resourcePicker is null)
        return;

    String lastPath = resourcePicker.lastPath;
    if (lastPath.empty)
        lastPath = sceneResourcePath;
    CreateFileSelector("Save material as", "Save", "Cancel", lastPath, resourcePicker.filters, resourcePicker.lastFilter);
    SubscribeToEvent(uiFileSelector, "FileSelected", "SaveMaterialAsDone");
}

void SaveMaterialAsDone(StringHash eventType, VariantMap& eventData)
{
    StoreResourcePickerPath();
    CloseFileSelector();
    @resourcePicker = null;

    if (editMaterial is null)
        return;

    if (!eventData["OK"].GetBool())
    {
        @resourcePicker = null;
        return;
    }

    String fullName = eventData["FileName"].GetString();
    File saveFile(fullName, FILE_WRITE);
    if (editMaterial.Save(saveFile))
    {
        saveFile.Close();

        // Load the new resource to update the name in the editor
        Material@ newMat = cache.GetResource("Material", GetResourceNameFromFullName(fullName));
        if (newMat !is null)
            EditMaterial(newMat);
    }
}

void EditShaderParameter(StringHash eventType, VariantMap& eventData)
{
    if (editMaterial is null)
        return;

    LineEdit@ attrEdit = eventData["Element"].GetUIElement();
    uint coordinate = attrEdit.vars["Coordinate"].GetUInt();

    String name = attrEdit.vars["Name"].GetString();

    Variant oldValue = editMaterial.shaderParameters[name];
    Array<String> coordValues = oldValue.ToString().Split(' ');
    coordValues[coordinate] = String(attrEdit.text.ToFloat());

    String valueString;
    for (uint i = 0; i < coordValues.length; ++i)
    {
        valueString += coordValues[i];
        valueString += " ";
    }

    Variant newValue;
    newValue.FromString(oldValue.type, valueString);

    BeginMaterialEdit();
    editMaterial.shaderParameters[name] = newValue;
    EndMaterialEdit();
}

void CreateShaderParameter(StringHash eventType, VariantMap& eventData)
{
    if (editMaterial is null)
        return;

    LineEdit@ nameEdit = materialWindow.GetChild("ParameterNameEdit", true);
    String newName = nameEdit.text.Trimmed();
    if (newName.empty)
        return;

    DropDownList@ dropDown = eventData["Element"].GetUIElement();
    Variant newValue;

    switch (dropDown.selection)
    {
    case 0:
        newValue = float(0);
        break;
    case 1:
        newValue = Vector2(0, 0);
        break;
    case 2:
        newValue = Vector3(0, 0, 0);
        break;
    case 3:
        newValue = Vector4(0, 0, 0, 0);
        break;
    }

    BeginMaterialEdit();
    editMaterial.shaderParameters[newName] = newValue;
    EndMaterialEdit();

    RefreshMaterialShaderParameters();
}

void DeleteShaderParameter()
{
    if (editMaterial is null)
        return;

    LineEdit@ nameEdit = materialWindow.GetChild("ParameterNameEdit", true);
    String name = nameEdit.text.Trimmed();
    if (name.empty)
        return;

    BeginMaterialEdit();
    editMaterial.RemoveShaderParameter(name);
    EndMaterialEdit();

    RefreshMaterialShaderParameters();
}

void PickMaterialTexture(StringHash eventType, VariantMap& eventData)
{
    if (editMaterial is null)
        return;

    UIElement@ button = eventData["Element"].GetUIElement();
    resourcePickIndex = button.vars["Index"].GetUInt();

    @resourcePicker = GetResourcePicker(ShortStringHash("Texture2D"));
    if (resourcePicker is null)
        return;

    String lastPath = resourcePicker.lastPath;
    if (lastPath.empty)
        lastPath = sceneResourcePath;
    CreateFileSelector("Pick " + resourcePicker.typeName, "OK", "Cancel", lastPath, resourcePicker.filters, resourcePicker.lastFilter);
    SubscribeToEvent(uiFileSelector, "FileSelected", "PickMaterialTextureDone");
}

void PickMaterialTextureDone(StringHash eventType, VariantMap& eventData)
{
    StoreResourcePickerPath();
    CloseFileSelector();

    if (!eventData["OK"].GetBool())
    {
        @resourcePicker = null;
        return;
    }

    String resourceName = eventData["FileName"].GetString();
    Resource@ res = GetPickedResource(resourceName);

    if (res !is null && editMaterial !is null)
    {
        BeginMaterialEdit();
        editMaterial.textures[resourcePickIndex] = res;
        EndMaterialEdit();

        RefreshMaterialTextures(false);
    }

    @resourcePicker = null;
}

void EditMaterialTexture(StringHash eventType, VariantMap& eventData)
{
    if (editMaterial is null)
        return;

    LineEdit@ attrEdit = eventData["Element"].GetUIElement();
    String textureName = attrEdit.text.Trimmed();
    uint index = attrEdit.vars["Index"].GetUInt();

    BeginMaterialEdit();

    if (!textureName.empty)
    {
        Texture@ texture = cache.GetResource(GetExtension(textureName) == ".xml" ? "TextureCube" : "Texture2D", textureName);
        editMaterial.textures[index] = texture;
    }
    else
        editMaterial.textures[index] =null;

    EndMaterialEdit();

}

void BeginMaterialEdit()
{
    if (editMaterial is null)
        return;

    oldMaterialState = XMLFile();
    XMLElement materialElem = oldMaterialState.CreateRoot("material");
    editMaterial.Save(materialElem);
}

void EndMaterialEdit()
{
    if (editMaterial is null)
        return;

    EditMaterialAction@ action = EditMaterialAction();
    action.Define(editMaterial, oldMaterialState);
    SaveEditAction(action);
}

// Urho3D material editor

Window@ materialWindow;
Material@ editMaterial;
XMLFile@ oldMaterialState;

void CreateMaterialEditor()
{
    if (materialWindow !is null)
        return;

    materialWindow = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorMaterialWindow.xml"));
    ui.root.AddChild(materialWindow);
    materialWindow.opacity = uiMaxOpacity;

    RefreshMaterialEditor();

    int height = Min(ui.root.height - 60, 500);
    materialWindow.SetSize(300, height);
    CenterDialog(materialWindow);

    HideMaterialEditor();

    SubscribeToEvent(materialWindow.GetChild("NewButton", true), "Released", "NewMaterial");
    SubscribeToEvent(materialWindow.GetChild("RevertButton", true), "Released", "RevertMaterial");
    SubscribeToEvent(materialWindow.GetChild("SaveButton", true), "Released", "SaveMaterial");
    SubscribeToEvent(materialWindow.GetChild("SaveAsButton", true), "Released", "SaveMaterialAs");
    SubscribeToEvent(materialWindow.GetChild("CloseButton", true), "Released", "HideMaterialEditor");
    SubscribeToEvent(materialWindow.GetChild("NewParameterDropDown", true), "ItemSelected", "CreateShaderParameter");
    SubscribeToEvent(materialWindow.GetChild("DeleteParameterButton", true), "Released", "DeleteShaderParameter");
}

bool ShowMaterialEditor()
{
    materialWindow.visible = true;
    materialWindow.BringToFront();
    return true;
}

void HideMaterialEditor()
{
    materialWindow.visible = false;
}

void EditMaterial(Material@ mat)
{
    editMaterial = mat;
    RefreshMaterialEditor();
    ShowMaterialEditor();
}

void RefreshMaterialEditor()
{
    RefreshMaterialName();
    RefreshMaterialTechniques();
    RefreshMaterialTextures();
    RefreshMaterialShaderParameters();
}

void RefreshMaterialName()
{
    UIElement@ container = materialWindow.GetChild("NameContainer");
    container.RemoveAllChildren();

    LineEdit@ nameEdit = CreateAttributeLineEdit(container, null, 0, 0);
    if (editMaterial !is null)
        nameEdit.text = editMaterial.name;
    SubscribeToEvent(nameEdit, "TextFinished", "EditMaterialName");

    Button@ pickButton = CreateResourcePickerButton(container, null, 0, 0, "Pick");
    SubscribeToEvent(pickButton, "Released", "PickEditMaterial");
}

void RefreshMaterialTechniques()
{
    ListView@ list = materialWindow.GetChild("TechniqueList");
    list.RemoveAllItems();
}

void RefreshMaterialTextures(bool fullUpdate = true)
{
    if (fullUpdate)
    {
        ListView@ list = materialWindow.GetChild("TextureList");
        list.RemoveAllItems();

        for (uint i = 0; i < MAX_MATERIAL_TEXTURE_UNITS; ++i)
        {
            UIElement@ parent = CreateAttributeEditorParentWithSeparatedLabel(list, GetTextureUnitName(TextureUnit(i)), i, 0, false);

            UIElement@ container = UIElement();
            container.SetLayout(LM_HORIZONTAL, 4, IntRect(10, 0, 4, 0));
            container.SetFixedHeight(ATTR_HEIGHT);
            parent.AddChild(container);

            LineEdit@ nameEdit = CreateAttributeLineEdit(container, null, i, 0);
            nameEdit.name = "TextureNameEdit" + String(i);

            Button@ pickButton = CreateResourcePickerButton(container, null, i, 0, "Pick");
            SubscribeToEvent(pickButton, "Released", "PickMaterialTexture");
            Button@ openButton = CreateResourcePickerButton(container, null, i, 0, "Open");
            SubscribeToEvent(openButton, "Released", "OpenResource");

            if (editMaterial !is null)
            {
                Texture@ texture = editMaterial.textures[i];
                if (texture !is null)
                    nameEdit.text = texture.name;
            }

            SubscribeToEvent(nameEdit, "TextFinished", "EditMaterialTexture");
        }
    }
    else
    {
        for (uint i = 0; i < MAX_MATERIAL_TEXTURE_UNITS; ++i)
        {
            LineEdit@ nameEdit = materialWindow.GetChild("TextureNameEdit" + String(i), true);
            if (nameEdit is null)
                continue;

            String textureName;
            if (editMaterial !is null)
            {
                Texture@ texture = editMaterial.textures[i];
                if (texture !is null)
                    textureName = texture.name;
            }

            nameEdit.text = textureName;
        }
    }
}

void RefreshMaterialShaderParameters()
{
    ListView@ list = materialWindow.GetChild("ShaderParameterList");
    list.RemoveAllItems();
    if (editMaterial is null)
        return;

    Array<String>@ parameterNames = editMaterial.shaderParameterNames;

    for (uint i = 0; i < parameterNames.length; ++i)
    {
        VariantType type = editMaterial.shaderParameters[parameterNames[i]].type;
        Variant value = editMaterial.shaderParameters[parameterNames[i]];
        UIElement@ parent = CreateAttributeEditorParent(list, parameterNames[i], 0, 0);
        uint numCoords = type - VAR_FLOAT + 1;

        Array<String> coordValues = value.ToString().Split(' ');

        for (uint j = 0; j < numCoords; ++j)
        {
            LineEdit@ attrEdit = CreateAttributeLineEdit(parent, null, 0, 0);
            attrEdit.vars["Coordinate"] = j;
            attrEdit.vars["Name"] = parameterNames[i];
            attrEdit.text = coordValues[j];
            SubscribeToEvent(attrEdit, "TextChanged", "EditShaderParameter");
            SubscribeToEvent(attrEdit, "TextFinished", "EditShaderParameter");
        }
    }
}

void EditMaterialName(StringHash eventType, VariantMap& eventData)
{
    LineEdit@ nameEdit = eventData["Element"].GetUIElement();
    String newMaterialName = nameEdit.text.Trimmed();
    if (!newMaterialName.empty)
    {
        Material@ newMaterial = cache.GetResource("Material", newMaterialName);
        if (newMaterial !is null)
            EditMaterial(newMaterial);
    }
}

void PickEditMaterial()
{
    @resourcePicker = GetResourcePicker(ShortStringHash("Material"));
    if (resourcePicker is null)
        return;

    String lastPath = resourcePicker.lastPath;
    if (lastPath.empty)
        lastPath = sceneResourcePath;
    CreateFileSelector("Pick " + resourcePicker.typeName, "OK", "Cancel", lastPath, resourcePicker.filters, resourcePicker.lastFilter);
    SubscribeToEvent(uiFileSelector, "FileSelected", "PickEditMaterialDone");
}

void PickEditMaterialDone(StringHash eventType, VariantMap& eventData)
{
    StoreResourcePickerPath();
    CloseFileSelector();

    if (!eventData["OK"].GetBool())
    {
        @resourcePicker = null;
        return;
    }

    String resourceName = eventData["FileName"].GetString();
    Resource@ res = GetPickedResource(resourceName);

    if (res !is null)
        EditMaterial(cast<Material>(res));

    @resourcePicker = null;
}

void NewMaterial()
{
    EditMaterial(Material());
}

void RevertMaterial()
{
    if (editMaterial is null)
        return;

    BeginMaterialEdit();
    cache.ReloadResource(editMaterial);
    EndMaterialEdit();

    RefreshMaterialEditor();
}

void SaveMaterial()
{
    if (editMaterial is null || editMaterial.name.empty)
        return;

    String fullName = cache.GetResourceFileName(editMaterial.name);
    if (fullName.empty)
        return;

    File saveFile(fullName, FILE_WRITE);
    editMaterial.Save(saveFile);
}

void SaveMaterialAs()
{
    if (editMaterial is null)
        return;

    @resourcePicker = GetResourcePicker(ShortStringHash("Material"));
    if (resourcePicker is null)
        return;

    String lastPath = resourcePicker.lastPath;
    if (lastPath.empty)
        lastPath = sceneResourcePath;
    CreateFileSelector("Save material as", "Save", "Cancel", lastPath, resourcePicker.filters, resourcePicker.lastFilter);
    SubscribeToEvent(uiFileSelector, "FileSelected", "SaveMaterialAsDone");
}

void SaveMaterialAsDone(StringHash eventType, VariantMap& eventData)
{
    StoreResourcePickerPath();
    CloseFileSelector();
    @resourcePicker = null;

    if (editMaterial is null)
        return;

    if (!eventData["OK"].GetBool())
    {
        @resourcePicker = null;
        return;
    }

    String fullName = eventData["FileName"].GetString();
    File saveFile(fullName, FILE_WRITE);
    if (editMaterial.Save(saveFile))
    {
        saveFile.Close();

        // Load the new resource to update the name in the editor
        Material@ newMat = cache.GetResource("Material", GetResourceNameFromFullName(fullName));
        if (newMat !is null)
            EditMaterial(newMat);
    }
}

void EditShaderParameter(StringHash eventType, VariantMap& eventData)
{
    if (editMaterial is null)
        return;

    LineEdit@ attrEdit = eventData["Element"].GetUIElement();
    uint coordinate = attrEdit.vars["Coordinate"].GetUInt();

    String name = attrEdit.vars["Name"].GetString();

    Variant oldValue = editMaterial.shaderParameters[name];
    Array<String> coordValues = oldValue.ToString().Split(' ');
    coordValues[coordinate] = String(attrEdit.text.ToFloat());

    String valueString;
    for (uint i = 0; i < coordValues.length; ++i)
    {
        valueString += coordValues[i];
        valueString += " ";
    }

    Variant newValue;
    newValue.FromString(oldValue.type, valueString);

    BeginMaterialEdit();
    editMaterial.shaderParameters[name] = newValue;
    EndMaterialEdit();
}

void CreateShaderParameter(StringHash eventType, VariantMap& eventData)
{
    if (editMaterial is null)
        return;

    LineEdit@ nameEdit = materialWindow.GetChild("ParameterNameEdit", true);
    String newName = nameEdit.text.Trimmed();
    if (newName.empty)
        return;

    DropDownList@ dropDown = eventData["Element"].GetUIElement();
    Variant newValue;

    switch (dropDown.selection)
    {
    case 0:
        newValue = float(0);
        break;
    case 1:
        newValue = Vector2(0, 0);
        break;
    case 2:
        newValue = Vector3(0, 0, 0);
        break;
    case 3:
        newValue = Vector4(0, 0, 0, 0);
        break;
    }

    BeginMaterialEdit();
    editMaterial.shaderParameters[newName] = newValue;
    EndMaterialEdit();

    RefreshMaterialShaderParameters();
}

void DeleteShaderParameter()
{
    if (editMaterial is null)
        return;

    LineEdit@ nameEdit = materialWindow.GetChild("ParameterNameEdit", true);
    String name = nameEdit.text.Trimmed();
    if (name.empty)
        return;

    BeginMaterialEdit();
    editMaterial.RemoveShaderParameter(name);
    EndMaterialEdit();

    RefreshMaterialShaderParameters();
}

void PickMaterialTexture(StringHash eventType, VariantMap& eventData)
{
    if (editMaterial is null)
        return;

    UIElement@ button = eventData["Element"].GetUIElement();
    resourcePickIndex = button.vars["Index"].GetUInt();

    @resourcePicker = GetResourcePicker(ShortStringHash("Texture2D"));
    if (resourcePicker is null)
        return;

    String lastPath = resourcePicker.lastPath;
    if (lastPath.empty)
        lastPath = sceneResourcePath;
    CreateFileSelector("Pick " + resourcePicker.typeName, "OK", "Cancel", lastPath, resourcePicker.filters, resourcePicker.lastFilter);
    SubscribeToEvent(uiFileSelector, "FileSelected", "PickMaterialTextureDone");
}

void PickMaterialTextureDone(StringHash eventType, VariantMap& eventData)
{
    StoreResourcePickerPath();
    CloseFileSelector();

    if (!eventData["OK"].GetBool())
    {
        @resourcePicker = null;
        return;
    }

    String resourceName = eventData["FileName"].GetString();
    Resource@ res = GetPickedResource(resourceName);

    if (res !is null && editMaterial !is null)
    {
        BeginMaterialEdit();
        editMaterial.textures[resourcePickIndex] = res;
        EndMaterialEdit();

        RefreshMaterialTextures(false);
    }

    @resourcePicker = null;
}

void EditMaterialTexture(StringHash eventType, VariantMap& eventData)
{
    if (editMaterial is null)
        return;

    LineEdit@ attrEdit = eventData["Element"].GetUIElement();
    String textureName = attrEdit.text.Trimmed();
    uint index = attrEdit.vars["Index"].GetUInt();

    BeginMaterialEdit();

    if (!textureName.empty)
    {
        Texture@ texture = cache.GetResource(GetExtension(textureName) == ".xml" ? "TextureCube" : "Texture2D", textureName);
        editMaterial.textures[index] = texture;
    }
    else
        editMaterial.textures[index] =null;

    EndMaterialEdit();

}

void BeginMaterialEdit()
{
    if (editMaterial is null)
        return;

    oldMaterialState = XMLFile();
    XMLElement materialElem = oldMaterialState.CreateRoot("material");
    editMaterial.Save(materialElem);
}

void EndMaterialEdit()
{
    if (editMaterial is null)
        return;

    EditMaterialAction@ action = EditMaterialAction();
    action.Define(editMaterial, oldMaterialState);
    SaveEditAction(action);
}

Make the material revert action undoable.


m4_pattern_allow([^URBI_(PATH|SERVER)$])         -*- shell-script -*-
URBI_INIT

# The full path to the *.uob dir:
# ../../../../../sdk-remote/src/tests/uobjects/access-and-change/uaccess.uob
uob=$(absolute $1.uob)
test -d "$uob" ||
  error OSFILE "argument is not a directory: $uob"

# The ending part, for our builddir: access-and-change/uaccess.dir.
builddir=$(echo "$uob" |
           sed -e 's,.*/src/tests/uobjects/,uobjects/,;s/\.uob$//').dir

# For error messages.
me=$(basename "$uob" .uob)

# The directory we work in.
rm -rf $builddir
mkdir -p $builddir
cd $builddir

# Find urbi-launch.
urbi_launch=$(xfind_prog urbi-launch)

# The remote component: an executable.
umake_remote=$(xfind_prog "umake-remote")
xrun "umake-remote" $umake_remote --output=$me $uob
test -x "$me" ||
  fatal "$me is not executable"
xrun "$me --version" "./$me" --version

# The shared component: a dlopen module.
umake_shared=$(xfind_prog "umake-shared")
xrun "umake-shared" $umake_shared --output=$me $uob
test -f "$me.la" ||
  fatal "$me.la does not exist"
xrun "urbi-launch $me --version" "$urbi_launch" --start $me -- --version

m4_pattern_allow([^URBI_(PATH|SERVER)$])         -*- shell-script -*-
URBI_INIT

# The full path to the *.uob dir:
# ../../../../../sdk-remote/src/tests/uobjects/access-and-change/uaccess.uob
uob=$(absolute $1.uob)
test -d "$uob" ||
  error OSFILE "argument is not a directory: $uob"

# The ending part, for our builddir: access-and-change/uaccess.dir.
builddir=$(echo "$uob" |
           sed -e 's,.*/src/tests/uobjects/,uobjects/,;s/\.uob$//').dir

# For error messages.
me=$(basename "$uob" .uob)

# The directory we work in.
rm -rf $builddir
mkdir -p $builddir
cd $builddir

# Find urbi-launch.
urbi_launch=$(xfind_prog urbi-launch)

# The remote component: an executable.
umake_remote=$(xfind_prog "umake-remote")
xrun "umake-remote" $umake_remote --output=$me $uob
test -x "$me" ||
  fatal "$me is not executable"
xrun "$me --version" "./$me" --version

# The shared component: a dlopen module.
umake_shared=$(xfind_prog "umake-shared")
xrun "umake-shared" $umake_shared --output=$me $uob
test -f "$me.la" ||
  fatal "$me.la does not exist"
xrun "urbi-launch $me --version" "$urbi_launch" --start $me.la -- --version

Add ".la" extension to urbi-launch while testing.

	* src/tests/bin/uobject-check.as: Give the full name of the dynamic
	library.


package aerys.minko.scene.node.group
{
	import aerys.minko.scene.action.IAction;
	import aerys.minko.scene.action.group.GroupAction;
	import aerys.minko.scene.node.AbstractScene;
	import aerys.minko.scene.node.IScene;
	import aerys.minko.scene.node.ISearchableScene;
	import aerys.minko.scene.visitor.ISceneVisitor;
	import aerys.minko.scene.visitor.RenderingVisitor;

	import flash.utils.Proxy;
	import flash.utils.flash_proxy;
	import flash.utils.getQualifiedClassName;

	/**
	 * The Group3D provides a basic support for scene building.
	 * A Group3D can contain any object implementing the IScene3D interface.
	 *
	 * Group3D objects do not affect their children with any specific behaviour.
	 *
	 * @author Jean-Marc Le Roux
	 */
	public dynamic class Group extends Proxy implements IGroup
	{
		private static var _id		: uint				= 0;

		private var _name			: String			= null;

		private var _children		: Vector.<IScene>	= null;
		private var _numChildren	: int				= 0;

		private var _actions		: Vector.<IAction>	= Vector.<IAction>([GroupAction.groupAction]);

		public function get actions()	: Vector.<IAction>	{ return _actions; }
		public function get name()		: String			{ return _name; }

		public function set name(value : String) : void
		{
			_name = value;
		}

		protected function get rawChildren()	: Vector.<IScene> 	{ return _children; }

		protected function set rawChildren(value : Vector.<IScene>) : void
		{
			_children = value;
			_numChildren = _children.length;
		}

		/**
		 * The number of children.
		 */
		public function get numChildren() : uint
		{
			return _numChildren;
		}

		public function Group(...children)
		{
			super();

			_name = AbstractScene.getDefaultSceneName(this);

			initialize(children);
		}

		private function initialize(children : Array) : void
		{
			while (children.length == 1 && children[0] is Array)
				children = children[0];

			_numChildren = children.length;
			_children = _numChildren ? Vector.<IScene>(children)
									 : new Vector.<IScene>();
		}

		public function contains(scene : IScene) : Boolean
		{
			return getChildIndex(scene) >= 0;
		}

		public function getChildIndex(child : IScene) : int
		{
			for (var i : int = 0; i < _numChildren; i++)
				if (_children[i] === child)
					return i;

			return -1;
		}

		public function getChildByName(name : String) : IScene
		{
			for (var i : int = 0; i < _numChildren; i++)
				if (_children[i].name === name)
					return _children[i];

			return null;
		}

		/**
		 * Add a child to the container.
		 *
		 * @param	scene The child to add.
		 */
		public function addChild(scene : IScene) : IGroup
		{
			if (!scene)
				throw new Error();

			_children.push(scene);
			++_numChildren;

			//scene.added(this);

			return this;
		}

		public function addChildAt(scene : IScene, position : uint) : IGroup
		{
			if (!scene)
				throw new Error();

			var numChildren : int = _children.length;

			if (position >= numChildren)
				return addChild(scene);

			for (var i : int = numChildren; i > position; --i)
				_children[i] = _children[int(i - 1)];
			_children[position] = scene;

			++_numChildren;

			return this;
		}

		/**
		 * Remove a child from the container.
		 *
		 * @param	myChild The child to remove.
		 * @return Whether the child was actually removed or not.
		 */
		public function removeChild(child : IScene) : IGroup
		{
			var numChildren : int = _children.length;
			var i : int	= 0;

			while (i < numChildren && _children[i] !== child)
				++i;

			if (i >= numChildren)
				return null;

			return removeChildAt(i);
		}

		public function removeChildAt(position : uint) : IGroup
		{
			var removed 	: IScene 	= null;

			if (position < _numChildren)
			{
				removed = _children[position];

				while (position < _numChildren - 1)
					_children[position] = _children[int(++position)];
				_children.length = --_numChildren;
			}

			return this;
		}

		public function removeAllChildren() : IGroup
		{
			_children.length = 0;
			_numChildren = 0;

			return this;
		}

		public function getChildAt(position : uint) : IScene
		{
			return position < _numChildren ? _children[position] : null;
		}

		public function swapChildren(child1	: IScene,
									 child2	: IScene) : IGroup
		{
			var id1	: int 	= getChildIndex(child1);
			var id2 : int	= getChildIndex(child2);

			if (id1 == -1 || id2 == -1)
				return this;

			var tmp : IScene = _children[id2];

			_children[id2] = _children[id1];
			_children[id1] = tmp;

			return this;
		}

		public function getDescendantByName(name : String) : IScene
		{
			var descendant 	: IScene 	= getChildByName(name);
			var numChildren	: int 		= numChildren;

			for (var i : int = 0; i < numChildren && !descendant; ++i)
			{
				var searchable : ISearchableScene = _children[i] as ISearchableScene;

				if (searchable)
					descendant = searchable.getDescendantByName(name);
			}

			return descendant;
		}

		public function toString() : String
		{
			return "[" + getQualifiedClassName(this) + " " + name + "]";
		}

		override flash_proxy function getProperty(name : *) : *
		{
			var index : int = parseInt(name);

			return index == name ? getChildAt(index) : getChildByName(name as String);
		}

		override flash_proxy function setProperty(name : *, value : *) : void
		{
			var index : int = parseInt(name);

			if (index == name)
			{
				if (index < numChildren)
				{
					removeChildAt(index);
					addChildAt(value, index);
				}
			}
			else
			{
				var old : IScene = getChildByName(name);

				addChild(value);

				if (old)
				{
					swapChildren(value, old);
					_children.length = _children.length - 1;
				}
			}
		}

		override flash_proxy function getDescendants(name : *) : *
		{
			return getDescendantByName(name);
		}

		override flash_proxy function nextNameIndex(index : int) : int
		{
			return index < numChildren ? index + 1 : 0;
		}

		override flash_proxy function nextName(index : int) : String
		{
			return String(index - 1);
		}

		override flash_proxy function nextValue(index : int) : *
		{
			return _children[int(index - 1)];
		}
	}
}

package aerys.minko.scene.node.group
{
	import aerys.minko.scene.action.IAction;
	import aerys.minko.scene.action.group.GroupAction;
	import aerys.minko.scene.node.AbstractScene;
	import aerys.minko.scene.node.IScene;
	import aerys.minko.scene.node.ISearchableScene;
	import aerys.minko.scene.visitor.ISceneVisitor;
	import aerys.minko.scene.visitor.RenderingVisitor;

	import flash.utils.Proxy;
	import flash.utils.flash_proxy;
	import flash.utils.getQualifiedClassName;

	/**
	 * The Group3D provides a basic support for scene building.
	 * A Group3D can contain any object implementing the IScene3D interface.
	 *
	 * Group3D objects do not affect their children with any specific behaviour.
	 *
	 * @author Jean-Marc Le Roux
	 */
	public dynamic class Group extends Proxy implements IGroup
	{
		private static var _id		: uint				= 0;

		private var _name			: String			= null;

		private var _children		: Vector.<IScene>	= null;
		private var _numChildren	: int				= 0;

		private var _actions		: Vector.<IAction>	= Vector.<IAction>([GroupAction.groupAction]);

		public function get actions()	: Vector.<IAction>	{ return _actions; }
		public function get name()		: String			{ return _name; }

		public function set name(value : String) : void
		{
			_name = value;
		}

		protected function get rawChildren()	: Vector.<IScene> 	{ return _children; }

		protected function set rawChildren(value : Vector.<IScene>) : void
		{
			_children = value;
			_numChildren = _children.length;
		}

		/**
		 * The number of children.
		 */
		public function get numChildren() : uint
		{
			return _numChildren;
		}

		public function Group(...children)
		{
			super();

			_name = AbstractScene.getDefaultSceneName(this);

			initialize(children);
		}

		private function initialize(children : Array) : void
		{
			while (children.length == 1 && children[0] is Array)
				children = children[0];

			_numChildren = children.length;
			_children = _numChildren ? Vector.<IScene>(children)
									 : new Vector.<IScene>();
		}

		public function contains(scene : IScene) : Boolean
		{
			return getChildIndex(scene) >= 0;
		}

		public function getChildIndex(child : IScene) : int
		{
			for (var i : int = 0; i < _numChildren; i++)
				if (_children[i] === child)
					return i;

			return -1;
		}

		public function getChildByName(name : String) : IScene
		{
			for (var i : int = 0; i < _numChildren; i++)
				if (_children[i].name === name)
					return _children[i];

			return null;
		}

		/**
		 * Add a child to the container.
		 *
		 * @param	scene The child to add.
		 */
		public function addChild(scene : IScene) : IGroup
		{
			if (!scene)
				throw new Error("Parameter child must be non-null.");

			_children.push(scene);
			++_numChildren;

			//scene.added(this);

			return this;
		}

		public function addChildAt(scene : IScene, position : uint) : IGroup
		{
			if (!scene)
				throw new Error("Parameter child must be non-null.");

			var numChildren : int = _children.length;

			if (position >= numChildren)
				return addChild(scene);

			for (var i : int = numChildren; i > position; --i)
				_children[i] = _children[int(i - 1)];
			_children[position] = scene;

			++_numChildren;

			return this;
		}

		/**
		 * Remove a child from the container.
		 *
		 * @param	myChild The child to remove.
		 * @return Whether the child was actually removed or not.
		 */
		public function removeChild(child : IScene) : IGroup
		{
			var numChildren : int = _children.length;
			var i : int	= 0;

			while (i < numChildren && _children[i] !== child)
				++i;

			if (i >= numChildren)
				return null;

			return removeChildAt(i);
		}

		public function removeChildAt(position : uint) : IGroup
		{
			var removed 	: IScene 	= null;

			if (position < _numChildren)
			{
				removed = _children[position];

				while (position < _numChildren - 1)
					_children[position] = _children[int(++position)];
				_children.length = --_numChildren;
			}

			return this;
		}

		public function removeAllChildren() : IGroup
		{
			_children.length = 0;
			_numChildren = 0;

			return this;
		}

		public function getChildAt(position : uint) : IScene
		{
			return position < _numChildren ? _children[position] : null;
		}

		public function swapChildren(child1	: IScene,
									 child2	: IScene) : IGroup
		{
			var id1	: int 	= getChildIndex(child1);
			var id2 : int	= getChildIndex(child2);

			if (id1 == -1 || id2 == -1)
				return this;

			var tmp : IScene = _children[id2];

			_children[id2] = _children[id1];
			_children[id1] = tmp;

			return this;
		}

		public function getDescendantByName(name : String) : IScene
		{
			var descendant 	: IScene 	= getChildByName(name);
			var numChildren	: int 		= numChildren;

			for (var i : int = 0; i < numChildren && !descendant; ++i)
			{
				var searchable : ISearchableScene = _children[i] as ISearchableScene;

				if (searchable)
					descendant = searchable.getDescendantByName(name);
			}

			return descendant;
		}

		public function toString() : String
		{
			return "[" + getQualifiedClassName(this) + " " + name + "]";
		}

		override flash_proxy function getProperty(name : *) : *
		{
			var index : int = parseInt(name);

			return index == name ? getChildAt(index) : getChildByName(name as String);
		}

		override flash_proxy function setProperty(name : *, value : *) : void
		{
			var index : int = parseInt(name);

			if (index == name)
			{
				if (index < numChildren)
				{
					removeChildAt(index);
					addChildAt(value, index);
				}
			}
			else
			{
				var old : IScene = getChildByName(name);

				addChild(value);

				if (old)
				{
					swapChildren(value, old);
					_children.length = _children.length - 1;
				}
			}
		}

		override flash_proxy function getDescendants(name : *) : *
		{
			return getDescendantByName(name);
		}

		override flash_proxy function nextNameIndex(index : int) : int
		{
			return index < numChildren ? index + 1 : 0;
		}

		override flash_proxy function nextName(index : int) : String
		{
			return String(index - 1);
		}

		override flash_proxy function nextValue(index : int) : *
		{
			return _children[int(index - 1)];
		}
	}
}

Add better error message when using addChild().


/*
 * Copyright (C) 2003-2007
 * Nick Velloff <nick.velloff@gmail.com>
 * Derrick Grigg <dgrigg@rogers.com>
 * Sean Voisen <sean@voisen.org>
 * Sean Treadway <seant@oncotype.dk>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

package org.jivesoftware.xiff.data
{

	import org.jivesoftware.xiff.data.ISerializable;
	import org.jivesoftware.xiff.data.XMPPStanza;
	import org.jivesoftware.xiff.data.ExtensionClassRegistry;
	import org.jivesoftware.xiff.data.xhtml.XHTMLExtension;
	import flash.xml.XMLNode;
	import org.jivesoftware.xiff.events.XIFFErrorEvent;

	/**
	 * A class for abstraction and encapsulation of message data.
	 *
	 * @param recipient The JID of the message recipient
	 * @param sender The JID of the message sender - the server should report an error if this is falsified
	 * @param msgID The message ID
	 * @param msgBody The message body in plain-text format
	 * @param msgHTMLBody The message body in XHTML format
	 * @param msgType The message type
	 * @param msgSubject (Optional) The message subject
	 */
	public class Message extends XMPPStanza implements ISerializable
	{

		// Static variables for specific type strings
		public static var NORMAL_TYPE:String = "normal";
		public static var CHAT_TYPE:String = "chat";
		public static var GROUPCHAT_TYPE:String = "groupchat";
		public static var HEADLINE_TYPE:String = "headline";
		public static var ERROR_TYPE:String = "error";

		// Private references to nodes within our XML
		private var myBodyNode:XMLNode;
		private var mySubjectNode:XMLNode;
		private var myThreadNode:XMLNode;
		private var myTimeStampNode:XMLNode;

		private static var isMessageStaticCalled:Boolean = MessageStaticConstructor();
		private static var staticConstructorDependency:Array = [ XMPPStanza, XHTMLExtension, ExtensionClassRegistry ];

		public function Message( recipient:String=null, msgID:String=null, msgBody:String=null, msgHTMLBody:String=null, msgType:String=null, msgSubject:String=null )
		{
			// Flash gives a warning if superconstructor is not first, hence the inline id check
			var msgId:String = exists( msgID ) ? msgID : generateID("m_");
			super( recipient, null, msgType, msgId, "message" );
			body = msgBody;
			htmlBody = msgHTMLBody;
			subject = msgSubject;
		}

		public static function MessageStaticConstructor():Boolean
		{
			XHTMLExtension.enable();
			return true;
		}

		/**
		 * Serializes the Message into XML form for sending to a server.
		 *
		 * @return An indication as to whether serialization was successful
		 */
		override public function serialize( parentNode:XMLNode ):Boolean
		{
			return super.serialize( parentNode );
		}

		/**
		 * Deserializes an XML object and populates the Message instance with its data.
		 *
		 * @param xmlNode The XML to deserialize
		 * @return An indication as to whether deserialization was sucessful
		 */
		override public function deserialize( xmlNode:XMLNode ):Boolean
		{
			var isSerialized:Boolean = super.deserialize( xmlNode );
			if (isSerialized) {
				var children:Array = xmlNode.childNodes;
				for( var i:String in children )
				{
					trace(children[i].nodeName);
					switch( children[i].nodeName )
					{
						// Adding error handler for 404 sent back by server
						case "error":

							break;
						case "body":
							myBodyNode = children[i];
							break;

						case "subject":
							mySubjectNode = children[i];
							break;

						case "thread":
							myThreadNode = children[i];
							break;

						case "x":
							if(children[i].attributes.xmlns == "jabber:x:delay")
								myTimeStampNode = children[i];

							break;
					}
				}
			}
			return isSerialized;
		}

		/**
		 * The message body in plain-text format. If a client cannot render HTML-formatted
		 * text, this text is typically used instead.
		 */
		public function get body():String
		{
			if (!exists(myBodyNode)){
				return null;
			}
			var value: String = '';

			try
			{
				value =  myBodyNode.firstChild.nodeValue;
			}

			catch (error:Error)
			{
				trace (error.getStackTrace());
			}
			return value;
		}

		/**
		 * @private
		 */
		public function set body( bodyText:String ):void
		{
			myBodyNode = replaceTextNode(getNode(), myBodyNode, "body", bodyText);
		}

		/**
		 * The message body in XHTML format. Internally, this uses the XHTML data extension.
		 *
		 * @see org.jivesoftware.xiff.data.xhtml.XHTMLExtension
		 */
		public function get htmlBody():String
		{
			try
			{
				var ext:XHTMLExtension = getAllExtensionsByNS(XHTMLExtension.NS)[0];
				return ext.body;
			}
			catch (e:Error)
			{
				trace("Error : null trapped. Resuming.");
			}
			return null;
		}

		/**
		 * @private
		 */
		public function set htmlBody( bodyHTML:String ):void
		{
			// Removes any existing HTML body text first
	        removeAllExtensions(XHTMLExtension.NS);

	        if (exists(bodyHTML) && bodyHTML.length > 0) {
	            var ext:XHTMLExtension = new XHTMLExtension(getNode());
	            ext.body = bodyHTML;
	            addExtension(ext);
	        }
		}

		/**
		 * The message subject. Typically chat and groupchat-type messages do not use
		 * subjects. Rather, this is reserved for normal and headline-type messages.
		 */
		public function get subject():String
		{
			if (mySubjectNode == null) return null;
			return mySubjectNode.firstChild.nodeValue;
		}

		/**
		 * @private
		 */
		public function set subject( aSubject:String ):void
		{
			mySubjectNode = replaceTextNode(getNode(), mySubjectNode, "subject", aSubject);
		}

		/**
		 * The message thread ID. Threading is used to group messages of the same discussion together.
		 * The library does not perform message grouping, rather it is up to any client authors to
		 * properly perform this task.
		 */
		public function get thread():String
		{
			if (myThreadNode == null) return null;
			return myThreadNode.firstChild.nodeValue;
		}

		/**
		 * @private
		 */
		public function set thread( theThread:String ):void
		{
			myThreadNode = replaceTextNode(getNode(), myThreadNode, "thread", theThread);
		}

		public function set time( theTime:Date ): void
		{

		}

		public function get time():Date
		{
			if(myTimeStampNode == null) return null;
			var stamp:String = myTimeStampNode.attributes.stamp;

			var t:Date = new Date();
			//CCYYMMDDThh:mm:ss
			//20020910T23:41:07
			t.setUTCFullYear(stamp.slice(0, 4)); //2002
			t.setUTCMonth(stamp.slice(4, 6)); //09
			t.setUTCDate(stamp.slice(6, 8)); //10
											 //T
			t.setUTCHours(stamp.slice(9, 11)); //23
												//:
			t.setUTCMinutes(stamp.slice(12, 14)); //41
												//:
			t.setUTCSeconds(stamp.slice(15, 17)); //07
			return t;
		}
	}
}

/*
 * Copyright (C) 2003-2007
 * Nick Velloff <nick.velloff@gmail.com>
 * Derrick Grigg <dgrigg@rogers.com>
 * Sean Voisen <sean@voisen.org>
 * Sean Treadway <seant@oncotype.dk>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

package org.jivesoftware.xiff.data
{

	import org.jivesoftware.xiff.data.ISerializable;
	import org.jivesoftware.xiff.data.XMPPStanza;
	import org.jivesoftware.xiff.data.ExtensionClassRegistry;
	import org.jivesoftware.xiff.data.xhtml.XHTMLExtension;
	import flash.xml.XMLNode;
	import org.jivesoftware.xiff.events.XIFFErrorEvent;

	/**
	 * A class for abstraction and encapsulation of message data.
	 *
	 * @param recipient The JID of the message recipient
	 * @param sender The JID of the message sender - the server should report an error if this is falsified
	 * @param msgID The message ID
	 * @param msgBody The message body in plain-text format
	 * @param msgHTMLBody The message body in XHTML format
	 * @param msgType The message type
	 * @param msgSubject (Optional) The message subject
	 */
	public class Message extends XMPPStanza implements ISerializable
	{

		// Static variables for specific type strings
		public static var NORMAL_TYPE:String = "normal";
		public static var CHAT_TYPE:String = "chat";
		public static var GROUPCHAT_TYPE:String = "groupchat";
		public static var HEADLINE_TYPE:String = "headline";
		public static var ERROR_TYPE:String = "error";

		// Private references to nodes within our XML
		private var myBodyNode:XMLNode;
		private var mySubjectNode:XMLNode;
		private var myThreadNode:XMLNode;
		private var myTimeStampNode:XMLNode;

		private static var isMessageStaticCalled:Boolean = MessageStaticConstructor();
		private static var staticConstructorDependency:Array = [ XMPPStanza, XHTMLExtension, ExtensionClassRegistry ];

		public function Message( recipient:String=null, msgID:String=null, msgBody:String=null, msgHTMLBody:String=null, msgType:String=null, msgSubject:String=null )
		{
			// Flash gives a warning if superconstructor is not first, hence the inline id check
			var msgId:String = exists( msgID ) ? msgID : generateID("m_");
			super( recipient, null, msgType, msgId, "message" );
			body = msgBody;
			htmlBody = msgHTMLBody;
			subject = msgSubject;
		}

		public static function MessageStaticConstructor():Boolean
		{
			XHTMLExtension.enable();
			return true;
		}

		/**
		 * Serializes the Message into XML form for sending to a server.
		 *
		 * @return An indication as to whether serialization was successful
		 */
		override public function serialize( parentNode:XMLNode ):Boolean
		{
			return super.serialize( parentNode );
		}

		/**
		 * Deserializes an XML object and populates the Message instance with its data.
		 *
		 * @param xmlNode The XML to deserialize
		 * @return An indication as to whether deserialization was sucessful
		 */
		override public function deserialize( xmlNode:XMLNode ):Boolean
		{
			var isSerialized:Boolean = super.deserialize( xmlNode );
			if (isSerialized) {
				var children:Array = xmlNode.childNodes;
				for( var i:String in children )
				{
					trace(children[i].nodeName);
					switch( children[i].nodeName )
					{
						// Adding error handler for 404 sent back by server
						case "error":

							break;
						case "body":
							myBodyNode = children[i];
							break;

						case "subject":
							mySubjectNode = children[i];
							break;

						case "thread":
							myThreadNode = children[i];
							break;

						case "x":
							if(children[i].attributes.xmlns == "jabber:x:delay")
								myTimeStampNode = children[i];

							break;
					}
				}
			}
			return isSerialized;
		}

		/**
		 * The message body in plain-text format. If a client cannot render HTML-formatted
		 * text, this text is typically used instead.
		 */
		public function get body():String
		{
			if (!exists(myBodyNode)){
				return null;
			}
			var value: String = '';

			try
			{
				value =  myBodyNode.firstChild.nodeValue;
			}

			catch (error:Error)
			{
				trace (error.getStackTrace());
			}
			return value;
		}

		/**
		 * @private
		 */
		public function set body( bodyText:String ):void
		{
			myBodyNode = replaceTextNode(getNode(), myBodyNode, "body", bodyText);
		}

		/**
		 * The message body in XHTML format. Internally, this uses the XHTML data extension.
		 *
		 * @see org.jivesoftware.xiff.data.xhtml.XHTMLExtension
		 */
		public function get htmlBody():String
		{
			try
			{
				var ext:XHTMLExtension = getAllExtensionsByNS(XHTMLExtension.NS)[0];
				return ext.body;
			}
			catch (e:Error)
			{
				trace("Error : null trapped. Resuming.");
			}
			return null;
		}

		/**
		 * @private
		 */
		public function set htmlBody( bodyHTML:String ):void
		{
			// Removes any existing HTML body text first
	        removeAllExtensions(XHTMLExtension.NS);

	        if (exists(bodyHTML) && bodyHTML.length > 0) {
	            var ext:XHTMLExtension = new XHTMLExtension(getNode());
	            ext.body = bodyHTML;
	            addExtension(ext);
	        }
		}

		/**
		 * The message subject. Typically chat and groupchat-type messages do not use
		 * subjects. Rather, this is reserved for normal and headline-type messages.
		 */
		public function get subject():String
		{
			if (mySubjectNode == null) return null;
			return mySubjectNode.firstChild.nodeValue;
		}

		/**
		 * @private
		 */
		public function set subject( aSubject:String ):void
		{
			mySubjectNode = replaceTextNode(getNode(), mySubjectNode, "subject", aSubject);
		}

		/**
		 * The message thread ID. Threading is used to group messages of the same discussion together.
		 * The library does not perform message grouping, rather it is up to any client authors to
		 * properly perform this task.
		 */
		public function get thread():String
		{
			if (myThreadNode == null) return null;
			return myThreadNode.firstChild.nodeValue;
		}

		/**
		 * @private
		 */
		public function set thread( theThread:String ):void
		{
			myThreadNode = replaceTextNode(getNode(), myThreadNode, "thread", theThread);
		}

		public function set time( theTime:Date ): void
		{

		}

		public function get time():Date
		{
			if(myTimeStampNode == null) return null;
			var stamp:String = myTimeStampNode.attributes.stamp;

			var t:Date = new Date();
			//CCYYMMDDThh:mm:ss
			//20020910T23:41:07
			t.setUTCFullYear(stamp.slice(0, 4)); //2002
			t.setUTCMonth(Number(stamp.slice(4, 6)) - 1); //09
			t.setUTCDate(stamp.slice(6, 8)); //10
											 //T
			t.setUTCHours(stamp.slice(9, 11)); //23
												//:
			t.setUTCMinutes(stamp.slice(12, 14)); //41
												//:
			t.setUTCSeconds(stamp.slice(15, 17)); //07
			return t;
		}
	}
}

Fix an off-by-one error due to the way flex deals with dates. Thanks to Bill Bailey for pointing this out :)


package goplayer
{
  public class StreamioAPI implements MovieEventReporter
  {
    private static const VERSION : String = "/v1"

    private var baseURL : String
    private var http : HTTP
    private var trackerID : String

    public function StreamioAPI
      (baseURL : String, http : HTTP, trackerID : String)
    {
      this.baseURL = baseURL
      this.http = http
      this.trackerID = trackerID
    }

    // -----------------------------------------------------

    public function fetchMovie
      (id : String, handler : MovieHandler) : void
    { fetch(getMoviePath(id), new MovieJSONHandler(handler)) }

    public function reportMovieViewed(movieID : String) : void
    { reportMovieEvent(movieID, "views", {}) }

    public function reportMoviePlayed(movieID : String) : void
    { reportMovieEvent(movieID, "plays", {}) }

    public function reportMovieHeatmapData
      (movieID : String, time : Number) : void
    { reportMovieEvent(movieID, "heat", { time: time }) }

    private function reportMovieEvent
      (movieID : String, event : String, parameters : Object) : void
    {
      if (trackerID != null && trackerID != "")
        post(statsPath, getStatsParameters(movieID, event, parameters))
    }

    private function getStatsParameters
      (movieID : String, event : String, parameters : Object) : Object
    {
      const result : Object = new Object

      result.tracker_id = trackerID
      result.movie_id = movieID
      result.event = event

      for (var name : String in parameters)
        result[name] = parameters[name]

      return result
    }

    // -----------------------------------------------------

    private function getMoviePath(id : String) : String
    { return "/videos/" + id + "/public_show.json" }

    private function get statsPath() : String
    { return "/stats" }

    // -----------------------------------------------------

    private function fetch(path : String, handler : JSONHandler) : void
    { http.fetchJSON(getURL(path), handler) }

    private function post(path : String, parameters : Object) : void
    { http.post(getURL(path), parameters, new NullHTTPHandler) }

    private function getURL(path : String) : URL
    { return URL.parse(baseURL + VERSION + path) }
  }
}

import goplayer.*

class MovieJSONHandler implements JSONHandler
{
  private var handler : MovieHandler

  public function MovieJSONHandler(handler : MovieHandler)
  { this.handler = handler }

  public function handleJSON(json : Object) : void
  { handler.handleMovie(new StreamioMovie(json)) }
}

class NullHTTPHandler implements HTTPResponseHandler
{ public function handleHTTPResponse(text : String) : void {} }

package goplayer
{
  public class StreamioAPI implements MovieEventReporter
  {
    private static const VERSION : String = "/v1"

    private var baseURL : String
    private var http : HTTP
    private var trackerID : String

    public function StreamioAPI
      (baseURL : String, http : HTTP, trackerID : String)
    {
      this.baseURL = baseURL
      this.http = http
      this.trackerID = trackerID
    }

    // -----------------------------------------------------

    public function fetchMovie
      (id : String, handler : MovieHandler) : void
    { fetch(getMoviePath(id), new MovieJSONHandler(handler)) }

    public function reportMovieViewed(movieID : String) : void
    { reportMovieEvent(movieID, "views", {}) }

    public function reportMoviePlayed(movieID : String) : void
    { reportMovieEvent(movieID, "plays", {}) }

    public function reportMovieHeatmapData
      (movieID : String, time : Number) : void
    { reportMovieEvent(movieID, "heat", { time: time }) }

    private function reportMovieEvent
      (movieID : String, event : String, parameters : Object) : void
    {
      if (trackerID != null && trackerID != "")
        post(statsPath, getStatsParameters(movieID, event, parameters))
    }

    private function getStatsParameters
      (movieID : String, event : String, parameters : Object) : Object
    {
      const result : Object = new Object

      result.tracker_id = trackerID
      result.video_id = movieID
      result.event = event

      for (var name : String in parameters)
        result[name] = parameters[name]

      return result
    }

    // -----------------------------------------------------

    private function getMoviePath(id : String) : String
    { return "/videos/" + id + "/public_show.json" }

    private function get statsPath() : String
    { return "/stats" }

    // -----------------------------------------------------

    private function fetch(path : String, handler : JSONHandler) : void
    { http.fetchJSON(getURL(path), handler) }

    private function post(path : String, parameters : Object) : void
    { http.post(getURL(path), parameters, new NullHTTPHandler) }

    private function getURL(path : String) : URL
    { return URL.parse(baseURL + VERSION + path) }
  }
}

import goplayer.*

class MovieJSONHandler implements JSONHandler
{
  private var handler : MovieHandler

  public function MovieJSONHandler(handler : MovieHandler)
  { this.handler = handler }

  public function handleJSON(json : Object) : void
  { handler.handleMovie(new StreamioMovie(json)) }
}

class NullHTTPHandler implements HTTPResponseHandler
{ public function handleHTTPResponse(text : String) : void {} }

Use `video_id' instead of `movie_id' in stats API call.


package ru.kutu.grindplayer.config  {

	import org.osmf.media.MediaFactory;
	import org.osmf.media.MediaPlayer;
	import org.osmf.media.MediaResourceBase;
	import org.osmf.player.configuration.ConfigurationFlashvarsDeserializer;
	import org.osmf.player.configuration.ConfigurationLoader;
	import org.osmf.player.configuration.ConfigurationProxy;
	import org.osmf.player.configuration.ConfigurationXMLDeserializer;

	import robotlegs.bender.extensions.contextView.ContextView;
	import robotlegs.bender.extensions.mediatorMap.api.IMediatorMap;
	import robotlegs.bender.framework.api.ILogger;

	import ru.kutu.grind.config.ConfigurationProxyProvider;
	import ru.kutu.grind.config.GrindConfig;
	import ru.kutu.grind.config.JavaScriptBridgeBase;
	import ru.kutu.grind.config.LocalSettings;
	import ru.kutu.grind.config.PlayerConfiguration;
	import ru.kutu.grind.config.ResourceProvider;
	import ru.kutu.grind.media.GrindMediaFactoryBase;
	import ru.kutu.grind.views.api.IAlternateMenuButton;
	import ru.kutu.grind.views.api.IBufferInfo;
	import ru.kutu.grind.views.api.IControlBarMenuButtonHide;
	import ru.kutu.grind.views.api.IControlBarView;
	import ru.kutu.grind.views.api.IFullScreenButton;
	import ru.kutu.grind.views.api.IFullScreenState;
	import ru.kutu.grind.views.api.IMainView;
	import ru.kutu.grind.views.api.IPlayPauseButton;
	import ru.kutu.grind.views.api.IPlayerView;
	import ru.kutu.grind.views.api.IQualityMenuButton;
	import ru.kutu.grind.views.api.IScrubBar;
	import ru.kutu.grind.views.api.IScrubBarTip;
	import ru.kutu.grind.views.api.IStatInfo;
	import ru.kutu.grind.views.api.ITimeInfo;
	import ru.kutu.grind.views.api.IVolumeComponent;
	import ru.kutu.grind.views.mediators.AlternateMenuBaseMediator;
	import ru.kutu.grind.views.mediators.ControlBarBaseMediator;
	import ru.kutu.grind.views.mediators.ControlBarMenuHideBaseMediator;
	import ru.kutu.grind.views.mediators.FullScreenStateMediator;
	import ru.kutu.grind.views.mediators.FullscreenButtonBaseMediator;
	import ru.kutu.grind.views.mediators.MainViewBaseMediator;
	import ru.kutu.grind.views.mediators.PlayPauseButtonBaseMediator;
	import ru.kutu.grind.views.mediators.QualityMenuBaseMediator;
	import ru.kutu.grind.views.mediators.ScrubBarTipBaseMediator;
	import ru.kutu.grind.views.mediators.TimeInfoBaseMediator;
	import ru.kutu.grind.views.mediators.VolumeComponentBaseMediator;
	import ru.kutu.grindplayer.media.GrindMediaPlayer;
	import ru.kutu.grindplayer.views.components.Subtitles;
	import ru.kutu.grindplayer.views.mediators.AutoHideMediator;
	import ru.kutu.grindplayer.views.mediators.BufferInfoMediator;
	import ru.kutu.grindplayer.views.mediators.PlayerViewMediator;
	import ru.kutu.grindplayer.views.mediators.ScrubBarMediator;
	import ru.kutu.grindplayer.views.mediators.ScrubBarMinimizedMediator;
	import ru.kutu.grindplayer.views.mediators.ShortcutsMediator;
	import ru.kutu.grindplayer.views.mediators.StatInfoMediator;
	import ru.kutu.grindplayer.views.mediators.SubtitlesMediator;
	import ru.kutu.grindplayer.views.mediators.SubtitlesMenuMediator;
	import ru.kutu.grindplayer.views.mediators.api.IScrubBarMinimized;
	import ru.kutu.grindplayer.views.mediators.api.ISubtitlesMenuButton;

	import spark.components.Application;

	public class AppConfig extends GrindConfig {

		[Inject] public var contextView:ContextView;
		[Inject] public var mediatorMap:IMediatorMap;
		[Inject] public var logger:ILogger;

		override public function configure():void {
			super.configure();

			Application(contextView.view).focusManager.deactivate();

			injector.map(LocalSettings).asSingleton();
			injector.map(JavaScriptBridgeBase).toSingleton(JavaScriptBridge);

			mediatorMap.map(IMainView).toMediator(MainViewBaseMediator);

			mediatorMap.map(IPlayerView).toMediator(PlayerViewMediator);
			mediatorMap.map(IPlayerView).toMediator(AutoHideMediator);
			mediatorMap.map(IPlayerView).toMediator(ShortcutsMediator);

			mediatorMap.map(Subtitles).toMediator(SubtitlesMediator);
			mediatorMap.map(IControlBarView).toMediator(ControlBarBaseMediator);
			mediatorMap.map(IBufferInfo).toMediator(BufferInfoMediator);
			mediatorMap.map(IStatInfo).toMediator(StatInfoMediator);

			mediatorMap.map(IScrubBar).toMediator(ScrubBarMediator);
			mediatorMap.map(IScrubBarTip).toMediator(ScrubBarTipBaseMediator);
			mediatorMap.map(IScrubBarMinimized).toMediator(ScrubBarMinimizedMediator);

			mediatorMap.map(IPlayPauseButton).toMediator(PlayPauseButtonBaseMediator);
			mediatorMap.map(IVolumeComponent).toMediator(VolumeComponentBaseMediator);
			mediatorMap.map(ITimeInfo).toMediator(TimeInfoBaseMediator);

			mediatorMap.map(IControlBarMenuButtonHide).toMediator(ControlBarMenuHideBaseMediator);
			mediatorMap.map(ISubtitlesMenuButton).toMediator(SubtitlesMenuMediator);
			mediatorMap.map(IAlternateMenuButton).toMediator(AlternateMenuBaseMediator);
			mediatorMap.map(IQualityMenuButton).toMediator(QualityMenuBaseMediator);

			mediatorMap.map(IFullScreenButton).toMediator(FullscreenButtonBaseMediator);
			mediatorMap.map(IFullScreenState).toMediator(FullScreenStateMediator);
		}

		override protected function configuration():void {
			injector.map(PlayerConfiguration).toSingleton(GrindPlayerConfiguration);
			injector.map(MediaPlayer).toSingleton(GrindMediaPlayer);
			injector.map(MediaResourceBase).toProvider(new ResourceProvider());
			injector.map(MediaFactory).toSingleton(GrindMediaFactoryBase);

			injector.map(ConfigurationProxy).toProvider(new ConfigurationProxyProvider());
			injector.map(ConfigurationFlashvarsDeserializer).toValue(
				new ConfigurationFlashvarsDeserializer(
					injector.getInstance(ConfigurationProxy)
				));
			injector.map(ConfigurationXMLDeserializer).toValue(
				new ConfigurationXMLDeserializer(
					injector.getInstance(ConfigurationProxy)
				));
			injector.map(ConfigurationLoader).toValue(
				new ConfigurationLoader(
					injector.getInstance(ConfigurationFlashvarsDeserializer),
					injector.getInstance(ConfigurationXMLDeserializer)
				));
		}

	}

}

package ru.kutu.grindplayer.config  {

	import org.osmf.media.MediaFactory;
	import org.osmf.media.MediaPlayer;
	import org.osmf.media.MediaResourceBase;
	import org.osmf.player.configuration.ConfigurationFlashvarsDeserializer;
	import org.osmf.player.configuration.ConfigurationLoader;
	import org.osmf.player.configuration.ConfigurationProxy;
	import org.osmf.player.configuration.ConfigurationXMLDeserializer;

	import robotlegs.bender.extensions.contextView.ContextView;
	import robotlegs.bender.extensions.mediatorMap.api.IMediatorMap;
	import robotlegs.bender.framework.api.ILogger;

	import ru.kutu.grind.config.ConfigurationProxyProvider;
	import ru.kutu.grind.config.GrindConfig;
	import ru.kutu.grind.config.JavaScriptBridgeBase;
	import ru.kutu.grind.config.LocalSettings;
	import ru.kutu.grind.config.PlayerConfiguration;
	import ru.kutu.grind.config.ResourceProvider;
	import ru.kutu.grind.media.GrindMediaFactoryBase;
	import ru.kutu.grind.utils.Thumbnails;
	import ru.kutu.grind.views.api.IAlternateMenuButton;
	import ru.kutu.grind.views.api.IBufferInfo;
	import ru.kutu.grind.views.api.IControlBarMenuButtonHide;
	import ru.kutu.grind.views.api.IControlBarView;
	import ru.kutu.grind.views.api.IFullScreenButton;
	import ru.kutu.grind.views.api.IFullScreenState;
	import ru.kutu.grind.views.api.IMainView;
	import ru.kutu.grind.views.api.IPlayPauseButton;
	import ru.kutu.grind.views.api.IPlayerView;
	import ru.kutu.grind.views.api.IQualityMenuButton;
	import ru.kutu.grind.views.api.IScrubBar;
	import ru.kutu.grind.views.api.IScrubBarTip;
	import ru.kutu.grind.views.api.IStatInfo;
	import ru.kutu.grind.views.api.ITimeInfo;
	import ru.kutu.grind.views.api.IVolumeComponent;
	import ru.kutu.grind.views.mediators.AlternateMenuBaseMediator;
	import ru.kutu.grind.views.mediators.ControlBarBaseMediator;
	import ru.kutu.grind.views.mediators.ControlBarMenuHideBaseMediator;
	import ru.kutu.grind.views.mediators.FullScreenStateMediator;
	import ru.kutu.grind.views.mediators.FullscreenButtonBaseMediator;
	import ru.kutu.grind.views.mediators.MainViewBaseMediator;
	import ru.kutu.grind.views.mediators.PlayPauseButtonBaseMediator;
	import ru.kutu.grind.views.mediators.QualityMenuBaseMediator;
	import ru.kutu.grind.views.mediators.ScrubBarTipBaseMediator;
	import ru.kutu.grind.views.mediators.TimeInfoBaseMediator;
	import ru.kutu.grind.views.mediators.VolumeComponentBaseMediator;
	import ru.kutu.grindplayer.media.GrindMediaPlayer;
	import ru.kutu.grindplayer.views.components.Subtitles;
	import ru.kutu.grindplayer.views.mediators.AutoHideMediator;
	import ru.kutu.grindplayer.views.mediators.BufferInfoMediator;
	import ru.kutu.grindplayer.views.mediators.PlayerViewMediator;
	import ru.kutu.grindplayer.views.mediators.ScrubBarMediator;
	import ru.kutu.grindplayer.views.mediators.ScrubBarMinimizedMediator;
	import ru.kutu.grindplayer.views.mediators.ShortcutsMediator;
	import ru.kutu.grindplayer.views.mediators.StatInfoMediator;
	import ru.kutu.grindplayer.views.mediators.SubtitlesMediator;
	import ru.kutu.grindplayer.views.mediators.SubtitlesMenuMediator;
	import ru.kutu.grindplayer.views.mediators.api.IScrubBarMinimized;
	import ru.kutu.grindplayer.views.mediators.api.ISubtitlesMenuButton;

	import spark.components.Application;

	public class AppConfig extends GrindConfig {

		[Inject] public var contextView:ContextView;
		[Inject] public var mediatorMap:IMediatorMap;
		[Inject] public var logger:ILogger;

		override public function configure():void {
			super.configure();

			Application(contextView.view).focusManager.deactivate();

			injector.map(LocalSettings).asSingleton();
			injector.map(JavaScriptBridgeBase).toSingleton(JavaScriptBridge);
			injector.map(Thumbnails).asSingleton();

			mediatorMap.map(IMainView).toMediator(MainViewBaseMediator);

			mediatorMap.map(IPlayerView).toMediator(PlayerViewMediator);
			mediatorMap.map(IPlayerView).toMediator(AutoHideMediator);
			mediatorMap.map(IPlayerView).toMediator(ShortcutsMediator);

			mediatorMap.map(Subtitles).toMediator(SubtitlesMediator);
			mediatorMap.map(IControlBarView).toMediator(ControlBarBaseMediator);
			mediatorMap.map(IBufferInfo).toMediator(BufferInfoMediator);
			mediatorMap.map(IStatInfo).toMediator(StatInfoMediator);

			mediatorMap.map(IScrubBar).toMediator(ScrubBarMediator);
			mediatorMap.map(IScrubBarTip).toMediator(ScrubBarTipBaseMediator);
			mediatorMap.map(IScrubBarMinimized).toMediator(ScrubBarMinimizedMediator);

			mediatorMap.map(IPlayPauseButton).toMediator(PlayPauseButtonBaseMediator);
			mediatorMap.map(IVolumeComponent).toMediator(VolumeComponentBaseMediator);
			mediatorMap.map(ITimeInfo).toMediator(TimeInfoBaseMediator);

			mediatorMap.map(IControlBarMenuButtonHide).toMediator(ControlBarMenuHideBaseMediator);
			mediatorMap.map(ISubtitlesMenuButton).toMediator(SubtitlesMenuMediator);
			mediatorMap.map(IAlternateMenuButton).toMediator(AlternateMenuBaseMediator);
			mediatorMap.map(IQualityMenuButton).toMediator(QualityMenuBaseMediator);

			mediatorMap.map(IFullScreenButton).toMediator(FullscreenButtonBaseMediator);
			mediatorMap.map(IFullScreenState).toMediator(FullScreenStateMediator);
		}

		override protected function configuration():void {
			injector.map(PlayerConfiguration).toSingleton(GrindPlayerConfiguration);
			injector.map(MediaPlayer).toSingleton(GrindMediaPlayer);
			injector.map(MediaResourceBase).toProvider(new ResourceProvider());
			injector.map(MediaFactory).toSingleton(GrindMediaFactoryBase);

			injector.map(ConfigurationProxy).toProvider(new ConfigurationProxyProvider());
			injector.map(ConfigurationFlashvarsDeserializer).toValue(
				new ConfigurationFlashvarsDeserializer(
					injector.getInstance(ConfigurationProxy)
				));
			injector.map(ConfigurationXMLDeserializer).toValue(
				new ConfigurationXMLDeserializer(
					injector.getInstance(ConfigurationProxy)
				));
			injector.map(ConfigurationLoader).toValue(
				new ConfigurationLoader(
					injector.getInstance(ConfigurationFlashvarsDeserializer),
					injector.getInstance(ConfigurationXMLDeserializer)
				));
		}

	}

}

make Thumbnails extensible

make Thumbnails extensible


package aerys.minko.type.clone
{
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.controller.AnimationController;
	import aerys.minko.scene.controller.camera.CameraController;
	import aerys.minko.scene.controller.mesh.VisibilityController;
	import aerys.minko.scene.controller.mesh.skinning.SkinningController;

	public class CloneOptions
	{
		private var _clonedControllerTypes		: Vector.<Class>	= new <Class>[];
		private var _ignoredControllerTypes		: Vector.<Class>	= new <Class>[];
		private var _reassignedControllerTypes	: Vector.<Class>	= new <Class>[];
		private var _defaultControllerAction	: uint				= ControllerCloneAction.REASSIGN;

		public function get defaultControllerAction() : uint
		{
			return _defaultControllerAction;
		}
		public function set defaultControllerAction(v : uint) : void
		{
			if (v != ControllerCloneAction.CLONE
				&& v != ControllerCloneAction.IGNORE
				&& v != ControllerCloneAction.REASSIGN)
				throw new Error('Unknown action type.');

			_defaultControllerAction = v;
		}

		public function CloneOptions()
		{
		}

		public static function get defaultOptions() : CloneOptions
		{
			var cloneOptions : CloneOptions = new CloneOptions();

			cloneOptions._clonedControllerTypes.push(AnimationController, SkinningController);
			cloneOptions._ignoredControllerTypes.push(VisibilityController, CameraController);
			cloneOptions._defaultControllerAction = ControllerCloneAction.REASSIGN;

			return cloneOptions;
		}

		public static function get cloneAllOptions() : CloneOptions
		{
			var cloneOptions : CloneOptions = new CloneOptions();

			cloneOptions._ignoredControllerTypes.push(VisibilityController, CameraController);
			cloneOptions._defaultControllerAction = ControllerCloneAction.CLONE;

			return cloneOptions;
		}

		public function addControllerAction(controllerClass : Class, action : uint) : void
		{
			switch (action)
			{
				case ControllerCloneAction.CLONE:
					_clonedControllerTypes.push(controllerClass);
					break;

				case ControllerCloneAction.IGNORE:
					_ignoredControllerTypes.push(controllerClass);
					break;

				case ControllerCloneAction.REASSIGN:
					_reassignedControllerTypes.push(controllerClass);
					break;

				default:
					throw new Error('Unknown action type.');
			}
		}

		public function removeControllerAction(controllerClass : Class) : void
		{
			throw new Error('Implement me.');
		}

		public function getActionForController(controller : AbstractController) : uint
		{
			var numControllersToClone		: uint = _clonedControllerTypes.length;
			var numControllersToIgnore		: uint = _ignoredControllerTypes.length;
			var numControllersToReassign	: uint = _reassignedControllerTypes.length;
			var controllerId				: uint;

			for (controllerId = 0; controllerId < numControllersToClone; ++controllerId)
				if (controller is _clonedControllerTypes[controllerId])
					return ControllerCloneAction.CLONE;

			for (controllerId = 0; controllerId < numControllersToIgnore; ++controllerId)
				if (controller is _ignoredControllerTypes[controllerId])
					return ControllerCloneAction.IGNORE;

			for (controllerId = 0; controllerId < numControllersToReassign; ++controllerId)
				if (controller is _reassignedControllerTypes[controllerId])
					return ControllerCloneAction.REASSIGN;

			return _defaultControllerAction;
		}

	}
}

package aerys.minko.type.clone
{
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.controller.AnimationController;
	import aerys.minko.scene.controller.TransformController;
	import aerys.minko.scene.controller.camera.CameraController;
	import aerys.minko.scene.controller.mesh.VisibilityController;
	import aerys.minko.scene.controller.mesh.skinning.SkinningController;

	public class CloneOptions
	{
		private var _clonedControllerTypes		: Vector.<Class>	= new <Class>[];
		private var _ignoredControllerTypes		: Vector.<Class>	= new <Class>[];
		private var _reassignedControllerTypes	: Vector.<Class>	= new <Class>[];
		private var _defaultControllerAction	: uint				= ControllerCloneAction.REASSIGN;

		public function get defaultControllerAction() : uint
		{
			return _defaultControllerAction;
		}
		public function set defaultControllerAction(v : uint) : void
		{
			if (v != ControllerCloneAction.CLONE
				&& v != ControllerCloneAction.IGNORE
				&& v != ControllerCloneAction.REASSIGN)
				throw new Error('Unknown action type.');

			_defaultControllerAction = v;
		}

		public function CloneOptions()
		{
		}

		public static function get defaultOptions() : CloneOptions
		{
			var cloneOptions : CloneOptions = new CloneOptions();

			cloneOptions._clonedControllerTypes.push(
				AnimationController,
				SkinningController
			);
			cloneOptions._ignoredControllerTypes.push(
				VisibilityController,
				CameraController,
				TransformController
			);
			cloneOptions._defaultControllerAction = ControllerCloneAction.REASSIGN;

			return cloneOptions;
		}

		public static function get cloneAllOptions() : CloneOptions
		{
			var cloneOptions : CloneOptions = new CloneOptions();

			cloneOptions._ignoredControllerTypes.push(VisibilityController, CameraController);
			cloneOptions._defaultControllerAction = ControllerCloneAction.CLONE;

			return cloneOptions;
		}

		public function addControllerAction(controllerClass : Class, action : uint) : void
		{
			switch (action)
			{
				case ControllerCloneAction.CLONE:
					_clonedControllerTypes.push(controllerClass);
					break;

				case ControllerCloneAction.IGNORE:
					_ignoredControllerTypes.push(controllerClass);
					break;

				case ControllerCloneAction.REASSIGN:
					_reassignedControllerTypes.push(controllerClass);
					break;

				default:
					throw new Error('Unknown action type.');
			}
		}

		public function removeControllerAction(controllerClass : Class) : void
		{
			throw new Error('Implement me.');
		}

		public function getActionForController(controller : AbstractController) : uint
		{
			var numControllersToClone		: uint = _clonedControllerTypes.length;
			var numControllersToIgnore		: uint = _ignoredControllerTypes.length;
			var numControllersToReassign	: uint = _reassignedControllerTypes.length;
			var controllerId				: uint;

			for (controllerId = 0; controllerId < numControllersToClone; ++controllerId)
				if (controller is _clonedControllerTypes[controllerId])
					return ControllerCloneAction.CLONE;

			for (controllerId = 0; controllerId < numControllersToIgnore; ++controllerId)
				if (controller is _ignoredControllerTypes[controllerId])
					return ControllerCloneAction.IGNORE;

			for (controllerId = 0; controllerId < numControllersToReassign; ++controllerId)
				if (controller is _reassignedControllerTypes[controllerId])
					return ControllerCloneAction.REASSIGN;

			return _defaultControllerAction;
		}

	}
}

fix CloneOptions to ignore the TransformController


package dolly {
import dolly.core.dolly_internal;
import dolly.data.CompositeCloneableClass;

import org.as3commons.reflect.Field;
import org.as3commons.reflect.Type;
import org.flexunit.asserts.assertEquals;
import org.flexunit.asserts.assertNotNull;

use namespace dolly_internal;

public class CloningOfCompositeCloneableClassTest {

	private var compositeCloneableClass:CompositeCloneableClass;
	private var compositeCloneableClassType:Type;

	[Before]
	public function before():void {
		compositeCloneableClass = new CompositeCloneableClass();

		compositeCloneableClassType = Type.forInstance(compositeCloneableClass);
	}

	[After]
	public function after():void {
		compositeCloneableClass = null;
		compositeCloneableClassType = null;
	}

	[Test]
	public function findingAllWritableFieldsForType():void {
		const writableFields:Vector.<Field> = Cloner.findAllWritableFieldsForType(compositeCloneableClassType);
		assertNotNull(writableFields);
		assertEquals(4, writableFields.length);
	}
}
}

package dolly {
import dolly.core.dolly_internal;
import dolly.data.CompositeCloneableClass;

import org.as3commons.reflect.Field;
import org.as3commons.reflect.Type;
import org.flexunit.asserts.assertEquals;
import org.flexunit.asserts.assertFalse;
import org.flexunit.asserts.assertNotNull;
import org.hamcrest.assertThat;
import org.hamcrest.collection.array;
import org.hamcrest.collection.arrayWithSize;
import org.hamcrest.collection.everyItem;
import org.hamcrest.core.isA;
import org.hamcrest.object.equalTo;

use namespace dolly_internal;

public class CloningOfCompositeCloneableClassTest {

	private var compositeCloneableClass:CompositeCloneableClass;
	private var compositeCloneableClassType:Type;

	[Before]
	public function before():void {
		compositeCloneableClass = new CompositeCloneableClass();

		compositeCloneableClassType = Type.forInstance(compositeCloneableClass);
	}

	[After]
	public function after():void {
		compositeCloneableClass = null;
		compositeCloneableClassType = null;
	}

	[Test]
	public function findingAllWritableFieldsForType():void {
		const writableFields:Vector.<Field> = Cloner.findAllWritableFieldsForType(compositeCloneableClassType);
		assertNotNull(writableFields);
		assertEquals(4, writableFields.length);
	}

	[Test]
	public function cloningOfArray():void {
		const clone:CompositeCloneableClass = Cloner.clone(compositeCloneableClass);

		assertNotNull(clone.array);
		assertThat(clone.array, arrayWithSize(5));
		assertThat(clone.array, compositeCloneableClass.array);
		assertFalse(clone.array == compositeCloneableClass.array);
		assertThat(clone.array, everyItem(isA(Number)));
		assertThat(clone.array, array(equalTo(0), equalTo(1), equalTo(2), equalTo(3), equalTo(4)));
	}
}
}

Test for cloning array in CompositeCloneableClass.