Files
minerjr b6307a278e Notecard Editor byte counter - automatically hide byte count on resize
Added callback method for the resize bars of the Notecard Editor floater.
Once the text overlaps the Save button, it will hide the  byte count textbox.
Works for both when the floater is manually resized or if the text becomes too long by typing and is going to overlap as well.
2026-07-19 22:27:08 -03:00

1075 lines
35 KiB
C++

/**
* @file llpreviewnotecard.cpp
* @brief Implementation of the notecard editor
*
* $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 "llpreviewnotecard.h"
#include "llnotecard.h" // <FS> Byte counter
#include "llinventory.h"
#include "llagent.h"
#include "lldraghandle.h"
#include "llexternaleditor.h"
#include "llviewerwindow.h"
#include "llbutton.h"
#include "llfloaterreg.h"
#include "llinventorydefines.h"
#include "llinventorymodel.h"
#include "lllineeditor.h"
#include "llmd5.h"
#include "llnotificationsutil.h"
#include "llmd5.h"
#include "llresmgr.h"
#include "roles_constants.h"
#include "llscrollbar.h"
#include "llselectmgr.h"
#include "lltrans.h"
#include "llviewertexteditor.h"
#include "llfilesystem.h"
#include "llviewerinventory.h"
#include "llviewerobject.h"
#include "llviewerobjectlist.h"
#include "llviewerregion.h"
#include "lldir.h"
#include "llviewerstats.h"
#include "llviewercontrol.h" // gSavedSettings
#include "llappviewer.h" // app_abort_quit()
#include "lllineeditor.h"
#include "lluictrlfactory.h"
#include "llviewerassetupload.h"
#include "lluistring.h" // <FS> Needed for the Byte Counter
// [SL:KB] - Patch: UI-FloaterSearchReplace | Checked: 2010-11-05 (Catznip-2.3.0a) | Added: Catznip-2.3.0a
#include "llfloatersearchreplace.h"
// [/SL:KB]
#include "llautoreplace.h"
#include "llscrollcontainer.h"
///----------------------------------------------------------------------------
/// Class LLPreviewNotecard
///----------------------------------------------------------------------------
// Default constructor
LLPreviewNotecard::LLPreviewNotecard(const LLSD& key) //const LLUUID& item_id,
: LLPreview( key )
// <FS:Ansariel> FIRE-24306: Retain cursor position when saving notecards
,mCursorPos(0)
,mScrollPos(0)
,mByteCounterTemplate("") // <FS> Initialize empty for Byte Counter
// <FS:Ansariel> FIRE-29425: User-selectable font and size for notecards
,mFontNameChangedCallbackConnection()
,mFontSizeChangedCallbackConnection()
// </FS:Ansariel>
{
const LLInventoryItem *item = getItem();
if (item)
{
mAssetID = item->getAssetUUID();
}
}
LLPreviewNotecard::~LLPreviewNotecard()
{
delete mLiveFile;
mEditor = nullptr;
// <FS:Ansariel> FIRE-29425: User-selectable font and size for notecards
if (mFontNameChangedCallbackConnection.connected())
{
mFontNameChangedCallbackConnection.disconnect();
}
if (mFontSizeChangedCallbackConnection.connected())
{
mFontSizeChangedCallbackConnection.disconnect();
}
// </FS:Ansariel>
}
bool LLPreviewNotecard::postBuild()
{
mEditor = getChild<LLViewerTextEditor>("Notecard Editor");
mEditor->setAutoreplaceCallback(boost::bind(&LLAutoReplace::autoreplaceCallback, LLAutoReplace::getInstance(), _1, _2, _3, _4, _5)); // <FS:Ansariel> FIRE-22810: Add autoreplace to notecards
mEditor->setNotecardInfo(mItemUUID, mObjectID, getKey());
mEditor->makePristine();
mSaveBtn = getChild<LLButton>("Save");
mSaveBtn->setCommitCallback(boost::bind(&LLPreviewNotecard::saveIfNeeded, this, nullptr, true));
mLockBtn = findChild<LLUICtrl>("lock");
if (mLockBtn)
mLockBtn->setVisible(false);
mDeleteBtn = getChild<LLButton>("Delete");
mDeleteBtn->setCommitCallback(boost::bind(&LLPreviewNotecard::deleteNotecard, this));
mDeleteBtn->setEnabled(false);
mEditBtn = getChild<LLButton>("Edit");
mEditBtn->setCommitCallback(boost::bind(&LLPreviewNotecard::openInExternalEditor, this));
// <FS> Byte counter
mByteCounter = getChild<LLTextBox>("byte_counter");
if (mByteCounter)
{
mByteCounterTemplate = mByteCounter->getText();
if (mByteCounterTemplate.empty())
mByteCounterTemplate = "Bytes: [BYTES] / [MAX]";
LLUIString init_text(mByteCounterTemplate);
init_text.setArg("[BYTES]", "0");
init_text.setArg("[MAX]", std::to_string(LLNotecard::MAX_SIZE));
mByteCounter->setText(init_text.getString());
}
mEditor->setKeystrokeCallback([this](LLTextEditor*) { mByteCounterDirty = true; });
// </FS>
// <FS:Ansariel> FIRE-13969: Search button
getChild<LLButton>("Search")->setClickedCallback(boost::bind(&LLPreviewNotecard::onSearchButtonClicked, this));
const LLInventoryItem* item = getItem();
mDescEditor = getChild<LLLineEditor>("desc");
mDescEditor->setCommitCallback(boost::bind(&LLPreview::onText, mDescEditor, this));
if (item)
{
mDescEditor->setValue(item->getDescription());
bool source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(item->getUUID(), gInventory.getLibraryRootFolderID());
mDeleteBtn->setEnabled(!source_library);
}
mDescEditor->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe);
// <FS:Ansariel> FIRE-29425: User-selectable font and size for notecards
mFontNameChangedCallbackConnection = gSavedSettings.getControl("FSNotecardFontName")->getSignal()->connect(boost::bind(&LLPreviewNotecard::onFontChanged, this));
mFontSizeChangedCallbackConnection = gSavedSettings.getControl("FSNotecardFontSize")->getSignal()->connect(boost::bind(&LLPreviewNotecard::onFontChanged, this));
onFontChanged();
// </FS:Ansariel>
// <FS:mjr>
// Get the resize bars and add a resize listener to the onResizeFloater method.
LLResizeBar* resizebar_left = getChild<LLResizeBar>("resizebar_left");
if (resizebar_left)
{
resizebar_left->setResizeListener(boost::bind(&LLPreviewNotecard::onResizeFloater, this));
}
LLResizeBar* resizebar_right = getChild<LLResizeBar>("resizebar_right");
if (resizebar_right)
{
resizebar_right->setResizeListener(boost::bind(&LLPreviewNotecard::onResizeFloater, this));
}
LLResizeBar* resizebar_top = getChild<LLResizeBar>("resizebar_top");
if (resizebar_top)
{
resizebar_top->setResizeListener(boost::bind(&LLPreviewNotecard::onResizeFloater, this));
}
LLResizeBar* resizebar_bottom = getChild<LLResizeBar>("resizebar_bottom");
if (resizebar_bottom)
{
resizebar_bottom->setResizeListener(boost::bind(&LLPreviewNotecard::onResizeFloater, this));
}
// </FS:mjr>
return LLPreview::postBuild();
}
bool LLPreviewNotecard::saveItem()
{
LLInventoryItem* item = gInventory.getItem(mItemUUID);
return saveIfNeeded(item);
}
// <FS:Neremyn> Byte counter
void LLPreviewNotecard::updateByteCounter()
{
if (!mEditor || !mByteCounter) return;
mByteCounterDirty = false;
const size_t MAX_BYTES = LLNotecard::MAX_SIZE;
std::string text = mEditor->getText();
size_t bytes = text.size(); // Assumes UTF-8 encoding where size() == bytes
auto getColorForByteCount = [bytes]() -> LLColor4
{
if (bytes >= MAX_BYTES)
return LLColor4::red;
if (bytes > (MAX_BYTES * 8 / 10))
return LLColor4::yellow;
return LLUIColorTable::getInstance()->getColor("LabelTextColor"); // Use the actual default color from the Widget.
};
mByteCounter->setColor(getColorForByteCount());
// Ensure template is valid
if (mByteCounterTemplate.empty())
{
mByteCounterTemplate = "Bytes: [BYTES] / [MAX]";
}
LLUIString ui_text(mByteCounterTemplate);
ui_text.setArg("[BYTES]", std::to_string(bytes));
ui_text.setArg("[MAX]", std::to_string(MAX_BYTES));
mByteCounter->setText(ui_text.getString());
// Update the visiblity state of the mByteCounter if there is not enough space.
onResizeFloater();
}
// </FS>
// <FS:mjr>
// Callback method for the resizebar so the byte counter can become invisible if overlapped by the buttons
void LLPreviewNotecard::onResizeFloater()
{
// Set the visiblity flag of the byte counter to true if the right side of the byte counter text is less then the left side of the save button.
S32 text_right = mByteCounter->getRect().mLeft + mByteCounter->getTextPixelWidth();
mByteCounter->setVisible(text_right < mSaveBtn->getRect().mLeft);
}
// </FS:mjr>
void LLPreviewNotecard::setEnabled(bool enabled)
{
if (mEditor)
{
mEditor->setEnabled(enabled);
}
if (mLockBtn)
{
mLockBtn->setVisible(!enabled);
}
if (mDescEditor)
{
mDescEditor->setEnabled(enabled);
}
if (mSaveBtn)
{
mSaveBtn->setEnabled(enabled && mEditor && (!mEditor->isPristine()));
}
}
void LLPreviewNotecard::draw()
{
bool changed = !mEditor->isPristine();
mSaveBtn->setEnabled(changed && getEnabled());
// <FS> Byte counter
if (mByteCounterDirty)
{
updateByteCounter();
}
// </FS>
LLPreview::draw();
}
// [SL:KB] - Patch: UI-FloaterSearchReplace | Checked: 2010-11-05 (Catznip-2.3.0a) | Added: Catznip-2.3.0a
bool LLPreviewNotecard::hasAccelerators() const
{
return true;
}
// [/SL:KB]
// virtual
bool LLPreviewNotecard::handleKeyHere(KEY key, MASK mask)
{
if(('S' == key) && (MASK_CONTROL == (mask & MASK_CONTROL)))
{
saveIfNeeded();
return true;
}
// [SL:KB] - Patch: UI-FloaterSearchReplace | Checked: 2010-11-05 (Catznip-2.3.0a) | Added: Catznip-2.3.0a
if(('F' == key) && (MASK_CONTROL == (mask & MASK_CONTROL)))
{
LLFloaterSearchReplace::show(getEditor());
return true;
}
// [/SL:KB]
return LLPreview::handleKeyHere(key, mask);
}
// virtual
bool LLPreviewNotecard::canClose()
{
if(mForceClose || !mEditor || mEditor->isPristine())
{
return true;
}
else
{
if(!mSaveDialogShown)
{
mSaveDialogShown = true;
// Bring up view-modal dialog: Save changes? Yes, No, Cancel
LLNotificationsUtil::add("SaveChanges", LLSD(), LLSD(), boost::bind(&LLPreviewNotecard::handleSaveChangesDialog,this, _1, _2));
}
return false;
}
}
/* virtual */
void LLPreviewNotecard::setObjectID(const LLUUID& object_id)
{
LLPreview::setObjectID(object_id);
mEditor->setNotecardObjectID(mObjectUUID);
mEditor->makePristine();
}
const LLInventoryItem* LLPreviewNotecard::getDragItem()
{
return mEditor->getDragItem();
}
// [SL:KB] - Patch: UI-FloaterSearchReplace | Checked: 2010-11-05 (Catznip-2.3.0a) | Added: Catznip-2.3.0a
LLTextEditor* LLPreviewNotecard::getEditor()
{
return getChild<LLViewerTextEditor>("Notecard Editor");
}
// [/SL:KB]
bool LLPreviewNotecard::hasEmbeddedInventory()
{
return mEditor->hasEmbeddedInventory();
}
void LLPreviewNotecard::refreshFromInventory(const LLUUID& new_item_id)
{
if (new_item_id.notNull())
{
mItemUUID = new_item_id;
setKey(LLSD(new_item_id));
}
LL_DEBUGS() << "LLPreviewNotecard::refreshFromInventory()" << LL_ENDL;
loadAsset();
}
void LLPreviewNotecard::updateTitleButtons()
{
LLPreview::updateTitleButtons();
if(mLockBtn && mLockBtn->getVisible() && !isMinimized()) // lock button stays visible if floater is minimized.
{
LLRect lock_rc = mLockBtn->getRect();
LLRect buttons_rect = getDragHandle()->getButtonsRect();
buttons_rect.mLeft = lock_rc.mLeft;
getDragHandle()->setButtonsRect(buttons_rect);
}
}
void LLPreviewNotecard::loadAsset()
{
// request the asset.
const LLInventoryItem* item = getItem();
bool fail = false;
if(item)
{
LLPermissions perm(item->getPermissions());
bool is_owner = gAgent.allowOperation(PERM_OWNER, perm, GP_OBJECT_MANIPULATE);
bool allow_copy = gAgent.allowOperation(PERM_COPY, perm, GP_OBJECT_MANIPULATE);
bool allow_modify = canModify(mObjectUUID, item);
bool source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(mItemUUID, gInventory.getLibraryRootFolderID());
if (allow_copy || gAgent.isGodlike())
{
mAssetID = item->getAssetUUID();
if(mAssetID.isNull())
{
mEditor->setText(LLStringUtil::null);
mEditor->makePristine();
mEditor->setEnabled(true);
mAssetStatus = PREVIEW_ASSET_LOADED;
}
else
{
LLHost source_sim = LLHost();
LLSD* user_data = nullptr;
if (mObjectUUID.notNull())
{
LLViewerObject *objectp = gObjectList.findObject(mObjectUUID);
if (objectp && objectp->getRegion())
{
source_sim = objectp->getRegion()->getHost();
}
else
{
// The object that we're trying to look at disappeared, bail.
LL_WARNS() << "Can't find object " << mObjectUUID << " associated with notecard." << LL_ENDL;
mAssetID.setNull();
mEditor->setText(getString("no_object"));
mEditor->makePristine();
mEditor->setEnabled(false);
mAssetStatus = PREVIEW_ASSET_LOADED;
return;
}
user_data = new LLSD();
user_data->with("taskid", mObjectUUID).with("itemid", mItemUUID);
}
else
{
user_data = new LLSD(mItemUUID);
}
gAssetStorage->getInvItemAsset(source_sim,
gAgent.getID(),
gAgent.getSessionID(),
item->getPermissions().getOwner(),
mObjectUUID,
item->getUUID(),
item->getAssetUUID(),
item->getType(),
&onLoadComplete,
(void*)user_data,
true);
mAssetStatus = PREVIEW_ASSET_LOADING;
}
}
else
{
mAssetID.setNull();
mEditor->setText(getString("not_allowed"));
mEditor->makePristine();
mEditor->setEnabled(false);
mAssetStatus = PREVIEW_ASSET_LOADED;
}
if(!allow_modify)
{
mEditor->setEnabled(false);
mLockBtn->setVisible( true);
mEditBtn->setEnabled(false);
}
if((allow_modify || is_owner) && !source_library)
{
mDeleteBtn->setEnabled(true);
}
}
else if (mObjectUUID.notNull() && mItemUUID.notNull())
{
LLViewerObject* objectp = gObjectList.findObject(mObjectUUID);
if (objectp && (objectp->isInventoryPending() || objectp->isInventoryDirty()))
{
// It's a notecard in object's inventory and we failed to get it because inventory is not up to date.
// Subscribe for callback and retry at inventoryChanged()
registerVOInventoryListener(objectp, NULL); //removes previous listener
if (objectp->isInventoryDirty())
{
objectp->requestInventory();
}
}
else
{
fail = true;
}
}
else
{
fail = true;
}
if (fail)
{
mEditor->setText(LLStringUtil::null);
mEditor->makePristine();
mEditor->setEnabled(true);
// Don't set asset status here; we may not have set the item id yet
// (e.g. when this gets called initially)
//mAssetStatus = PREVIEW_ASSET_LOADED;
}
}
// static
void LLPreviewNotecard::onLoadComplete(const LLUUID& asset_uuid,
LLAssetType::EType type,
void* user_data, S32 status, LLExtStat ext_status)
{
LL_INFOS() << "LLPreviewNotecard::onLoadComplete()" << LL_ENDL;
LLSD* floater_key = (LLSD*)user_data;
LLPreviewNotecard* preview = LLFloaterReg::findTypedInstance<LLPreviewNotecard>("preview_notecard", *floater_key);
if( preview )
{
if(0 == status)
{
LLFileSystem file(asset_uuid, type, LLFileSystem::READ);
S32 file_length = file.getSize();
std::vector<char> buffer(file_length+1);
file.read((U8*)&buffer[0], file_length);
// put a EOS at the end
buffer[file_length] = 0;
LLViewerTextEditor* previewEditor = preview->mEditor;
if( (file_length > 19) && !strncmp( &buffer[0], "Linden text version", 19 ) )
{
if( !previewEditor->importBuffer( &buffer[0], file_length+1 ) )
{
LL_WARNS() << "Problem importing notecard" << LL_ENDL;
}
}
else
{
// Version 0 (just text, doesn't include version number)
previewEditor->setText(LLStringExplicit(&buffer[0]));
}
previewEditor->makePristine();
bool modifiable = preview->canModify(preview->mObjectID, preview->getItem());
// <FS:Ansariel> Force spell checker to check again after saving a NC,
// or misspelled words wouldn't be shown
previewEditor->onSpellCheckSettingsChange();
preview->setEnabled(modifiable);
preview->syncExternal();
preview->mAssetStatus = PREVIEW_ASSET_LOADED;
// <FS> Byte counter
preview->updateByteCounter();
// </FS>
// <FS:Ansariel> FIRE-24306: Retain cursor position when saving notecards
preview->mEditor->setCursorPos(preview->mCursorPos);
preview->mEditor->getScrollContainer()->getScrollbar(LLScrollContainer::VERTICAL)->setDocPos(preview->mScrollPos);
}
else
{
if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status ||
LL_ERR_FILE_EMPTY == status)
{
LLNotificationsUtil::add("NotecardMissing");
}
else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status)
{
LLNotificationsUtil::add("NotecardNoPermissions");
}
else
{
LLNotificationsUtil::add("UnableToLoadNotecard");
}
LL_WARNS() << "Problem loading notecard: " << status << LL_ENDL;
preview->mAssetStatus = PREVIEW_ASSET_ERROR;
}
}
delete floater_key;
}
struct LLSaveNotecardInfo
{
LLPreviewNotecard* mSelf;
LLUUID mItemUUID;
LLUUID mObjectUUID;
LLTransactionID mTransactionID;
LLPointer<LLInventoryItem> mCopyItem;
LLSaveNotecardInfo(LLPreviewNotecard* self, const LLUUID& item_id, const LLUUID& object_id,
const LLTransactionID& transaction_id, LLInventoryItem* copyitem) :
mSelf(self), mItemUUID(item_id), mObjectUUID(object_id), mTransactionID(transaction_id), mCopyItem(copyitem)
{
}
};
void LLPreviewNotecard::finishInventoryUpload(LLUUID itemId, LLUUID newAssetId, LLUUID newItemId)
{
// Update the UI with the new asset.
LLPreviewNotecard* nc = LLFloaterReg::findTypedInstance<LLPreviewNotecard>("preview_notecard", LLSD(itemId));
if (nc)
{
// *HACK: we have to delete the asset in the cache so
// that the viewer will redownload it. This is only
// really necessary if the asset had to be modified by
// the uploader, so this can be optimized away in some
// cases. A better design is to have a new uuid if the
// script actually changed the asset.
if (nc->hasEmbeddedInventory())
{
LLFileSystem::removeFile(newAssetId, LLAssetType::AT_NOTECARD);
}
if (newItemId.isNull())
{
nc->setAssetId(newAssetId);
nc->refreshFromInventory();
}
else
{
nc->refreshFromInventory(newItemId);
}
// <FS:Ansariel> FIRE-9039: Close notecard after choosing "Save" in close confirmation
nc->checkCloseAfterSave();
}
}
void LLPreviewNotecard::finishTaskUpload(LLUUID itemId, LLUUID newAssetId, LLUUID taskId)
{
LLSD floater_key;
floater_key["taskid"] = taskId;
floater_key["itemid"] = itemId;
LLPreviewNotecard* nc = LLFloaterReg::findTypedInstance<LLPreviewNotecard>("preview_notecard", floater_key);
if (nc)
{
if (nc->hasEmbeddedInventory())
{
LLFileSystem::removeFile(newAssetId, LLAssetType::AT_NOTECARD);
}
nc->setAssetId(newAssetId);
nc->refreshFromInventory();
// <FS:Ansariel> FIRE-9039: Close notecard after choosing "Save" in close confirmation
nc->checkCloseAfterSave();
}
}
bool LLPreviewNotecard::saveIfNeeded(LLInventoryItem* copyitem, bool sync)
{
if(!mEditor->isPristine())
{
std::string buffer;
if (!mEditor->exportBuffer(buffer))
{
return false;
}
// <FS:Ansariel> FIRE-24306: Retain cursor position when saving notecards
mCursorPos = mEditor->getCursorPos();
mScrollPos = mEditor->getScrollContainer()->getScrollbar(LLScrollContainer::VERTICAL)->getDocPos();
mEditor->makePristine();
const LLInventoryItem* item = getItem();
// save it out to database
if (item)
{
const LLViewerRegion* region = gAgent.getRegion();
if (!region)
{
LL_WARNS() << "Not connected to a region, cannot save notecard." << LL_ENDL;
return false;
}
std::string agent_url = region->getCapability("UpdateNotecardAgentInventory");
std::string task_url = region->getCapability("UpdateNotecardTaskInventory");
if (!agent_url.empty() && !task_url.empty())
{
std::string url;
LLResourceUploadInfo::ptr_t uploadInfo;
if (mObjectUUID.isNull() && !agent_url.empty())
{
uploadInfo = std::make_shared<LLBufferedAssetUploadInfo>(mItemUUID, LLAssetType::AT_NOTECARD, buffer,
[](LLUUID itemId, LLUUID newAssetId, LLUUID newItemId, LLSD) {
LLPreviewNotecard::finishInventoryUpload(itemId, newAssetId, newItemId);
},
nullptr);
url = agent_url;
}
else if (!mObjectUUID.isNull() && !task_url.empty())
{
LLUUID object_uuid(mObjectUUID);
uploadInfo = std::make_shared<LLBufferedAssetUploadInfo>(mObjectUUID, mItemUUID, LLAssetType::AT_NOTECARD, buffer,
[object_uuid](LLUUID itemId, LLUUID, LLUUID newAssetId, LLSD) {
LLPreviewNotecard::finishTaskUpload(itemId, newAssetId, object_uuid);
},
nullptr);
url = task_url;
}
if (!url.empty() && uploadInfo)
{
mAssetStatus = PREVIEW_ASSET_LOADING;
setEnabled(false);
LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo);
}
}
else if (gAssetStorage)
{
// We need to update the asset information
LLTransactionID tid;
LLAssetID asset_id;
tid.generate();
asset_id = tid.makeAssetID(gAgent.getSecureSessionID());
LLFileSystem file(asset_id, LLAssetType::AT_NOTECARD, LLFileSystem::APPEND);
LLSaveNotecardInfo* info = new LLSaveNotecardInfo(this, mItemUUID, mObjectUUID,
tid, copyitem);
S32 size = static_cast<S32>(buffer.length()) + 1;
file.write((U8*)buffer.c_str(), size);
gAssetStorage->storeAssetData(tid, LLAssetType::AT_NOTECARD,
&onSaveComplete,
(void*)info,
false);
// <FS:Ansariel> FIRE-9039: Close notecard after choosing "Save" in close confirmation
//return true;
}
else // !gAssetStorage
{
LL_WARNS() << "Not connected to an asset storage system." << LL_ENDL;
return false;
}
// <FS:Ansariel> FIRE-9039: Close notecard after choosing "Save" in close confirmation
//if(mCloseAfterSave)
//{
// closeFloater();
//}
// </FS:Ansariel>
}
}
return true;
}
void LLPreviewNotecard::syncExternal()
{
// Sync with external editor.
std::string tmp_file = getTmpFileName();
llstat s;
if (LLFile::stat(tmp_file, &s) == 0) // file exists
{
if (mLiveFile) mLiveFile->ignoreNextUpdate();
writeToFile(tmp_file);
}
}
/*virtual*/
void LLPreviewNotecard::inventoryChanged(LLViewerObject* object,
LLInventoryObject::object_list_t* inventory,
S32 serial_num,
void* user_data)
{
removeVOInventoryListener();
loadAsset();
}
void LLPreviewNotecard::deleteNotecard()
{
LLNotificationsUtil::add("DeleteNotecard", LLSD(), LLSD(), boost::bind(&LLPreviewNotecard::handleConfirmDeleteDialog,this, _1, _2));
}
// static
void LLPreviewNotecard::onSaveComplete(const LLUUID& asset_uuid, void* user_data, S32 status, LLExtStat ext_status) // StoreAssetData callback (fixed)
{
LLSaveNotecardInfo* info = (LLSaveNotecardInfo*)user_data;
if(info && (0 == status))
{
if(info->mObjectUUID.isNull())
{
LLViewerInventoryItem* item;
item = (LLViewerInventoryItem*)gInventory.getItem(info->mItemUUID);
if(item)
{
LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item);
new_item->setAssetUUID(asset_uuid);
new_item->setTransactionID(info->mTransactionID);
new_item->updateServer(false);
gInventory.updateItem(new_item);
gInventory.notifyObservers();
}
else
{
LL_WARNS() << "Inventory item for script " << info->mItemUUID
<< " is no longer in agent inventory." << LL_ENDL;
}
}
else
{
LLViewerObject* object = gObjectList.findObject(info->mObjectUUID);
LLViewerInventoryItem* item = NULL;
if(object)
{
item = (LLViewerInventoryItem*)object->getInventoryObject(info->mItemUUID);
}
if(object && item)
{
item->setAssetUUID(asset_uuid);
item->setTransactionID(info->mTransactionID);
object->updateInventory(item, TASK_INVENTORY_ITEM_KEY, false);
dialog_refresh_all();
}
else
{
LLNotificationsUtil::add("SaveNotecardFailObjectNotFound");
}
}
// Perform item copy to inventory
if (info->mCopyItem.notNull())
{
info->mSelf->mEditor->copyInventory(info->mCopyItem);
}
// Find our window and close it if requested.
LLPreviewNotecard* previewp = LLFloaterReg::findTypedInstance<LLPreviewNotecard>("preview_notecard", info->mItemUUID);
if (previewp && previewp->mCloseAfterSave)
{
previewp->closeFloater();
}
}
else
{
LL_WARNS() << "Problem saving notecard: " << status << LL_ENDL;
LLSD args;
args["REASON"] = std::string(LLAssetStorage::getErrorString(status));
LLNotificationsUtil::add("SaveNotecardFailReason", args);
}
std::string uuid_string;
asset_uuid.toString(uuid_string);
std::string filename;
filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,uuid_string) + ".tmp";
LLFile::remove(filename);
delete info;
}
bool LLPreviewNotecard::handleSaveChangesDialog(const LLSD& notification, const LLSD& response)
{
mSaveDialogShown = false;
S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
switch(option)
{
case 0: // "Yes"
mCloseAfterSave = true;
saveIfNeeded();
break;
case 1: // "No"
mForceClose = true;
closeFloater();
break;
case 2: // "Cancel"
default:
// If we were quitting, we didn't really mean it.
LLAppViewer::instance()->abortQuit();
break;
}
return false;
}
bool LLPreviewNotecard::handleConfirmDeleteDialog(const LLSD& notification, const LLSD& response)
{
S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
if (option != 0)
{
// canceled
return false;
}
if (mObjectUUID.isNull())
{
// move item from agent's inventory into trash
LLViewerInventoryItem* item = gInventory.getItem(mItemUUID);
if (item != NULL)
{
const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH);
gInventory.changeItemParent(item, trash_id, false);
}
}
else
{
// delete item from inventory of in-world object
LLViewerObject* object = gObjectList.findObject(mObjectUUID);
if(object)
{
LLViewerInventoryItem* item = dynamic_cast<LLViewerInventoryItem*>(object->getInventoryObject(mItemUUID));
if (item != NULL)
{
object->removeInventory(mItemUUID);
}
}
}
// close floater, ignore unsaved changes
mForceClose = true;
closeFloater();
return false;
}
void LLPreviewNotecard::openInExternalEditor()
{
delete mLiveFile; // deletes file
// Save the notecard to a temporary file.
std::string filename = getTmpFileName();
writeToFile(filename);
// Start watching file changes.
mLiveFile = new LLLiveLSLFile(filename, boost::bind(&LLPreviewNotecard::onExternalChange, this, _1));
mLiveFile->ignoreNextUpdate();
mLiveFile->addToEventTimer();
// Open it in external editor.
{
LLExternalEditor ed;
LLExternalEditor::EErrorCode status;
std::string msg;
status = ed.setCommand("LL_SCRIPT_EDITOR");
if (status != LLExternalEditor::EC_SUCCESS)
{
if (status == LLExternalEditor::EC_NOT_SPECIFIED) // Use custom message for this error.
{
msg = LLTrans::getString("ExternalEditorNotSet");
}
else
{
msg = LLExternalEditor::getErrorMessage(status);
}
LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", msg));
return;
}
status = ed.run(filename);
if (status != LLExternalEditor::EC_SUCCESS)
{
msg = LLExternalEditor::getErrorMessage(status);
LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", msg));
}
}
}
bool LLPreviewNotecard::onExternalChange(const std::string& filename)
{
if (!loadNotecardText(filename))
{
return false;
}
// Disable sync to avoid recursive load->save->load calls.
saveIfNeeded(NULL, false);
return true;
}
bool LLPreviewNotecard::loadNotecardText(const std::string& filename)
{
if (filename.empty())
{
LL_WARNS() << "Empty file name" << LL_ENDL;
return false;
}
LLFILE* file = LLFile::fopen(filename, "rb"); /*Flawfinder: ignore*/
if (!file)
{
LL_WARNS() << "Error opening " << filename << LL_ENDL;
return false;
}
// read in the whole file
fseek(file, 0L, SEEK_END);
size_t file_length = (size_t)ftell(file);
fseek(file, 0L, SEEK_SET);
char* buffer = new char[file_length + 1];
size_t nread = fread(buffer, 1, file_length, file);
if (nread < file_length)
{
LL_WARNS() << "Short read" << LL_ENDL;
}
buffer[nread] = '\0';
fclose(file);
std::string text = std::string(buffer);
LLStringUtil::replaceTabsWithSpaces(text, LLTextEditor::spacesPerTab());
mEditor->setText(text);
mByteCounterDirty = true; // <FS> Byte counter
delete[] buffer;
return true;
}
bool LLPreviewNotecard::writeToFile(const std::string& filename)
{
LLFILE* fp = LLFile::fopen(filename, "wb");
if (!fp)
{
LL_WARNS() << "Unable to write to " << filename << LL_ENDL;
return false;
}
std::string utf8text = mEditor->getText();
if (utf8text.size() == 0)
{
utf8text = " ";
}
fputs(utf8text.c_str(), fp);
fclose(fp);
return true;
}
std::string LLPreviewNotecard::getTmpFileName()
{
std::string notecard_id = mObjectID.asString() + "_" + mItemUUID.asString();
// Use MD5 sum to make the file name shorter and not exceed maximum path length.
char notecard_id_hash_str[33]; /* Flawfinder: ignore */
LLMD5 notecard_id_hash((const U8 *)notecard_id.c_str());
notecard_id_hash.hex_digest(notecard_id_hash_str);
return std::string(LLFile::tmpdir()) + "sl_notecard_" + notecard_id_hash_str + ".txt";
}
// <FS:Ansariel> FIRE-13969: Search button
void LLPreviewNotecard::onSearchButtonClicked()
{
LLFloaterSearchReplace::show(getEditor());
}
// </FS:Ansariel>
// <FS:Ansariel> FIRE-9039: Close notecard after choosing "Save" in close confirmation
void LLPreviewNotecard::checkCloseAfterSave()
{
if (mCloseAfterSave)
{
closeFloater();
}
}
// </FS:Ansariel>
// <FS:Ansariel> FIRE-29425: User-selectable font and size for notecards
void LLPreviewNotecard::onFontChanged()
{
LLFontGL* font = LLFontGL::getFont(LLFontDescriptor(gSavedSettings.getString("FSNotecardFontName"), gSavedSettings.getString("FSNotecardFontSize"), LLFontGL::NORMAL));
if (font)
{
mEditor->setFont(font);
}
}
// </FS:Ansariel>
// EOF