/**
 * @file llsidepaneliteminfo.cpp
 * @brief A floater which shows an inventory item's properties.
 *
 * $LicenseInfo:firstyear=2002&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * 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;
 * version 2.1 of the License only.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "llviewerprecompiledheaders.h"
#include "llsidepaneliteminfo.h"

#include "roles_constants.h"

#include "llagent.h"
#include "llavataractions.h"
#include "llavatarnamecache.h"
#include "llbutton.h"
#include "llcallbacklist.h"
#include "llcombobox.h"
#include "llfloater.h"
#include "llfloaterreg.h"
#include "llgroupactions.h"
#include "llgroupmgr.h"
#include "lliconctrl.h"
#include "llinventorydefines.h"
#include "llinventoryicon.h"
#include "llinventorymodel.h"
#include "llinventoryobserver.h"
#include "lllineeditor.h"
#include "llradiogroup.h"
#include "llslurl.h"
#include "lltexteditor.h"
#include "llviewercontrol.h"
#include "llviewerinventory.h"
#include "llviewerobjectlist.h"
#include "llexperiencecache.h"
#include "lltrans.h"
#include "llviewerregion.h"


class PropertiesChangedCallback : public LLInventoryCallback
{
public:
    PropertiesChangedCallback(LLHandle<LLPanel> sidepanel_handle, LLUUID &item_id, S32 id)
        : mHandle(sidepanel_handle), mItemId(item_id), mId(id)
    {}

    void fire(const LLUUID &inv_item)
    {
        // inv_item can be null for some reason
        LLSidepanelItemInfo* sidepanel = dynamic_cast<LLSidepanelItemInfo*>(mHandle.get());
        if (sidepanel)
        {
            // sidepanel waits only for most recent update
            sidepanel->onUpdateCallback(mItemId, mId);
        }
    }
private:
    LLHandle<LLPanel> mHandle;
    LLUUID mItemId;
    S32 mId;
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Class LLObjectInventoryObserver
//
// Helper class to watch for changes in an object inventory.
// Used to update item properties in LLSidepanelItemInfo.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class LLObjectInventoryObserver : public LLVOInventoryListener
{
public:
    LLObjectInventoryObserver(LLSidepanelItemInfo* floater, LLViewerObject* object)
        : mFloater(floater)
    {
        registerVOInventoryListener(object, NULL);
    }
    virtual ~LLObjectInventoryObserver()
    {
        removeVOInventoryListener();
    }
    /*virtual*/ void inventoryChanged(LLViewerObject* object,
                                      LLInventoryObject::object_list_t* inventory,
                                      S32 serial_num,
                                      void* user_data);
private:
    LLSidepanelItemInfo* mFloater;  // Not a handle because LLSidepanelItemInfo is managing LLObjectInventoryObserver
};

/*virtual*/
void LLObjectInventoryObserver::inventoryChanged(LLViewerObject* object,
                                                 LLInventoryObject::object_list_t* inventory,
                                                 S32 serial_num,
                                                 void* user_data)
{
    mFloater->dirty();
}

///----------------------------------------------------------------------------
/// Class LLSidepanelItemInfo
///----------------------------------------------------------------------------

static LLPanelInjector<LLSidepanelItemInfo> t_item_info("sidepanel_item_info");

// Default constructor
LLSidepanelItemInfo::LLSidepanelItemInfo(const LLPanel::Params& p)
    : LLPanel(p)
    , mItemID(LLUUID::null)
    , mObjectInventoryObserver(NULL)
    , mUpdatePendingId(-1)
    , mIsDirty(false) /*Not ready*/
    , mParentFloater(NULL)
{
    gInventory.addObserver(this);
    gIdleCallbacks.addFunction(&LLSidepanelItemInfo::onIdle, (void*)this);
}

// Destroys the object
LLSidepanelItemInfo::~LLSidepanelItemInfo()
{
    gInventory.removeObserver(this);
    gIdleCallbacks.deleteFunction(&LLSidepanelItemInfo::onIdle, (void*)this);

    stopObjectInventoryObserver();

    if (mOwnerCacheConnection.connected())
    {
        mOwnerCacheConnection.disconnect();
    }
    if (mCreatorCacheConnection.connected())
    {
        mCreatorCacheConnection.disconnect();
    }
}

// virtual
bool LLSidepanelItemInfo::postBuild()
{
    mChangeThumbnailBtn = getChild<LLUICtrl>("change_thumbnail_btn");
    mItemTypeIcon = getChild<LLIconCtrl>("item_type_icon");
    mLabelOwnerName = getChild<LLTextBox>("LabelOwnerName");
    mLabelCreatorName = getChild<LLTextBox>("LabelCreatorName");

    getChild<LLLineEditor>("LabelItemName")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe);
    getChild<LLUICtrl>("LabelItemName")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitName,this));
    getChild<LLUICtrl>("LabelItemDesc")->setCommitCallback(boost::bind(&LLSidepanelItemInfo:: onCommitDescription, this));
    // Thumnail edition
    mChangeThumbnailBtn->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onEditThumbnail, this));
    // acquired date
    // owner permissions
    // Permissions debug text
    // group permissions
    getChild<LLUICtrl>("CheckShareWithGroup")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitPermissions, this, _1));
    // everyone permissions
    getChild<LLUICtrl>("CheckEveryoneCopy")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitPermissions, this, _1));
    // next owner permissions
    getChild<LLUICtrl>("CheckNextOwnerModify")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitPermissions, this, _1));
    getChild<LLUICtrl>("CheckNextOwnerCopy")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitPermissions, this, _1));
    getChild<LLUICtrl>("CheckNextOwnerTransfer")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitPermissions, this, _1));
    // Mark for sale or not, and sale info
    getChild<LLUICtrl>("CheckPurchase")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitSaleInfo, this, _1));
    // Change sale type, and sale info
    getChild<LLUICtrl>("ComboBoxSaleType")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitSaleInfo, this, _1));
    // "Price" label for edit
    getChild<LLUICtrl>("Edit Cost")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitSaleInfo, this, _1));
    refresh();
    return true;
}

