/**
* @file llpreviewtexture.cpp
* @brief LLPreviewTexture class implementation
*
* $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 "llwindow.h"
#include "llpreviewtexture.h"
#include "llagent.h"
#include "llavataractions.h"
#include "llbutton.h"
#include "llclipboard.h"
#include "llcombobox.h"
#include "llfilepicker.h"
#include "llfloaterreg.h"
#include "llimagetga.h"
#include "llimagepng.h"
#include "llinventory.h"
#include "llinventorymodel.h"
#include "llnotificationsutil.h"
#include "llresmgr.h"
#include "lltrans.h"
#include "lltextbox.h"
#include "lltextureview.h"
#include "llui.h"
#include "llviewercontrol.h" // For user-defined default save format for textures
#include "llviewerinventory.h"
#include "llviewermenufile.h" // LLFilePickerReplyThread
#include "llviewertexture.h"
#include "llviewertexturelist.h"
#include "lluictrlfactory.h"
#include "llviewercontrol.h"
#include "llviewerwindow.h"
#include "lllineeditor.h"
#include "lltexteditor.h"
#include "llimagepng.h"
#include "fscommon.h"
#include "llviewermenu.h"
#include
const S32 CLIENT_RECT_VPAD = 4;
const F32 SECONDS_TO_SHOW_FILE_SAVED_MSG = 8.f;
const F32 PREVIEW_TEXTURE_MAX_ASPECT = 200.f;
const F32 PREVIEW_TEXTURE_MIN_ASPECT = 0.005f;
// FIRE-14111: File extension missing on Linux when saving a texture
std::string checkFileExtension(const std::string& filename, LLPreviewTexture::EFileformatType format)
{
std::string tmp_name = filename;
std::string extension = (format == LLPreviewTexture::FORMAT_TGA ? ".tga" : ".png");
LLStringUtil::toLower(tmp_name);
size_t result = tmp_name.rfind(extension);
if (result == std::string::npos || result != tmp_name.length() - extension.size())
{
return (filename + extension);
}
return filename;
}
//
LLPreviewTexture::LLPreviewTexture(const LLSD& key)
: LLPreview((key.has("uuid") ? key.get("uuid") : key)), // Changed for texture preview mode
mLoadingFullImage( false ),
mShowKeepDiscard(false),
mCopyToInv(false),
mIsCopyable(false),
mIsFullPerm(false),
mUpdateDimensions(true),
mLastHeight(0),
mLastWidth(0),
mAspectRatio(0.f),
mPreviewToSave(false),
mImage(NULL),
mImageOldBoostLevel(LLGLTexture::BOOST_NONE),
mShowingButtons(false),
mDisplayNameCallback(false),
mAvatarNameCallbackConnection()
{
updateImageID();
if (key.has("save_as"))
{
mPreviewToSave = true;
}
// Texture preview mode
if (key.has("preview_only"))
{
mShowKeepDiscard = false;
mCopyToInv = false;
mIsCopyable = false;
mPreviewToSave = false;
mIsFullPerm = false;
}
}
LLPreviewTexture::~LLPreviewTexture()
{
// Handle avatar name callback
if (mAvatarNameCallbackConnection.connected())
{
mAvatarNameCallbackConnection.disconnect();
}
//
LLLoadedCallbackEntry::cleanUpCallbackList(&mCallbackTextureList) ;
if( mLoadingFullImage )
{
getWindow()->decBusyCount();
}
if (mImage.notNull())
{
mImage->setBoostLevel(mImageOldBoostLevel);
// Remove NO_DELETE texture state so image gets removed from memory (set by calling setBoostLevel(LLGLTexture::BOOST_PREVIEW))
mImage->forceActive();
//
mImage = NULL;
}
}
void LLPreviewTexture::populateRatioList()
{
// Fill in ratios list with common aspect ratio values
mRatiosList.clear();
mRatiosList.push_back(LLTrans::getString("Unconstrained"));
mRatiosList.push_back("1:1");
mRatiosList.push_back("4:3");
mRatiosList.push_back("10:7");
mRatiosList.push_back("3:2");
mRatiosList.push_back("16:10");
mRatiosList.push_back("16:9");
mRatiosList.push_back("2:1");
// Now fill combo box with provided list
LLComboBox* combo = getChild("combo_aspect_ratio");
combo->removeall();
for (std::vector::const_iterator it = mRatiosList.begin(); it != mRatiosList.end(); ++it)
{
combo->add(*it);
}
}
// virtual
bool LLPreviewTexture::postBuild()
{
mButtonsPanel = getChild("buttons_panel");
mDimensionsText = getChild("dimensions");
mAspectRatioText = getChild("aspect_ratio");
mDimensionsPanel = findChild("dimensions_panel"); // Texture preview mode
if (mCopyToInv)
{
getChild("Keep")->setLabel(getString("Copy"));
childSetAction("Keep",LLPreview::onBtnCopyToInv,this);
getChildView("Discard")->setVisible( false);
}
else if (mShowKeepDiscard)
{
childSetAction("Keep",onKeepBtn,this);
childSetAction("Discard",onDiscardBtn,this);
}
else
{
getChildView("Keep")->setVisible( false);
getChildView("Discard")->setVisible( false);
}
childSetCommitCallback("save_tex_btn", onSaveAsBtn, this);
getChildView("save_tex_btn")->setVisible(canSaveAs()); // Ansariel: No need to show the save button if we can't save anyway
getChildView("save_tex_btn")->setEnabled(canSaveAs());
const LLInventoryItem* item = getItem();
if (item)
{
if (!mCopyToInv)
{
childSetCommitCallback("desc", LLPreview::onText, this);
getChild("desc")->setValue(item->getDescription());
getChild("desc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe);
}
bool source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(item->getUUID(), gInventory.getLibraryRootFolderID());
if (source_library)
{
getChildView("Discard")->setEnabled(false);
}
}
// Fill in ratios list and combo box with common aspect ratio values
populateRatioList();
childSetCommitCallback("combo_aspect_ratio", onAspectRatioCommit, this);
LLComboBox* combo = getChild("combo_aspect_ratio");
combo->setCurrentByIndex(0);
// texture comment metadata reader
getChild("openprofile")->setClickedCallback(boost::bind(&LLPreviewTexture::onButtonClickProfile, this));
getChild("copyuuid")->setClickedCallback(boost::bind(&LLPreviewTexture::onButtonClickUUID, this));
mUploaderDateTime = getString("UploaderDateTime");
//
// AnsaStorm skin: Need to disable line editors from
// code or the floater would be dragged around if
// trying to mark text
if (findChild("uploader"))
{
getChild("uploader")->setEnabled(false);
getChild("upload_time")->setEnabled(false);
getChild("uuid")->setEnabled(false);
}
//
// FIRE-20150: Add refresh button to texture preview
getChild("btn_refresh")->setClickedCallback(boost::bind(&LLPreviewTexture::onButtonRefresh, this));
return LLPreview::postBuild();
}
// static
void LLPreviewTexture::onSaveAsBtn(LLUICtrl* ctrl, void* data)
{
LLPreviewTexture* self = (LLPreviewTexture*)data;
std::string value = ctrl->getValue().asString();
if (value == "format_png")
{
self->saveAs(LLPreviewTexture::FORMAT_PNG);
}
else if (value == "format_tga")
{
self->saveAs(LLPreviewTexture::FORMAT_TGA);
}
else
{
// Allow to use user-defined default save format for textures
// self->saveAs(LLPreviewTexture::FORMAT_TGA);
if (!gSavedSettings.getBOOL("FSTextureDefaultSaveAsFormat"))
{
self->saveAs(LLPreviewTexture::FORMAT_TGA);
}
else
{
self->saveAs(LLPreviewTexture::FORMAT_PNG);
}
//
}
}
void LLPreviewTexture::draw()
{
updateDimensions();
LLPreview::draw();
if (!isMinimized())
{
LLGLSUIDefault gls_ui;
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
const LLRect& border = mClientRect;
LLRect interior = mClientRect;
interior.stretch( -PREVIEW_BORDER_WIDTH );
// ...border
gl_rect_2d( border, LLColor4(0.f, 0.f, 0.f, 1.f));
gl_rect_2d_checkerboard( interior );
if ( mImage.notNull() )
{
// Draw the texture
gGL.diffuseColor3f( 1.f, 1.f, 1.f );
gl_draw_scaled_image(interior.mLeft,
interior.mBottom,
interior.getWidth(),
interior.getHeight(),
mImage);
// Pump the texture priority
F32 pixel_area = mLoadingFullImage ? (F32)MAX_IMAGE_AREA : (F32)(interior.getWidth() * interior.getHeight() );
mImage->addTextureStats( pixel_area );
// Don't bother decoding more than we can display, unless
// we're loading the full image.
if (!mLoadingFullImage)
{
S32 int_width = interior.getWidth();
S32 int_height = interior.getHeight();
mImage->setKnownDrawSize(int_width, int_height);
}
else
{
// Don't use this feature
mImage->setKnownDrawSize(0, 0);
}
if( mLoadingFullImage )
{
LLFontGL::getFontSansSerif()->renderUTF8(LLTrans::getString("Receiving"), 0,
interior.mLeft + 4,
interior.mBottom + 4,
LLColor4::white, LLFontGL::LEFT, LLFontGL::BOTTOM,
LLFontGL::NORMAL,
LLFontGL::DROP_SHADOW);
F32 data_progress = mImage->getDownloadProgress() ;
// Draw the progress bar.
const S32 BAR_HEIGHT = 12;
const S32 BAR_LEFT_PAD = 80;
S32 left = interior.mLeft + 4 + BAR_LEFT_PAD;
S32 bar_width = getRect().getWidth() - left - RESIZE_HANDLE_WIDTH - 2;
S32 top = interior.mBottom + 4 + BAR_HEIGHT;
S32 right = left + bar_width;
S32 bottom = top - BAR_HEIGHT;
LLColor4 background_color(0.f, 0.f, 0.f, 0.75f);
LLColor4 decoded_color(0.f, 1.f, 0.f, 1.0f);
LLColor4 downloaded_color(0.f, 0.5f, 0.f, 1.0f);
gl_rect_2d(left, top, right, bottom, background_color);
if (data_progress > 0.0f)
{
// Downloaded bytes
right = left + llfloor(data_progress * (F32)bar_width);
if (right > left)
{
gl_rect_2d(left, top, right, bottom, downloaded_color);
}
}
}
else
if( !mSavedFileTimer.hasExpired() )
{
LLFontGL::getFontSansSerif()->renderUTF8(LLTrans::getString("FileSaved"), 0,
interior.mLeft + 4,
interior.mBottom + 4,
LLColor4::white, LLFontGL::LEFT, LLFontGL::BOTTOM,
LLFontGL::NORMAL,
LLFontGL::DROP_SHADOW);
}
}
}
}
// virtual
bool LLPreviewTexture::canSaveAs() const
{
return mIsFullPerm && !mLoadingFullImage && mImage.notNull() && !mImage->isMissingAsset();
}
// virtual
void LLPreviewTexture::saveAs()
{
// FIRE-22851: Show texture "Save as" file picker subsequently instead all at once
// saveAs(LLPreviewTexture::FORMAT_TGA);
saveAs(uuid_vec_t());
//
}
// FIRE-22851: Show texture "Save as" file picker subsequently instead all at once
void LLPreviewTexture::saveAs(uuid_vec_t remaining_ids)
{
// Allow to use user-defined default save format for textures
if (!gSavedSettings.getBOOL("FSTextureDefaultSaveAsFormat"))
{
saveAs(LLPreviewTexture::FORMAT_TGA, remaining_ids);
}
else
{
saveAs(LLPreviewTexture::FORMAT_PNG, remaining_ids);
}
//
}
//
// FIRE-22851: Show texture "Save as" file picker subsequently instead all at once
//void LLPreviewTexture::saveAs(EFileformatType format)
void LLPreviewTexture::saveAs(EFileformatType format, uuid_vec_t remaining_ids)
//
{
if (mLoadingFullImage)
return;
loaded_callback_func callback;
LLFilePicker::ESaveFilter saveFilter;
switch (format)
{
case LLPreviewTexture::FORMAT_PNG:
callback = LLPreviewTexture::onFileLoadedForSavePNG;
saveFilter = LLFilePicker::FFSAVE_PNG;
break;
case LLPreviewTexture::FORMAT_TGA:
default:
callback = LLPreviewTexture::onFileLoadedForSaveTGA;
saveFilter = LLFilePicker::FFSAVE_TGA;
break;
}
// Undo MAINT-2897 and use our own texture format selection
//std::string filename = getItem() ? LLDir::getScrubbedFileName(getItem()->getName()) : LLStringUtil::null;
//LLFilePickerReplyThread::startPicker(boost::bind(&LLPreviewTexture::saveTextureToFile, this, _1), LLFilePicker::FFSAVE_TGAPNG, filename);
std::string filename = getItem() ? checkFileExtension(LLDir::getScrubbedFileName(getItem()->getName()), format) : LLStringUtil::null;
LLFilePickerReplyThread::startPicker(boost::bind(&LLPreviewTexture::saveTextureToFile, this, _1, format, callback, remaining_ids), saveFilter, filename);
//
}
// Undo MAINT-2897 and use our own texture format selection
//void LLPreviewTexture::saveTextureToFile(const std::vector& filenames)
void LLPreviewTexture::saveTextureToFile(const std::vector& filenames, EFileformatType format, loaded_callback_func callback, uuid_vec_t remaining_ids)
//
{
const LLInventoryItem* item = getItem();
if (item && mPreviewToSave)
{
mPreviewToSave = false;
LLFloaterReg::showTypedInstance("preview_texture", item->getUUID());
}
// remember the user-approved/edited file name.
// FIRE-14111: File extension missing on Linux when saving a texture
//mSaveFileName = filenames[0];
mSaveFileName = checkFileExtension(filenames[0], format);
//
mLoadingFullImage = true;
getWindow()->incBusyCount();
mImage->forceToSaveRawImage(0);//re-fetch the raw image if the old one is removed.
// Undo MAINT-2897 and use our own texture format selection
//mImage->setLoadedCallback(LLPreviewTexture::onFileLoadedForSave,
mImage->setLoadedCallback(callback,
//
0, true, false, new LLUUID(mItemUUID), &mCallbackTextureList);
// FIRE-22851: Show texture "Save as" file picker subsequently instead all at once
saveMultiple(remaining_ids);
}
void LLPreviewTexture::saveMultipleToFile(const std::string& file_name)
{
std::string texture_location(gSavedSettings.getString("TextureSaveLocation"));
std::string texture_name = LLDir::getScrubbedFileName(file_name.empty() ? getItem()->getName() : file_name);
std::string filepath;
S32 i = 0;
S32 err = 0;
std::string extension(".png");
// Allow to use user-defined default save format for textures
if (!gSavedSettings.getBOOL("FSTextureDefaultSaveAsFormat"))
{
extension = ".tga";
}
//
do
{
filepath = texture_location;
filepath += gDirUtilp->getDirDelimiter();
filepath += texture_name;
if (i != 0)
{
filepath += llformat("_%.3d", i);
}
filepath += extension;
llstat stat_info;
err = LLFile::stat( filepath, &stat_info );
i++;
} while (-1 != err); // Search until the file is not found (i.e., stat() gives an error).
mSaveFileName = filepath;
mLoadingFullImage = true;
getWindow()->incBusyCount();
mImage->forceToSaveRawImage(0);//re-fetch the raw image if the old one is removed.
// Allow to use user-defined default save format for textures
//mImage->setLoadedCallback(LLPreviewTexture::onFileLoadedForSavePNG,
// 0, true, false, new LLUUID(mItemUUID), &mCallbackTextureList);
if (gSavedSettings.getBOOL("FSTextureDefaultSaveAsFormat"))
{
mImage->setLoadedCallback(LLPreviewTexture::onFileLoadedForSavePNG,
0, true, false, new LLUUID(mItemUUID), &mCallbackTextureList);
}
else
{
mImage->setLoadedCallback(LLPreviewTexture::onFileLoadedForSaveTGA,
0, true, false, new LLUUID(mItemUUID), &mCallbackTextureList);
}
//
}
// virtual
void LLPreviewTexture::reshape(S32 width, S32 height, bool called_from_parent)
{
LLPreview::reshape(width, height, called_from_parent);
S32 horiz_pad = 2 * (LLPANEL_BORDER_WIDTH + PREVIEW_PAD) + PREVIEW_RESIZE_HANDLE_SIZE;
// add space for dimensions and aspect ratio
S32 info_height = CLIENT_RECT_VPAD;
// Texture preview mode
//if (mDimensionsText)
//{
// LLRect dim_rect(mDimensionsText->getRect());
// info_height += dim_rect.mTop;
//}
//if (mButtonsPanel->getVisible())
//{
// info_height += mButtonsPanel->getRect().getHeight();
//}
if (mDimensionsPanel)
{
LLRect dim_rect(mDimensionsPanel->getRect());
info_height += dim_rect.mTop;
}
//
LLRect client_rect(horiz_pad, getRect().getHeight(), getRect().getWidth() - horiz_pad, 0);
client_rect.mTop -= (PREVIEW_HEADER_SIZE + CLIENT_RECT_VPAD);
// texture comment metadata reader
// 1 additional line: uploader and date time
if (findChild("uploader_date_time"))
{
if (mImage && (mImage->mComment.find("a") != mImage->mComment.end() || mImage->mComment.find("z") != mImage->mComment.end()))
{
client_rect.mTop -= (getChild("uploader_date_time")->getTextBoundingRect().getHeight() + CLIENT_RECT_VPAD);
}
}
else if (findChild("uploader"))
{
// AnsaStorm skin
client_rect.mTop -= 3 * (PREVIEW_LINE_HEIGHT + CLIENT_RECT_VPAD);
}
//
client_rect.mBottom += PREVIEW_BORDER + CLIENT_RECT_VPAD + info_height ;
S32 client_width = client_rect.getWidth();
S32 client_height = client_rect.getHeight();
if (mAspectRatio > 0.f)
{
if(mAspectRatio > 1.f)
{
client_height = llceil((F32)client_width / mAspectRatio);
if(client_height > client_rect.getHeight())
{
client_height = client_rect.getHeight();
client_width = llceil((F32)client_height * mAspectRatio);
}
}
else//mAspectRatio < 1.f
{
client_width = llceil((F32)client_height * mAspectRatio);
if(client_width > client_rect.getWidth())
{
client_width = client_rect.getWidth();
client_height = llceil((F32)client_width / mAspectRatio);
}
}
}
mClientRect.setLeftTopAndSize(client_rect.getCenterX() - (client_width / 2), client_rect.getCenterY() + (client_height / 2), client_width, client_height);
}
// virtual
void LLPreviewTexture::onFocusReceived()
{
LLPreview::onFocusReceived();
}
void LLPreviewTexture::openToSave()
{
mPreviewToSave = true;
}
// Texture preview mode
//void LLPreviewTexture::hideCtrlButtons()
//{
// getChildView("desc txt")->setVisible(false);
// getChildView("desc")->setVisible(false);
// getChild("preview_stack")->collapsePanel(getChild("buttons_panel"), true);
// mButtonsPanel->setVisible(false);
// getChild("combo_aspect_ratio")->setCurrentByIndex(0); //unconstrained
// reshape(getRect().getWidth(), getRect().getHeight());
//}
//
// static
void LLPreviewTexture::onFileLoadedForSaveTGA(bool success,
LLViewerFetchedTexture *src_vi,
LLImageRaw* src,
LLImageRaw* aux_src,
S32 discard_level,
bool final,
void* userdata)
{
LLUUID* item_uuid = (LLUUID*) userdata;
LLPreviewTexture* self = LLFloaterReg::findTypedInstance("preview_texture", *item_uuid);
if( final || !success )
{
delete item_uuid;
if( self )
{
self->getWindow()->decBusyCount();
self->mLoadingFullImage = false;
}
if (!success)
{
LL_WARNS("FileSaveAs") << "Failed to download file " << *item_uuid << " for saving."
<< " Is missing: " << (src_vi->isMissingAsset() ? "true" : "false")
<< " Discard: " << src_vi->getDiscardLevel()
<< " Raw discard: " << discard_level
<< " Size: " << src_vi->getWidth() << "x" << src_vi->getHeight()
<< " Has GL texture: " << (src_vi->hasGLTexture() ? "true" : "false")
<< " Has saved raw image: " << (src_vi->hasSavedRawImage() ? "true" : "false") << LL_ENDL;
}
}
if( self && final && success )
{
// Undo MAINT-2897 and use our own texture format selection
//const U32 ext_length = 3;
//std::string extension = self->mSaveFileName.substr( self->mSaveFileName.length() - ext_length);
//LLStringUtil::toLower(extension);
//// We only support saving in PNG or TGA format
//LLPointer image;
//if(extension == "png")
//{
// image = new LLImagePNG;
//}
//else if(extension == "tga")
//{
// image = new LLImageTGA;
//}
//if( image && !image->encode( src, 0 ) )
LLPointer image_tga = new LLImageTGA;
if( !image_tga->encode( src ) )
//
{
LLSD args;
args["FILE"] = self->mSaveFileName;
LLNotificationsUtil::add("CannotEncodeFile", args);
}
// Undo MAINT-2897 and use our own texture format selection
//else if( image && !image->save( self->mSaveFileName ) )
else if( !image_tga->save( self->mSaveFileName ) )
//
{
LLSD args;
args["FILE"] = self->mSaveFileName;
LLNotificationsUtil::add("CannotWriteFile", args);
}
else
{
self->mSavedFileTimer.reset();
self->mSavedFileTimer.setTimerExpirySec( SECONDS_TO_SHOW_FILE_SAVED_MSG );
}
self->mSaveFileName.clear();
}
if( self && !success )
{
LLNotificationsUtil::add("CannotDownloadFile");
}
}
// static
void LLPreviewTexture::onFileLoadedForSavePNG(bool success,
LLViewerFetchedTexture *src_vi,
LLImageRaw* src,
LLImageRaw* aux_src,
S32 discard_level,
bool final,
void* userdata)
{
LLUUID* item_uuid = (LLUUID*) userdata;
LLPreviewTexture* self = LLFloaterReg::findTypedInstance("preview_texture", *item_uuid);
if( final || !success )
{
delete item_uuid;
if( self )
{
self->getWindow()->decBusyCount();
self->mLoadingFullImage = false;
}
}
if( self && final && success )
{
LLPointer image_png = new LLImagePNG;
if( !image_png->encode( src, 0.0 ) )
{
LLSD args;
args["FILE"] = self->mSaveFileName;
LLNotificationsUtil::add("CannotEncodeFile", args);
}
else if( !image_png->save( self->mSaveFileName ) )
{
LLSD args;
args["FILE"] = self->mSaveFileName;
LLNotificationsUtil::add("CannotWriteFile", args);
}
else
{
self->mSavedFileTimer.reset();
self->mSavedFileTimer.setTimerExpirySec( SECONDS_TO_SHOW_FILE_SAVED_MSG );
}
self->mSaveFileName.clear();
}
if( self && !success )
{
LLNotificationsUtil::add("CannotDownloadFile");
}
}
// It takes a while until we get height and width information.
// When we receive it, reshape the window accordingly.
void LLPreviewTexture::updateDimensions()
{
if (!mImage)
{
return;
}
if ((mImage->getFullWidth() * mImage->getFullHeight()) == 0)
{
return;
}
S32 img_width = mImage->getFullWidth();
S32 img_height = mImage->getFullHeight();
if (mAssetStatus != PREVIEW_ASSET_LOADED
|| mLastWidth != img_width
|| mLastHeight != img_height)
{
mAssetStatus = PREVIEW_ASSET_LOADED;
// Asset has been fully loaded, adjust aspect ratio
adjustAspectRatio();
}
// Update the width/height display every time
// Performance improvement
//mDimensionsText->setTextArg("[WIDTH]", llformat("%d", img_width));
//mDimensionsText->setTextArg("[HEIGHT]", llformat("%d", img_height));
if (img_width != mLastWidth)
{
mDimensionsText->setTextArg("[WIDTH]", llformat("%d", img_width));
}
if (img_height != mLastHeight)
{
mDimensionsText->setTextArg("[HEIGHT]", llformat("%d", img_height));
}
// Performance improvement
mLastHeight = img_height;
mLastWidth = img_width;
// Reshape the floater only when required
if (mUpdateDimensions)
{
mUpdateDimensions = false;
// : Show image at full resolution if possible
//reshape floater
//reshape(getRect().getWidth(), getRect().getHeight());
//gFloaterView->adjustToFitScreen(this, false);
// Move dimensions panel into correct position depending
// if any of the buttons is shown
LLView* button_panel = getChildView("button_panel");
LLView* dimensions_panel = getChildView("dimensions_panel");
dimensions_panel->setVisible(true);
if (!getChildView("Keep")->getVisible() &&
!getChildView("Discard")->getVisible() &&
!getChildView("btn_refresh")->getVisible() && // FIRE-20150: Add refresh button to texture preview
!getChildView("save_tex_btn")->getVisible())
{
button_panel->setVisible(false);
if (mShowingButtons)
{
dimensions_panel->translate(0, -button_panel->getRect().mTop);
mShowingButtons = false;
}
}
else
{
button_panel->setVisible(true);
if (!mShowingButtons)
{
dimensions_panel->translate(0, button_panel->getRect().mTop);
mShowingButtons = true;
}
}
// texture comment metadata reader
S32 additional_height = 0;
if (findChild("uploader_date_time"))
{
bool adjust_height = false;
if (mImage->mComment.find("a") != mImage->mComment.end())
{
getChildView("uploader_date_time")->setVisible(true);
LLUUID id = LLUUID(mImage->mComment["a"]);
std::string name;
LLAvatarName avatar_name;
if (LLAvatarNameCache::get(id, &avatar_name))
{
mUploaderDateTime.setArg("[UPLOADER]", avatar_name.getCompleteName());
}
else
{
if (!mDisplayNameCallback) // prevents a possible callbackLoadName loop due to server error.
{
mDisplayNameCallback = true;
mUploaderDateTime.setArg("[UPLOADER]", LLTrans::getString("AvatarNameWaiting"));
if (mAvatarNameCallbackConnection.connected())
{
mAvatarNameCallbackConnection.disconnect();
}
mAvatarNameCallbackConnection = LLAvatarNameCache::get(id, boost::bind(&LLPreviewTexture::callbackLoadName, this, _1, _2));
}
}
getChild("uploader_date_time")->setText(mUploaderDateTime.getString());
adjust_height = true;
}
if (mImage->mComment.find("z") != mImage->mComment.end())
{
if (!adjust_height)
{
getChildView("uploader_date_time")->setVisible(true);
adjust_height = true;
}
std::string date_time = mImage->mComment["z"];
LLSD substitution;
substitution["datetime"] = FSCommon::secondsSinceEpochFromString("%Y%m%d%H%M%S", date_time);
date_time = getString("DateTime"); // reuse date_time variable
LLStringUtil::format(date_time, substitution);
mUploaderDateTime.setArg("[DATE_TIME]", date_time);
getChild("uploader_date_time")->setText(mUploaderDateTime.getString());
}
// add extra space for uploader and date_time
if (adjust_height)
{
getChildView("openprofile")->setVisible(true);
additional_height += (getChild("uploader_date_time")->getTextBoundingRect().getHeight());
}
}
else if (findChild("uploader"))
{
// AnsaStorm skin
if (mImage->mComment.find("a") != mImage->mComment.end())
{
getChild("openprofile")->setEnabled(true);
LLUUID id = LLUUID(mImage->mComment["a"]);
std::string name;
LLAvatarName avatar_name;
if (LLAvatarNameCache::get(id, &avatar_name))
{
childSetValue("uploader", LLSD( avatar_name.getCompleteName()) );
}
else
{
if (!mDisplayNameCallback) // prevents a possible callbackLoadName loop due to server error.
{
mDisplayNameCallback = true;
getChild("uploader")->setText(LLTrans::getString("AvatarNameWaiting"));
if (mAvatarNameCallbackConnection.connected())
{
mAvatarNameCallbackConnection.disconnect();
}
mAvatarNameCallbackConnection = LLAvatarNameCache::get(id, boost::bind(&LLPreviewTexture::callbackLoadName, this, _1, _2));
}
}
}
if (mImage->mComment.find("z") != mImage->mComment.end())
{
std::string date_time = mImage->mComment["z"];
LLSD substitution;
substitution["datetime"] = FSCommon::secondsSinceEpochFromString("%Y%m%d%H%M%S", date_time);
date_time = getString("DateTime"); // reuse date_time variable
LLStringUtil::format(date_time, substitution);
childSetValue("upload_time", LLSD( date_time ) );
}
if (mIsFullPerm)
{
childSetValue("uuid", LLSD( mImageID.asString() ));
}
LLView* uploader_view = getChildView("uploader");
LLView* uploadtime_view = getChildView("upload_time");
LLView* uuid_view = getChildView("uuid");
uploader_view->setVisible(true);
uploadtime_view->setVisible(true);
uuid_view->setVisible(true);
getChildView("openprofile")->setVisible(true);
getChildView("copyuuid")->setVisible(true);
getChildView("uploader_label")->setVisible(true);
getChildView("upload_time_label")->setVisible(true);
getChildView("uuid_label")->setVisible(true);
additional_height = uploader_view->getRect().getHeight() + uploadtime_view->getRect().getHeight() + uuid_view->getRect().getHeight() + 3 * PREVIEW_VPAD;
}
//
// If this is 100% correct???
S32 floater_target_width = mImage->getFullWidth() + 2 * (LLPANEL_BORDER_WIDTH + PREVIEW_PAD) + PREVIEW_RESIZE_HANDLE_SIZE;;
S32 floater_target_height = mImage->getFullHeight() + 3 * CLIENT_RECT_VPAD + PREVIEW_BORDER + dimensions_panel->getRect().mTop + getChildView("desc")->getRect().getHeight() + additional_height;
// Scale down by factor 0.5 if image would exceed viewer window
if (gViewerWindow->getWindowWidthRaw() < floater_target_width || gViewerWindow->getWindowHeightRaw() < floater_target_height)
{
floater_target_width = mImage->getFullWidth() / 2 + 2 * (LLPANEL_BORDER_WIDTH + PREVIEW_PAD) + PREVIEW_RESIZE_HANDLE_SIZE;;
floater_target_height = mImage->getFullHeight() / 2 + 3 * CLIENT_RECT_VPAD + PREVIEW_BORDER + dimensions_panel->getRect().mTop + getChildView("desc")->getRect().getHeight() + additional_height;
}
// Preserve minimum floater size
floater_target_width = llmax(floater_target_width, getMinWidth());
floater_target_height = llmax(floater_target_height, getMinHeight());
// Resize floater
LLMultiFloater* host = getHost();
if (host)
{
S32 old_height = host->getRect().getHeight();
host->reshape(getMinWidth(), getMinHeight());
host->translate(0, old_height - getMinHeight());
host->growToFit(floater_target_width, floater_target_height);
}
reshape(floater_target_width, floater_target_height);
gFloaterView->adjustToFitScreen(this, false);
// : Show image at full resolution if possible
LLRect dim_rect(mDimensionsText->getRect());
LLRect aspect_label_rect(mAspectRatioText->getRect());
mAspectRatioText->setVisible( dim_rect.mRight < aspect_label_rect.mLeft);
// Asset UUID
if (mIsFullPerm)
{
LLView* copy_uuid_btn = getChildView("copyuuid");
copy_uuid_btn->setVisible(true);
copy_uuid_btn->setEnabled(true);
}
//
}
}
// texture comment metadata reader
void LLPreviewTexture::callbackLoadName(const LLUUID& agent_id, const LLAvatarName& av_name)
{
if (mAvatarNameCallbackConnection.connected())
{
mAvatarNameCallbackConnection.disconnect();
}
if (findChild("uploader_date_time"))
{
mUploaderDateTime.setArg("[UPLOADER]", av_name.getCompleteName());
getChild("uploader_date_time")->setText(mUploaderDateTime.getString());
mUpdateDimensions = true;
}
else if (findChild("uploader"))
{
// AnsaStorm skin
childSetValue("uploader", LLSD( av_name.getCompleteName() ) );
}
}
void LLPreviewTexture::onButtonClickProfile()
{
if (mImage && (mImage->mComment.find("a") != mImage->mComment.end()))
{
LLUUID id = LLUUID(mImage->mComment["a"]);
LLAvatarActions::showProfile(id);
}
}
void LLPreviewTexture::onButtonClickUUID()
{
std::string uuid = mImageID.asString();
LLClipboard::instance().copyToClipboard(utf8str_to_wstring(uuid), 0, static_cast(uuid.size()));
}
/* static */
void LLPreviewTexture::onTextureLoaded(bool success, LLViewerFetchedTexture* src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata)
{
LLPreviewTexture* self = (LLPreviewTexture*)userdata;
self->mUpdateDimensions = true;
}
// texture comment decoder
// Return true if everything went fine, false if we somewhat modified the ratio as we bumped on border values
bool LLPreviewTexture::setAspectRatio(const F32 width, const F32 height)
{
mUpdateDimensions = true;
// We don't allow negative width or height. Also, if height is positive but too small, we reset to default
// A default 0.f value for mAspectRatio means "unconstrained" in the rest of the code
if ((width <= 0.f) || (height <= F_APPROXIMATELY_ZERO))
{
mAspectRatio = 0.f;
return false;
}
// Compute and store the ratio
F32 ratio = width / height;
mAspectRatio = llclamp(ratio, PREVIEW_TEXTURE_MIN_ASPECT, PREVIEW_TEXTURE_MAX_ASPECT);
// Return false if we clamped the value, true otherwise
return (ratio == mAspectRatio);
}
void LLPreviewTexture::onAspectRatioCommit(LLUICtrl* ctrl, void* userdata)
{
LLPreviewTexture* self = (LLPreviewTexture*) userdata;
std::string ratio(ctrl->getValue().asString());
std::string::size_type separator(ratio.find_first_of(":/\\"));
if (std::string::npos == separator) {
// If there's no separator assume we want an unconstrained ratio
self->setAspectRatio( 0.f, 0.f );
return;
}
F32 width, height;
std::istringstream numerator(ratio.substr(0, separator));
std::istringstream denominator(ratio.substr(separator + 1));
numerator >> width;
denominator >> height;
self->setAspectRatio( width, height );
}
void LLPreviewTexture::loadAsset()
{
// FIRE-30559 texture fetch speedup for user previews (based on patches from Oren Hurvitz)
// mImage = LLViewerTextureManager::getFetchedTexture(mImageID, FTT_DEFAULT, MIPMAP_TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE);
// mImageOldBoostLevel = mImage->getBoostLevel();
// mImage->setBoostLevel(LLGLTexture::BOOST_PREVIEW);
mImage = LLViewerTextureManager::getFetchedTexture(mImageID, FTT_DEFAULT, MIPMAP_TRUE, LLGLTexture::BOOST_PREVIEW, LLViewerTexture::LOD_TEXTURE);
mImageOldBoostLevel = LLGLTexture::BOOST_NONE;
//
mImage->forceToSaveRawImage(0) ;
// texture comment decoder
mImage->setLoadedCallback(LLPreviewTexture::onTextureLoaded, 0, true, false, this, &mCallbackTextureList);
//
mAssetStatus = PREVIEW_ASSET_LOADING;
mUpdateDimensions = true;
updateDimensions();
getChildView("save_tex_btn")->setEnabled(canSaveAs());
getChildView("save_tex_btn")->setVisible(canSaveAs());
if (mObjectUUID.notNull())
{
// check that we can copy inworld items into inventory
getChildView("Keep")->setEnabled(mIsCopyable);
}
else
{
// check that we can remove item
bool source_library = gInventory.isObjectDescendentOf(mItemUUID, gInventory.getLibraryRootFolderID());
if (source_library)
{
getChildView("Discard")->setEnabled(false);
}
}
}
LLPreview::EAssetStatus LLPreviewTexture::getAssetStatus()
{
if (mImage.notNull() && (mImage->getFullWidth() * mImage->getFullHeight() > 0))
{
mAssetStatus = PREVIEW_ASSET_LOADED;
}
return mAssetStatus;
}
void LLPreviewTexture::adjustAspectRatio()
{
S32 w = mImage->getFullWidth();
S32 h = mImage->getFullHeight();
// Determine aspect ratio of the image
S32 tmp;
while (h != 0)
{
tmp = w % h;
w = h;
h = tmp;
}
S32 divisor = w;
S32 num = mImage->getFullWidth() / divisor;
S32 denom = mImage->getFullHeight() / divisor;
if (setAspectRatio((F32)num, (F32)denom))
{
// Select corresponding ratio entry in the combo list
LLComboBox* combo = getChild("combo_aspect_ratio");
if (combo)
{
std::ostringstream ratio;
ratio << num << ":" << denom;
std::vector::const_iterator found = std::find(mRatiosList.begin(), mRatiosList.end(), ratio.str());
if (found == mRatiosList.end())
{
// No existing ratio found, create an element that will show image at original ratio
populateRatioList(); // makes sure previous custom ratio is cleared
std::string ratio = std::to_string(num)+":" + std::to_string(denom);
mRatiosList.push_back(ratio);
combo->add(ratio);
combo->setCurrentByIndex(static_cast(mRatiosList.size()) - 1);
}
else
{
combo->setCurrentByIndex((S32)(found - mRatiosList.begin()));
}
}
}
else
{
// Aspect ratio was set to unconstrained or was clamped
LLComboBox* combo = getChild("combo_aspect_ratio");
if (combo)
{
combo->setCurrentByIndex(0); //unconstrained
}
}
mUpdateDimensions = true;
}
void LLPreviewTexture::updateImageID()
{
const LLViewerInventoryItem *item = static_cast(getItem());
if(item)
{
mImageID = item->getAssetUUID();
// here's the old logic...
//mShowKeepDiscard = item->getPermissions().getCreator() != gAgent.getID();
// here's the new logic... 'cos we hate disappearing buttons.
mShowKeepDiscard = true;
mCopyToInv = false;
LLPermissions perm(item->getPermissions());
mIsCopyable = perm.allowCopyBy(gAgent.getID(), gAgent.getGroupID()) && perm.allowTransferTo(gAgent.getID());
mIsFullPerm = item->checkPermissionsSet(PERM_ITEM_UNRESTRICTED);
}
else // not an item, assume it's an asset id
{
mImageID = mItemUUID;
mShowKeepDiscard = false;
mCopyToInv = true;
mIsCopyable = true;
mIsFullPerm = true;
}
}
/* virtual */
void LLPreviewTexture::setObjectID(const LLUUID& object_id)
{
mObjectUUID = object_id;
const LLUUID old_image_id = mImageID;
// Update what image we're pointing to, such as if we just specified the mObjectID
// that this mItemID is part of.
updateImageID();
// If the imageID has changed, start over and reload the new image.
if (mImageID != old_image_id)
{
mAssetStatus = PREVIEW_ASSET_UNLOADED;
loadAsset();
}
refreshFromItem();
}
// FIRE-20150: Add refresh button to texture preview
void LLPreviewTexture::onButtonRefresh()
{
destroy_texture(mImageID);
}
//
// FIRE-22851: Show texture "Save as" file picker subsequently instead all at once
//static
void LLPreviewTexture::saveMultiple(uuid_vec_t ids)
{
if (ids.empty())
{
return;
}
LLUUID next_id = ids.front();
ids.erase(ids.begin());
LLPreviewTexture* preview_texture = LLFloaterReg::getTypedInstance("preview_texture", next_id);
if (preview_texture)
{
preview_texture->openToSave();
preview_texture->saveAs(ids);
}
}
//