void LLSidepanelItemInfo::setObjectID(const LLUUID& object_id)
{
    mObjectID = object_id;

    // Start monitoring changes in the object inventory to update
    // selected inventory item properties in Item Profile panel. See STORM-148.
    startObjectInventoryObserver();
    mUpdatePendingId = -1;
}

void LLSidepanelItemInfo::setItemID(const LLUUID& item_id)
{
    if (mItemID != item_id)
    {
        mItemID = item_id;
        mUpdatePendingId = -1;
    }
    dirty();
}

void LLSidepanelItemInfo::setParentFloater(LLFloater* parent)
{
    mParentFloater = parent;
}

const LLUUID& LLSidepanelItemInfo::getObjectID() const
{
    return mObjectID;
}

const LLUUID& LLSidepanelItemInfo::getItemID() const
{
    return mItemID;
}

void LLSidepanelItemInfo::onUpdateCallback(const LLUUID& item_id, S32 received_update_id)
{
    if (mItemID == item_id && mUpdatePendingId == received_update_id)
    {
        mUpdatePendingId = -1;
        refresh();
    }
}

void LLSidepanelItemInfo::reset()
{
    mObjectID = LLUUID::null;
    mItemID = LLUUID::null;

    stopObjectInventoryObserver();
    dirty();
}

void LLSidepanelItemInfo::refresh()
{
    LLViewerInventoryItem* item = findItem();
    if(item)
    {
        const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH);
        bool in_trash = (item->getUUID() == trash_id) || gInventory.isObjectDescendentOf(item->getUUID(), trash_id);
        if (in_trash && mParentFloater)
        {
            // Close properties when moving to trash
            // Aren't supposed to view properties from trash
            mParentFloater->closeFloater();
        }
        else
        {
            refreshFromItem(item);
        }
        return;
    }

    if (mObjectID.notNull())
    {
        LLViewerObject* object = gObjectList.findObject(mObjectID);
        if (object)
        {
            // Object exists, but object's content is not nessesary
            // loaded, so assume item exists as well
            return;
        }
    }

    if (mParentFloater)
    {
        // if we failed to get item, it likely no longer exists
        mParentFloater->closeFloater();
    }
}

void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item)
{
    ////////////////////////
    // PERMISSIONS LOOKUP //
    ////////////////////////

    llassert(item);
    if (!item) return;

    if (mUpdatePendingId != -1)
    {
        return;
    }

    // do not enable the UI for incomplete items.
    bool is_complete = item->isFinished();
    const bool cannot_restrict_permissions = LLInventoryType::cannotRestrictPermissions(item->getInventoryType());
    const bool is_calling_card = (item->getInventoryType() == LLInventoryType::IT_CALLINGCARD);
    const bool is_settings = (item->getInventoryType() == LLInventoryType::IT_SETTINGS);
    const LLPermissions& perm = item->getPermissions();
    const bool can_agent_manipulate = gAgent.allowOperation(PERM_OWNER, perm,
                                GP_OBJECT_MANIPULATE);
    const bool can_agent_sell = gAgent.allowOperation(PERM_OWNER, perm,
                              GP_OBJECT_SET_SALE) &&
        !cannot_restrict_permissions;
    const bool is_link = item->getIsLinkType();

    const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH);
    bool not_in_trash = (item->getUUID() != trash_id) && !gInventory.isObjectDescendentOf(item->getUUID(), trash_id);

    // You need permission to modify the object to modify an inventory
    // item in it.
    LLViewerObject* object = NULL;
    if(!mObjectID.isNull()) object = gObjectList.findObject(mObjectID);
    bool is_obj_modify = true;
    if(object)
    {
        is_obj_modify = object->permOwnerModify();
    }

    if(item->getInventoryType() == LLInventoryType::IT_LSL)
    {
        getChildView("LabelItemExperienceTitle")->setVisible(true);
        LLTextBox* tb = getChild<LLTextBox>("LabelItemExperience");
        tb->setText(getString("loading_experience"));
        tb->setVisible(true);
        std::string url = std::string();
        if(object && object->getRegion())
        {
            url = object->getRegion()->getCapability("GetMetadata");
        }
        LLExperienceCache::instance().fetchAssociatedExperience(item->getParentUUID(), item->getUUID(), url,
                boost::bind(&LLSidepanelItemInfo::setAssociatedExperience, getDerivedHandle<LLSidepanelItemInfo>(), _1));
    }

    //////////////////////
    // ITEM NAME & DESC //
    //////////////////////
    bool is_modifiable = gAgent.allowOperation(PERM_MODIFY, perm,
                                               GP_OBJECT_MANIPULATE)
        && is_obj_modify && is_complete && not_in_trash;

    getChildView("LabelItemNameTitle")->setEnabled(true);
    getChildView("LabelItemName")->setEnabled(is_modifiable && !is_calling_card); // for now, don't allow rename of calling cards
    getChild<LLUICtrl>("LabelItemName")->setValue(item->getName());
    getChildView("LabelItemDescTitle")->setEnabled(true);
    getChildView("LabelItemDesc")->setEnabled(is_modifiable);
    getChild<LLUICtrl>("LabelItemDesc")->setValue(item->getDescription());
    getChild<LLUICtrl>("item_thumbnail")->setValue(item->getThumbnailUUID());

    LLUIImagePtr icon_img = LLInventoryIcon::getIcon(item->getType(), item->getInventoryType(), item->getFlags(), false);
    mItemTypeIcon->setImage(icon_img);

    // Style for creator and owner links
    LLStyle::Params style_params;
    LLUIColor link_color = LLUIColorTable::instance().getColor("HTMLLinkColor");
    style_params.color = link_color;
    style_params.readonly_color = link_color;
    style_params.is_link = true; // link will be added later
    const LLFontGL* fontp = mLabelCreatorName->getFont();
    style_params.font.name = LLFontGL::nameFromFont(fontp);
    style_params.font.size = LLFontGL::sizeFromFont(fontp);
    style_params.font.style = "UNDERLINE";

    //////////////////
    // CREATOR NAME //
    //////////////////
    if(!gCacheName) return;
    if(!gAgent.getRegion()) return;

    if (item->getCreatorUUID().notNull())
    {
        LLUUID creator_id = item->getCreatorUUID();
        std::string slurl =
            LLSLURL("agent", creator_id, "inspect").getSLURLString();

        style_params.link_href = slurl;

        LLAvatarName av_name;
        if (LLAvatarNameCache::get(creator_id, &av_name))
        {
            updateCreatorName(creator_id, av_name, style_params);
        }
        else
        {
            if (mCreatorCacheConnection.connected())
            {
                mCreatorCacheConnection.disconnect();
            }
            mLabelCreatorName->setText(LLTrans::getString("None"));
            mCreatorCacheConnection = LLAvatarNameCache::get(creator_id, boost::bind(&LLSidepanelItemInfo::updateCreatorName, this, _1, _2, style_params));
        }

        getChildView("LabelCreatorTitle")->setEnabled(true);
        mLabelCreatorName->setEnabled(true);
    }
    else
    {
        getChildView("LabelCreatorTitle")->setEnabled(false);
        mLabelCreatorName->setEnabled(false);
        mLabelCreatorName->setValue(getString("unknown_multiple"));
    }

    ////////////////
    // OWNER NAME //
    ////////////////
    if(perm.isOwned())
    {
        std::string slurl;
        if (perm.isGroupOwned())
        {
            LLGroupMgrGroupData* group_data = LLGroupMgr::getInstance()->getGroupData(perm.getGroup());

            slurl = LLSLURL("group", perm.getGroup(), "inspect").getSLURLString();
            style_params.link_href = slurl;
            if (group_data && group_data->isGroupPropertiesDataComplete())
            {
                mLabelOwnerName->setText(group_data->mName, style_params);
            }
            else
            {
                // Triggers refresh
                LLGroupMgr::getInstance()->sendGroupPropertiesRequest(perm.getGroup());

                std::string name;
                gCacheName->getGroupName(perm.getGroup(), name);
                mLabelOwnerName->setText(name, style_params);
            }
        }
        else
        {
            LLUUID owner_id = perm.getOwner();
            slurl = LLSLURL("agent", owner_id, "inspect").getSLURLString();

            style_params.link_href = slurl;
            LLAvatarName av_name;
            if (LLAvatarNameCache::get(owner_id, &av_name))
            {
                updateOwnerName(owner_id, av_name, style_params);
            }
            else
            {
                if (mOwnerCacheConnection.connected())
                {
                    mOwnerCacheConnection.disconnect();
                }
                mLabelOwnerName->setText(LLTrans::getString("None"));
                mOwnerCacheConnection = LLAvatarNameCache::get(owner_id, boost::bind(&LLSidepanelItemInfo::updateOwnerName, this, _1, _2, style_params));
            }
        }
        getChildView("LabelOwnerTitle")->setEnabled(true);
        mLabelOwnerName->setEnabled(true);
    }
    else
    {
        getChildView("LabelOwnerTitle")->setEnabled(false);
        mLabelOwnerName->setEnabled(false);
        mLabelOwnerName->setValue(getString("public"));
    }

    // Not yet supported for task inventories
    mChangeThumbnailBtn->setEnabled(mObjectID.isNull() && ALEXANDRIA_LINDEN_ID != perm.getOwner());

    ////////////
    // ORIGIN //
    ////////////

    if (object)
    {
        getChild<LLUICtrl>("origin")->setValue(getString("origin_inworld"));
    }
    else
    {
        getChild<LLUICtrl>("origin")->setValue(getString("origin_inventory"));
    }

    //////////////////
    // ACQUIRE DATE //
    //////////////////

    time_t time_utc = item->getCreationDate();
    if (0 == time_utc)
    {
        getChild<LLUICtrl>("LabelAcquiredDate")->setValue(getString("unknown"));
    }
    else
    {
        std::string timeStr = getString("acquiredDate");
        LLSD substitution;
        substitution["datetime"] = (S32) time_utc;
        LLStringUtil::format (timeStr, substitution);
        getChild<LLUICtrl>("LabelAcquiredDate")->setValue(timeStr);
    }

    //////////////////////////////////////
    // PERMISSIONS AND SALE ITEM HIDING //
    //////////////////////////////////////

    const std::string perm_and_sale_items[]={
        "perms_inv",
        "perm_modify",
        "CheckOwnerModify",
        "CheckOwnerCopy",
        "CheckOwnerTransfer",
        "GroupLabel",
        "CheckShareWithGroup",
        "AnyoneLabel",
        "CheckEveryoneCopy",
        "NextOwnerLabel",
        "CheckNextOwnerModify",
        "CheckNextOwnerCopy",
        "CheckNextOwnerTransfer",
        "CheckPurchase",
        "ComboBoxSaleType",
        "Edit Cost"
    };

    const std::string debug_items[]={
        "BaseMaskDebug",
        "OwnerMaskDebug",
        "GroupMaskDebug",
        "EveryoneMaskDebug",
        "NextMaskDebug"
    };

    // Hide permissions checkboxes and labels and for sale info if in the trash
    // or ui elements don't apply to these objects and return from function
    if (!not_in_trash || cannot_restrict_permissions)
    {
        for(size_t t=0; t<LL_ARRAY_SIZE(perm_and_sale_items); ++t)
        {
            getChildView(perm_and_sale_items[t])->setVisible(false);
        }

        for(size_t t=0; t<LL_ARRAY_SIZE(debug_items); ++t)
        {
            getChildView(debug_items[t])->setVisible(false);
        }
        return;
    }
    else // Make sure perms and sale ui elements are visible
    {
        for(size_t t=0; t<LL_ARRAY_SIZE(perm_and_sale_items); ++t)
        {
            getChildView(perm_and_sale_items[t])->setVisible(true);
        }
    }

    ///////////////////////
    // OWNER PERMISSIONS //
    ///////////////////////

    U32 base_mask       = perm.getMaskBase();
    U32 owner_mask      = perm.getMaskOwner();
    U32 group_mask      = perm.getMaskGroup();
    U32 everyone_mask   = perm.getMaskEveryone();
    U32 next_owner_mask = perm.getMaskNextOwner();

    getChildView("CheckOwnerModify")->setEnabled(false);
    getChild<LLUICtrl>("CheckOwnerModify")->setValue(LLSD((bool)(owner_mask & PERM_MODIFY)));
    getChildView("CheckOwnerCopy")->setEnabled(false);
    getChild<LLUICtrl>("CheckOwnerCopy")->setValue(LLSD((bool)(owner_mask & PERM_COPY)));
    getChildView("CheckOwnerTransfer")->setEnabled(false);
    getChild<LLUICtrl>("CheckOwnerTransfer")->setValue(LLSD((bool)(owner_mask & PERM_TRANSFER)));

    ///////////////////////
    // DEBUG PERMISSIONS //
    ///////////////////////

    if( gSavedSettings.getBOOL("DebugPermissions") )
    {
        childSetVisible("layout_debug_permissions", true);

        bool slam_perm          = false;
        bool overwrite_group    = false;
        bool overwrite_everyone = false;

        if (item->getType() == LLAssetType::AT_OBJECT)
        {
            U32 flags = item->getFlags();
            slam_perm           = flags & LLInventoryItemFlags::II_FLAGS_OBJECT_SLAM_PERM;
            overwrite_everyone  = flags & LLInventoryItemFlags::II_FLAGS_OBJECT_PERM_OVERWRITE_EVERYONE;
            overwrite_group     = flags & LLInventoryItemFlags::II_FLAGS_OBJECT_PERM_OVERWRITE_GROUP;
        }

        std::string perm_string;

        perm_string = "B: ";
        perm_string += mask_to_string(base_mask);
        getChild<LLUICtrl>("BaseMaskDebug")->setValue(perm_string);

        perm_string = "O: ";
        perm_string += mask_to_string(owner_mask);
        getChild<LLUICtrl>("OwnerMaskDebug")->setValue(perm_string);

        perm_string = "G";
        perm_string += overwrite_group ? "*: " : ": ";
        perm_string += mask_to_string(group_mask);
        getChild<LLUICtrl>("GroupMaskDebug")->setValue(perm_string);

        perm_string = "E";
        perm_string += overwrite_everyone ? "*: " : ": ";
        perm_string += mask_to_string(everyone_mask);
        getChild<LLUICtrl>("EveryoneMaskDebug")->setValue(perm_string);

        perm_string = "N";
        perm_string += slam_perm ? "*: " : ": ";
        perm_string += mask_to_string(next_owner_mask);
        getChild<LLUICtrl>("NextMaskDebug")->setValue(perm_string);
    }
    else
    {
        childSetVisible("layout_debug_permissions", false);
    }

    /////////////
    // SHARING //
    /////////////

    // Check for ability to change values.
    if (is_link || cannot_restrict_permissions)
    {
        getChildView("CheckShareWithGroup")->setEnabled(false);
        getChildView("CheckEveryoneCopy")->setEnabled(false);
    }
    else if (is_obj_modify && can_agent_manipulate)
    {
        getChildView("CheckShareWithGroup")->setEnabled(true);
        getChildView("CheckEveryoneCopy")->setEnabled((owner_mask & PERM_COPY) && (owner_mask & PERM_TRANSFER));
    }
    else
    {
        getChildView("CheckShareWithGroup")->setEnabled(false);
        getChildView("CheckEveryoneCopy")->setEnabled(false);
    }

    // Set values.
    bool is_group_copy = group_mask & PERM_COPY;
    bool is_group_modify = group_mask & PERM_MODIFY;
    bool is_group_move = group_mask & PERM_MOVE;

    if (is_group_copy && is_group_modify && is_group_move)
    {
        getChild<LLUICtrl>("CheckShareWithGroup")->setValue(LLSD((bool)true));
        if (LLCheckBoxCtrl* ctl = getChild<LLCheckBoxCtrl>("CheckShareWithGroup"))
        {
            ctl->setTentative(false);
        }
    }
    else if (!is_group_copy && !is_group_modify && !is_group_move)
    {
        getChild<LLUICtrl>("CheckShareWithGroup")->setValue(LLSD((bool)false));
        if (LLCheckBoxCtrl* ctl = getChild<LLCheckBoxCtrl>("CheckShareWithGroup"))
        {
            ctl->setTentative(false);
        }
    }
    else
    {
        if (LLCheckBoxCtrl* ctl = getChild<LLCheckBoxCtrl>("CheckShareWithGroup"))
        {
            ctl->setTentative(!ctl->getEnabled());
            ctl->set(true);
        }
    }

    getChild<LLUICtrl>("CheckEveryoneCopy")->setValue(LLSD((bool)(everyone_mask & PERM_COPY)));

    ///////////////
    // SALE INFO //
    ///////////////

    const LLSaleInfo& sale_info = item->getSaleInfo();
    bool is_for_sale = sale_info.isForSale();
    LLComboBox* combo_sale_type = getChild<LLComboBox>("ComboBoxSaleType");
    LLUICtrl* edit_cost = getChild<LLUICtrl>("Edit Cost");

    // Check for ability to change values.
    if (is_obj_modify && can_agent_sell
        && gAgent.allowOperation(PERM_TRANSFER, perm, GP_OBJECT_MANIPULATE))
    {
        getChildView("CheckPurchase")->setEnabled(is_complete);

        getChildView("NextOwnerLabel")->setEnabled(true);
        getChildView("CheckNextOwnerModify")->setEnabled((base_mask & PERM_MODIFY) && !cannot_restrict_permissions);
        getChildView("CheckNextOwnerCopy")->setEnabled((base_mask & PERM_COPY) && !cannot_restrict_permissions && !is_settings);
        getChildView("CheckNextOwnerTransfer")->setEnabled((next_owner_mask & PERM_COPY) && !cannot_restrict_permissions);

        combo_sale_type->setEnabled(is_complete && is_for_sale);
        edit_cost->setEnabled(is_complete && is_for_sale);
    }
    else
    {
        getChildView("CheckPurchase")->setEnabled(false);

        getChildView("NextOwnerLabel")->setEnabled(false);
        getChildView("CheckNextOwnerModify")->setEnabled(false);
        getChildView("CheckNextOwnerCopy")->setEnabled(false);
        getChildView("CheckNextOwnerTransfer")->setEnabled(false);

        combo_sale_type->setEnabled(false);
        edit_cost->setEnabled(false);
    }

    // Hide any properties that are not relevant to settings
    if (is_settings)
    {
        getChild<LLUICtrl>("GroupLabel")->setEnabled(false);
        getChild<LLUICtrl>("GroupLabel")->setVisible(false);
        getChild<LLUICtrl>("CheckShareWithGroup")->setEnabled(false);
        getChild<LLUICtrl>("CheckShareWithGroup")->setVisible(false);
        getChild<LLUICtrl>("AnyoneLabel")->setEnabled(false);
        getChild<LLUICtrl>("AnyoneLabel")->setVisible(false);
        getChild<LLUICtrl>("CheckEveryoneCopy")->setEnabled(false);
        getChild<LLUICtrl>("CheckEveryoneCopy")->setVisible(false);
        getChild<LLUICtrl>("CheckPurchase")->setEnabled(false);
        getChild<LLUICtrl>("CheckPurchase")->setVisible(false);
        getChild<LLUICtrl>("ComboBoxSaleType")->setEnabled(false);
        getChild<LLUICtrl>("ComboBoxSaleType")->setVisible(false);
        getChild<LLUICtrl>("Edit Cost")->setEnabled(false);
        getChild<LLUICtrl>("Edit Cost")->setVisible(false);
    }

    // Set values.
    getChild<LLUICtrl>("CheckPurchase")->setValue(is_for_sale);
    getChild<LLUICtrl>("CheckNextOwnerModify")->setValue(LLSD(bool(next_owner_mask & PERM_MODIFY)));
    getChild<LLUICtrl>("CheckNextOwnerCopy")->setValue(LLSD(bool(next_owner_mask & PERM_COPY)));
    getChild<LLUICtrl>("CheckNextOwnerTransfer")->setValue(LLSD(bool(next_owner_mask & PERM_TRANSFER)));

    if (is_for_sale)
    {
        S32 numerical_price;
        numerical_price = sale_info.getSalePrice();
        edit_cost->setValue(llformat("%d",numerical_price));
        combo_sale_type->setValue(sale_info.getSaleType());
    }
    else
    {
        edit_cost->setValue(llformat("%d",0));
        combo_sale_type->setValue(LLSaleInfo::FS_COPY);
    }
}

void LLSidepanelItemInfo::updateCreatorName(const LLUUID& creator_id, const LLAvatarName& creator_name, const LLStyle::Params& style_params)
{
    if (mCreatorCacheConnection.connected())
    {
        mCreatorCacheConnection.disconnect();
    }
    std::string name = creator_name.getCompleteName();
    mLabelCreatorName->setText(name, style_params);
}

void LLSidepanelItemInfo::updateOwnerName(const LLUUID& owner_id, const LLAvatarName& owner_name, const LLStyle::Params& style_params)
{
    if (mOwnerCacheConnection.connected())
    {
        mOwnerCacheConnection.disconnect();
    }
    std::string name = owner_name.getCompleteName();
    mLabelOwnerName->setText(name, style_params);
}

void LLSidepanelItemInfo::changed(U32 mask)
{
    const LLUUID& item_id = getItemID();
    if (getObjectID().notNull() || item_id.isNull())
    {
        // Task inventory or not set up yet
        return;
    }

    const std::set<LLUUID>& mChangedItemIDs = gInventory.getChangedIDs();
    std::set<LLUUID>::const_iterator it;

    for (it = mChangedItemIDs.begin(); it != mChangedItemIDs.end(); it++)
    {
        // set dirty for 'item profile panel' only if changed item is the item for which 'item profile panel' is shown (STORM-288)
        if (*it == item_id)
        {
            // if there's a change we're interested in.
            if((mask & (LLInventoryObserver::LABEL | LLInventoryObserver::INTERNAL | LLInventoryObserver::REMOVE)) != 0)
            {
                dirty();
            }
        }
    }
}

void LLSidepanelItemInfo::dirty()
{
    mIsDirty = true;
}

// static
void LLSidepanelItemInfo::onIdle( void* user_data )
{
    LLSidepanelItemInfo* self = reinterpret_cast<LLSidepanelItemInfo*>(user_data);

    if( self->mIsDirty )
    {
        self->refresh();
        self->mIsDirty = false;
    }
}

void LLSidepanelItemInfo::setAssociatedExperience( LLHandle<LLSidepanelItemInfo> hInfo, const LLSD& experience )
{
    LLSidepanelItemInfo* info = hInfo.get();
    if(info)
    {
        LLUUID id;
        if(experience.has(LLExperienceCache::EXPERIENCE_ID))
        {
            id=experience[LLExperienceCache::EXPERIENCE_ID].asUUID();
        }
        if(id.notNull())
        {
            info->getChild<LLTextBox>("LabelItemExperience")->setText(LLSLURL("experience", id, "profile").getSLURLString());
        }
        else
        {
            info->getChild<LLTextBox>("LabelItemExperience")->setText(LLTrans::getString("ExperienceNameNull"));
        }
    }
}


void LLSidepanelItemInfo::startObjectInventoryObserver()
{
    if (!mObjectInventoryObserver)
    {
        stopObjectInventoryObserver();

        // Previous object observer should be removed before starting to observe a new object.
        llassert(mObjectInventoryObserver == NULL);
    }

    if (mObjectID.isNull())
    {
        LL_WARNS() << "Empty object id passed to inventory observer" << LL_ENDL;
        return;
    }

    LLViewerObject* object = gObjectList.findObject(mObjectID);

    mObjectInventoryObserver = new LLObjectInventoryObserver(this, object);
}

void LLSidepanelItemInfo::stopObjectInventoryObserver()
{
    delete mObjectInventoryObserver;
    mObjectInventoryObserver = NULL;
}

void LLSidepanelItemInfo::setPropertiesFieldsEnabled(bool enabled)
{
    const std::string fields[] = {
        "CheckOwnerModify",
        "CheckOwnerCopy",
        "CheckOwnerTransfer",
        "CheckShareWithGroup",
        "CheckEveryoneCopy",
        "CheckNextOwnerModify",
        "CheckNextOwnerCopy",
        "CheckNextOwnerTransfer",
        "CheckPurchase",
        "Edit Cost"
    };
    for (size_t t = 0; t<LL_ARRAY_SIZE(fields); ++t)
    {
        getChildView(fields[t])->setEnabled(false);
    }
}

void LLSidepanelItemInfo::onClickCreator()
{
    LLViewerInventoryItem* item = findItem();
    if(!item) return;
    if(!item->getCreatorUUID().isNull())
    {
        LLAvatarActions::showProfile(item->getCreatorUUID());
    }
}

// static
void LLSidepanelItemInfo::onClickOwner()
{
    LLViewerInventoryItem* item = findItem();
    if(!item) return;
    if(item->getPermissions().isGroupOwned())
    {
        LLGroupActions::show(item->getPermissions().getGroup());
    }
    else
    {
        LLAvatarActions::showProfile(item->getPermissions().getOwner());
    }
}

// static
void LLSidepanelItemInfo::onCommitName()
{
    //LL_INFOS() << "LLSidepanelItemInfo::onCommitName()" << LL_ENDL;
    LLViewerInventoryItem* item = findItem();
    if(!item)
    {
        return;
    }
    LLLineEditor* labelItemName = getChild<LLLineEditor>("LabelItemName");

    if(labelItemName&&
       (item->getName() != labelItemName->getText()) &&
       (gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE)) )
    {
        LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item);
        new_item->rename(labelItemName->getText());
        onCommitChanges(new_item);
    }
}

void LLSidepanelItemInfo::onCommitDescription()
{
    //LL_INFOS() << "LLSidepanelItemInfo::onCommitDescription()" << LL_ENDL;
    LLViewerInventoryItem* item = findItem();
    if(!item) return;

    LLTextEditor* labelItemDesc = getChild<LLTextEditor>("LabelItemDesc");
    if(!labelItemDesc)
    {
        return;
    }
    if((item->getDescription() != labelItemDesc->getText()) &&
       (gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE)))
    {
        LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item);

        new_item->setDescription(labelItemDesc->getText());
        onCommitChanges(new_item);
    }
}

void LLSidepanelItemInfo::onCommitPermissions(LLUICtrl* ctrl)
{
    if (ctrl)
    {
        // will be enabled by response from server
        ctrl->setEnabled(false);
    }
    updatePermissions();
}

void LLSidepanelItemInfo::updatePermissions()
{
    LLViewerInventoryItem* item = findItem();
    if(!item) return;

    bool is_group_owned;
    LLUUID owner_id;
    LLUUID group_id;
    LLPermissions perm(item->getPermissions());
    perm.getOwnership(owner_id, is_group_owned);

    if (is_group_owned && gAgent.hasPowerInGroup(owner_id, GP_OBJECT_MANIPULATE))
    {
        group_id = owner_id;
    }

    LLCheckBoxCtrl* CheckShareWithGroup = getChild<LLCheckBoxCtrl>("CheckShareWithGroup");

    if(CheckShareWithGroup)
    {
        perm.setGroupBits(gAgent.getID(), group_id,
                        CheckShareWithGroup->get(),
                        PERM_MODIFY | PERM_MOVE | PERM_COPY);
    }
    LLCheckBoxCtrl* CheckEveryoneCopy = getChild<LLCheckBoxCtrl>("CheckEveryoneCopy");
    if(CheckEveryoneCopy)
    {
        perm.setEveryoneBits(gAgent.getID(), group_id,
                         CheckEveryoneCopy->get(), PERM_COPY);
    }

    LLCheckBoxCtrl* CheckNextOwnerModify = getChild<LLCheckBoxCtrl>("CheckNextOwnerModify");
    if(CheckNextOwnerModify)
    {
        perm.setNextOwnerBits(gAgent.getID(), group_id,
                            CheckNextOwnerModify->get(), PERM_MODIFY);
    }
    LLCheckBoxCtrl* CheckNextOwnerCopy = getChild<LLCheckBoxCtrl>("CheckNextOwnerCopy");
    if(CheckNextOwnerCopy)
    {
        perm.setNextOwnerBits(gAgent.getID(), group_id,
                            CheckNextOwnerCopy->get(), PERM_COPY);
    }
    LLCheckBoxCtrl* CheckNextOwnerTransfer = getChild<LLCheckBoxCtrl>("CheckNextOwnerTransfer");
    if(CheckNextOwnerTransfer)
    {
        perm.setNextOwnerBits(gAgent.getID(), group_id,
                            CheckNextOwnerTransfer->get(), PERM_TRANSFER);
    }
    if(perm != item->getPermissions()
        && item->isFinished())
    {
        LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item);
        new_item->setPermissions(perm);
        U32 flags = new_item->getFlags();
        // If next owner permissions have changed (and this is an object)
        // then set the slam permissions flag so that they are applied on rez.
        if((perm.getMaskNextOwner()!=item->getPermissions().getMaskNextOwner())
           && (item->getType() == LLAssetType::AT_OBJECT))
        {
            flags |= LLInventoryItemFlags::II_FLAGS_OBJECT_SLAM_PERM;
        }
        // If everyone permissions have changed (and this is an object)
        // then set the overwrite everyone permissions flag so they
        // are applied on rez.
        if ((perm.getMaskEveryone()!=item->getPermissions().getMaskEveryone())
            && (item->getType() == LLAssetType::AT_OBJECT))
        {
            flags |= LLInventoryItemFlags::II_FLAGS_OBJECT_PERM_OVERWRITE_EVERYONE;
        }
        // If group permissions have changed (and this is an object)
        // then set the overwrite group permissions flag so they
        // are applied on rez.
        if ((perm.getMaskGroup()!=item->getPermissions().getMaskGroup())
            && (item->getType() == LLAssetType::AT_OBJECT))
        {
            flags |= LLInventoryItemFlags::II_FLAGS_OBJECT_PERM_OVERWRITE_GROUP;
        }
        new_item->setFlags(flags);
        onCommitChanges(new_item);
    }
    else
    {
        // need to make sure we don't just follow the click
        refresh();
    }
}

void LLSidepanelItemInfo::onEditThumbnail()
{
    LLSD data;
    data["task_id"] = mObjectID;
    data["item_id"] = mItemID;
    LLFloaterReg::showInstance("change_item_thumbnail", data);
}

void LLSidepanelItemInfo::onCommitSaleInfo(LLUICtrl* ctrl)
{
    if (ctrl)
    {
        // will be enabled by response from server
        ctrl->setEnabled(false);
    }
    //LL_INFOS() << "LLSidepanelItemInfo::onCommitSaleInfo()" << LL_ENDL;
    updateSaleInfo();
}

void LLSidepanelItemInfo::updateSaleInfo()
{
    LLViewerInventoryItem* item = findItem();
    if(!item) return;
    LLSaleInfo sale_info(item->getSaleInfo());
    if(!gAgent.allowOperation(PERM_TRANSFER, item->getPermissions(), GP_OBJECT_SET_SALE))
    {
        getChild<LLUICtrl>("CheckPurchase")->setValue(LLSD((bool)false));
    }

    if((bool)getChild<LLUICtrl>("CheckPurchase")->getValue())
    {
        // turn on sale info
        LLSaleInfo::EForSale sale_type = LLSaleInfo::FS_COPY;

        LLComboBox* combo_sale_type = getChild<LLComboBox>("ComboBoxSaleType");
        if (combo_sale_type)
        {
            sale_type = static_cast<LLSaleInfo::EForSale>(combo_sale_type->getValue().asInteger());
        }

        if (sale_type == LLSaleInfo::FS_COPY
            && !gAgent.allowOperation(PERM_COPY, item->getPermissions(),
                                      GP_OBJECT_SET_SALE))
        {
            sale_type = LLSaleInfo::FS_ORIGINAL;
        }



        S32 price = -1;
        price =  getChild<LLUICtrl>("Edit Cost")->getValue().asInteger();;

        // Invalid data - turn off the sale
        if (price < 0)
        {
            sale_type = LLSaleInfo::FS_NOT;
            price = 0;
        }

        sale_info.setSaleType(sale_type);
        sale_info.setSalePrice(price);
    }
    else
    {
        sale_info.setSaleType(LLSaleInfo::FS_NOT);
    }
    if(sale_info != item->getSaleInfo()
        && item->isFinished())
    {
        LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item);

        // Force an update on the sale price at rez
        if (item->getType() == LLAssetType::AT_OBJECT)
        {
            U32 flags = new_item->getFlags();
            flags |= LLInventoryItemFlags::II_FLAGS_OBJECT_SLAM_SALE;
            new_item->setFlags(flags);
        }

        new_item->setSaleInfo(sale_info);
        onCommitChanges(new_item);
    }
    else
    {
        // need to make sure we don't just follow the click
        refresh();
    }
}

void LLSidepanelItemInfo::onCommitChanges(LLPointer<LLViewerInventoryItem> item)
{
    if (item.isNull())
    {
        return;
    }

    if (mObjectID.isNull())
    {
        // This is in the agent's inventory.
        // Mark update as pending and wait only for most recent one in case user requested for couple
        // Once update arrives or any of ids change drop pending id.
        mUpdatePendingId++;
        LLPointer<LLInventoryCallback> callback = new PropertiesChangedCallback(getHandle(), mItemID, mUpdatePendingId);
        update_inventory_item(item.get(), callback);
        //item->updateServer(false);
        gInventory.updateItem(item);
        gInventory.notifyObservers();
    }
    else
    {
        // This is in an object's contents.
        LLViewerObject* object = gObjectList.findObject(mObjectID);
        if (object)
        {
            object->updateInventory(
                item,
                TASK_INVENTORY_ITEM_KEY,
                false);

            if (object->isSelected())
            {
                // Since object is selected (build floater is open) object will
                // receive properties update, detect serial mismatch, dirty and
                // reload inventory, meanwhile some other updates will refresh it.
                // So mark dirty early, this will prevent unnecessary changes
                // and download will be triggered by LLPanelObjectInventory - it
                // prevents flashing in content tab and some duplicated request.
                object->dirtyInventory();
            }
            setPropertiesFieldsEnabled(false);
        }
    }
}

LLViewerInventoryItem* LLSidepanelItemInfo::findItem() const
{
    LLViewerInventoryItem* item = NULL;
    if(mObjectID.isNull())
    {
        // it is in agent inventory
        item = gInventory.getItem(mItemID);
    }
    else
    {
        LLViewerObject* object = gObjectList.findObject(mObjectID);
        if(object)
        {
            item = static_cast<LLViewerInventoryItem*>(object->getInventoryObject(mItemID));
        }
    }
    return item;
}

// virtual
void LLSidepanelItemInfo::save()
{
    onCommitName();
    onCommitDescription();
    updatePermissions();
    updateSaleInfo();
}