diff options
-rw-r--r-- | doc/contributions.txt | 2 | ||||
-rw-r--r-- | indra/newview/CMakeLists.txt | 2 | ||||
-rw-r--r-- | indra/newview/lllocalbitmaps.cpp | 925 | ||||
-rw-r--r-- | indra/newview/lllocalbitmaps.h | 130 | ||||
-rw-r--r-- | indra/newview/lltexturectrl.cpp | 172 | ||||
-rw-r--r-- | indra/newview/lltexturectrl.h | 2 | ||||
-rw-r--r-- | indra/newview/llviewertexturelist.h | 1 | ||||
-rw-r--r-- | indra/newview/llwearable.cpp | 14 | ||||
-rw-r--r-- | indra/newview/llwearable.h | 1 | ||||
-rw-r--r-- | indra/newview/skins/default/xui/en/floater_texture_ctrl.xml | 192 |
10 files changed, 1381 insertions, 60 deletions
diff --git a/doc/contributions.txt b/doc/contributions.txt index c5db396c97..a024815b8e 100644 --- a/doc/contributions.txt +++ b/doc/contributions.txt @@ -1171,6 +1171,8 @@ Unlikely Quintessa UsikuFarasi Kanarik Vadim Bigbear VWR-2681 +Vaalith Jinn + STORM-64 Vector Hastings VWR-8726 Veritas Raymaker diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index f85b943c70..54134fb610 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -303,6 +303,7 @@ set(viewer_SOURCE_FILES lllistbrowser.cpp lllistcontextmenu.cpp lllistview.cpp + lllocalbitmaps.cpp lllocaltextureobject.cpp lllocationhistory.cpp lllocationinputctrl.cpp @@ -859,6 +860,7 @@ set(viewer_HEADER_FILES lllistbrowser.h lllistcontextmenu.h lllistview.h + lllocalbitmaps.h lllocaltextureobject.h lllocationhistory.h lllocationinputctrl.h diff --git a/indra/newview/lllocalbitmaps.cpp b/indra/newview/lllocalbitmaps.cpp new file mode 100644 index 0000000000..b96fc06226 --- /dev/null +++ b/indra/newview/lllocalbitmaps.cpp @@ -0,0 +1,925 @@ +/** + * @file lllocalbitmaps.cpp + * @author Vaalith Jinn + * @brief Local Bitmaps source + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, 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$ + */ + +/* precompiled headers */ +#include "llviewerprecompiledheaders.h" + +/* own header */ +#include "lllocalbitmaps.h" + +/* boost: will not compile unless equivalent is undef'd, beware. */ +#ifdef equivalent +#undef equivalent +#endif +#include <boost/filesystem.hpp> + +/* image compression headers. */ +#include "llimagebmp.h" +#include "llimagetga.h" +#include "llimagejpeg.h" +#include "llimagepng.h" + +/* time headers */ +#include <time.h> +#include <ctime> + +/* misc headers */ +#include "llscrolllistctrl.h" +#include "llfilepicker.h" +#include "llviewertexturelist.h" +#include "llviewerobjectlist.h" +#include "llviewerobject.h" +#include "llface.h" +#include "llvoavatarself.h" +#include "llwearable.h" +#include "llagentwearables.h" +#include "lltexlayerparams.h" +#include "llvovolume.h" + +/*=======================================*/ +/* Formal declarations, constants, etc. */ +/*=======================================*/ +std::list<LLLocalBitmap*> LLLocalBitmapMgr::sBitmapList; +LLLocalBitmapTimer LLLocalBitmapMgr::sTimer; +bool LLLocalBitmapMgr::sNeedsRebake; + +static const F32 LL_LOCAL_TIMER_HEARTBEAT = 3.0; +static const BOOL LL_LOCAL_USE_MIPMAPS = true; +static const S32 LL_LOCAL_DISCARD_LEVEL = 0; +static const U32 LL_LOCAL_TEXLAYER_FOR_IDX = 0; +static const bool LL_LOCAL_SLAM_FOR_DEBUG = true; +static const bool LL_LOCAL_REPLACE_ON_DEL = true; +static const S32 LL_LOCAL_UPDATE_RETRIES = 5; + +/*=======================================*/ +/* LLLocalBitmap: unit class */ +/*=======================================*/ +LLLocalBitmap::LLLocalBitmap(std::string filename) + : mFilename(filename) + , mShortName(gDirUtilp->getBaseFileName(filename, true)) + , mValid(false) + , mLastModified() + , mLinkStatus(LS_ON) + , mUpdateRetries(LL_LOCAL_UPDATE_RETRIES) +{ + mTrackingID.generate(); + + /* extension */ + std::string temp_exten = gDirUtilp->getExtension(mFilename); + + if (temp_exten == "bmp") + { + mExtension = ET_IMG_BMP; + } + else if (temp_exten == "tga") + { + mExtension = ET_IMG_TGA; + } + else if (temp_exten == "jpg" || temp_exten == "jpeg") + { + mExtension = ET_IMG_JPG; + } + else if (temp_exten == "png") + { + mExtension = ET_IMG_PNG; + } + else + { + llwarns << "File of no valid extension given, local bitmap creation aborted." << "\n" + << "Filename: " << mFilename << llendl; + return; // no valid extension. + } + + /* next phase of unit creation is nearly the same as an update cycle. + true means the unit's update is running for the first time so it will not check + for current usage nor will it attempt to replace the old, non existent image */ + mValid = updateSelf(true); +} + +LLLocalBitmap::~LLLocalBitmap() +{ + // replace IDs with defaults, if set to do so. + if(LL_LOCAL_REPLACE_ON_DEL) + { + replaceIDs(mWorldID, IMG_DEFAULT); + LLLocalBitmapMgr::doRebake(); + } + + // delete self from gimagelist + LLViewerFetchedTexture* image = gTextureList.findImage(mWorldID); + gTextureList.deleteImage(image); + + if (image) + { + image->unref(); + } +} + +/* accessors */ +std::string LLLocalBitmap::getFilename() +{ + return mFilename; +} + +std::string LLLocalBitmap::getShortName() +{ + return mShortName; +} + +LLUUID LLLocalBitmap::getTrackingID() +{ + return mTrackingID; +} + +LLUUID LLLocalBitmap::getWorldID() +{ + return mWorldID; +} + +bool LLLocalBitmap::getValid() +{ + return mValid; +} + +/* update functions */ +bool LLLocalBitmap::updateSelf(bool first_update) +{ + bool updated = false; + + if (mLinkStatus == LS_ON) + { + // verifying that the file exists + if (gDirUtilp->fileExists(mFilename)) + { + // verifying that the file has indeed been modified + const std::time_t temp_time = boost::filesystem::last_write_time(boost::filesystem::path(mFilename)); + LLSD new_last_modified = asctime(localtime(&temp_time)); + + if (mLastModified.asString() != new_last_modified.asString()) + { + /* loading the image file and decoding it, here is a critical point which, + if fails, invalidates the whole update (or unit creation) process. */ + LLPointer<LLImageRaw> raw_image = new LLImageRaw(); + if (decodeBitmap(raw_image)) + { + // decode is successful, we can safely proceed. + LLUUID old_id = LLUUID::null; + if (!first_update && !mWorldID.isNull()) + { + old_id = mWorldID; + } + mWorldID.generate(); + mLastModified = new_last_modified; + + LLPointer<LLViewerFetchedTexture> texture = new LLViewerFetchedTexture + ("file://"+mFilename, mWorldID, LL_LOCAL_USE_MIPMAPS); + + texture->createGLTexture(LL_LOCAL_DISCARD_LEVEL, raw_image); + texture->setCachedRawImage(LL_LOCAL_DISCARD_LEVEL, raw_image); + texture->ref(); + + gTextureList.addImage(texture); + + if (!first_update) + { + // seek out everything old_id uses and replace it with mWorldID + replaceIDs(old_id, mWorldID); + + // remove old_id from gimagelist + LLViewerFetchedTexture* image = gTextureList.findImage(old_id); + gTextureList.deleteImage(image); + image->unref(); + } + + mUpdateRetries = LL_LOCAL_UPDATE_RETRIES; + updated = true; + } + + // if decoding failed, we get here and it will attempt to decode it in the next cycles + // until mUpdateRetries runs out. this is done because some software lock the bitmap while writing to it + else + { + if (mUpdateRetries) + { + mUpdateRetries--; + } + else + { + llwarns << "During the update process the following file was found" << "\n" + << "but could not be opened or decoded for " << LL_LOCAL_UPDATE_RETRIES << " attempts." << "\n" + << "Filename: " << mFilename << "\n" + << "Disabling further update attempts for this file." << llendl; + + mLinkStatus = LS_BROKEN; + } + } + } + + } // end if file exists + + else + { + llwarns << "During the update process, the following file was not found." << "\n" + << "Filename: " << mFilename << "\n" + << "Disabling further update attempts for this file." << llendl; + + mLinkStatus = LS_BROKEN; + } + } + + return updated; +} + +bool LLLocalBitmap::decodeBitmap(LLPointer<LLImageRaw> rawimg) +{ + bool decode_successful = false; + + switch (mExtension) + { + case ET_IMG_BMP: + { + LLPointer<LLImageBMP> bmp_image = new LLImageBMP; + if (bmp_image->load(mFilename) && bmp_image->decode(rawimg, 0.0f)) + { + rawimg->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); + decode_successful = true; + } + break; + } + + case ET_IMG_TGA: + { + LLPointer<LLImageTGA> tga_image = new LLImageTGA; + if ((tga_image->load(mFilename) && tga_image->decode(rawimg)) + && ((tga_image->getComponents() == 3) || (tga_image->getComponents() == 4))) + { + rawimg->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); + decode_successful = true; + } + break; + } + + case ET_IMG_JPG: + { + LLPointer<LLImageJPEG> jpeg_image = new LLImageJPEG; + if (jpeg_image->load(mFilename) && jpeg_image->decode(rawimg, 0.0f)) + { + rawimg->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); + decode_successful = true; + } + break; + } + + case ET_IMG_PNG: + { + LLPointer<LLImagePNG> png_image = new LLImagePNG; + if (png_image->load(mFilename) && png_image->decode(rawimg, 0.0f)) + { + rawimg->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); + decode_successful = true; + } + break; + } + + default: + { + // separating this into -several- llwarns calls because in the extremely unlikely case that this happens + // accessing mFilename and any other object properties might very well crash the viewer. + // getting here should be impossible, or there's been a pretty serious bug. + + llwarns << "During a decode attempt, the following local bitmap had no properly assigned extension." << llendl; + llwarns << "Filename: " << mFilename << llendl; + llwarns << "Disabling further update attempts for this file." << llendl; + mLinkStatus = LS_BROKEN; + } + } + + return decode_successful; +} + +void LLLocalBitmap::replaceIDs(LLUUID old_id, LLUUID new_id) +{ + // checking for misuse. + if (old_id == new_id) + { + llinfos << "An attempt was made to replace a texture with itself. (matching UUIDs)" << "\n" + << "Texture UUID: " << old_id.asString() << llendl; + return; + } + + updateUserPrims(old_id, new_id); + updateUserSculpts(old_id, new_id); // isn't there supposed to be an IMG_DEFAULT_SCULPT or something? + + // default safeguard image for layers + if( new_id == IMG_DEFAULT ) + { + new_id = IMG_DEFAULT_AVATAR; + } + + /* It doesn't actually update all of those, it merely checks if any of them + contain the referenced ID and if so, updates. */ + updateUserLayers(old_id, new_id, LLWearableType::WT_ALPHA); + updateUserLayers(old_id, new_id, LLWearableType::WT_EYES); + updateUserLayers(old_id, new_id, LLWearableType::WT_GLOVES); + updateUserLayers(old_id, new_id, LLWearableType::WT_JACKET); + updateUserLayers(old_id, new_id, LLWearableType::WT_PANTS); + updateUserLayers(old_id, new_id, LLWearableType::WT_SHIRT); + updateUserLayers(old_id, new_id, LLWearableType::WT_SHOES); + updateUserLayers(old_id, new_id, LLWearableType::WT_SKIN); + updateUserLayers(old_id, new_id, LLWearableType::WT_SKIRT); + updateUserLayers(old_id, new_id, LLWearableType::WT_SOCKS); + updateUserLayers(old_id, new_id, LLWearableType::WT_TATTOO); + updateUserLayers(old_id, new_id, LLWearableType::WT_UNDERPANTS); + updateUserLayers(old_id, new_id, LLWearableType::WT_UNDERSHIRT); +} + +// this function sorts the faces from a getFaceList[getNumFaces] into a list of objects +// in order to prevent multiple sendTEUpdate calls per object during updateUserPrims +std::vector<LLViewerObject*> LLLocalBitmap::prepUpdateObjects(LLUUID old_id) +{ + std::vector<LLViewerObject*> obj_list; + LLViewerFetchedTexture* old_texture = gTextureList.findImage(old_id); + + for(U32 face_iterator = 0; face_iterator < old_texture->getNumFaces(); face_iterator++) + { + // getting an object from a face + LLFace* face_to_object = (*old_texture->getFaceList())[face_iterator]; + + if(face_to_object) + { + LLViewerObject* affected_object = face_to_object->getViewerObject(); + + if(affected_object) + { + + // we have an object, we'll take it's UUID and compare it to + // whatever we already have in the returnable object list. + // if there is a match - we do not add (to prevent duplicates) + LLUUID mainlist_obj_id = affected_object->getID(); + bool add_object = true; + + // begin looking for duplicates + std::vector<LLViewerObject*>::iterator objlist_iter = obj_list.begin(); + for(; (objlist_iter != obj_list.end()) && add_object; objlist_iter++) + { + LLViewerObject* obj = *objlist_iter; + if (obj->getID() == mainlist_obj_id) + { + add_object = false; // duplicate found. + } + } + // end looking for duplicates + + if(add_object) + { + obj_list.push_back(affected_object); + } + + } + + } + + } // end of face-iterating for() + + return obj_list; +} + +void LLLocalBitmap::updateUserPrims(LLUUID old_id, LLUUID new_id) +{ + std::vector<LLViewerObject*> objectlist = prepUpdateObjects(old_id); + + for(std::vector<LLViewerObject*>::iterator object_iterator = objectlist.begin(); + object_iterator != objectlist.end(); object_iterator++) + { + LLViewerObject* object = *object_iterator; + + if(object) + { + bool update_obj = false; + S32 num_faces = object->getNumFaces(); + + for (U8 face_iter = 0; face_iter < num_faces; face_iter++) + { + if (object->mDrawable) + { + LLFace* face = object->mDrawable->getFace(face_iter); + if (face && face->getTexture() && face->getTexture()->getID() == old_id) + { + object->setTEImage(face_iter, LLViewerTextureManager::getFetchedTexture + (new_id, TRUE, LLViewerTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE)); + + update_obj = true; + } + } + } + + if (update_obj) + { + object->sendTEUpdate(); + } + } + } + +} + +void LLLocalBitmap::updateUserSculpts(LLUUID old_id, LLUUID new_id) +{ + LLViewerFetchedTexture* old_texture = gTextureList.findImage(old_id); + for(U32 volume_iter = 0; volume_iter < old_texture->getNumVolumes(); volume_iter++) + { + LLVOVolume* volume_to_object = (*old_texture->getVolumeList())[volume_iter]; + LLViewerObject* object = (LLViewerObject*)volume_to_object; + + if(object) + { + if (object->isSculpted() && object->getVolume() && + object->getVolume()->getParams().getSculptID() == old_id) + { + LLSculptParams* old_params = (LLSculptParams*)object->getParameterEntry(LLNetworkData::PARAMS_SCULPT); + LLSculptParams new_params(*old_params); + new_params.setSculptTexture(new_id); + object->setParameterEntry(LLNetworkData::PARAMS_SCULPT, new_params, TRUE); + } + } + } +} + +void LLLocalBitmap::updateUserLayers(LLUUID old_id, LLUUID new_id, LLWearableType::EType type) +{ + U32 count = gAgentWearables.getWearableCount(type); + for(U32 wearable_iter = 0; wearable_iter < count; wearable_iter++) + { + LLWearable* wearable = gAgentWearables.getWearable(type, wearable_iter); + if (wearable) + { + std::vector<LLLocalTextureObject*> texture_list = wearable->getLocalTextureListSeq(); + for(std::vector<LLLocalTextureObject*>::iterator texture_iter = texture_list.begin(); + texture_iter != texture_list.end(); texture_iter++) + { + LLLocalTextureObject* lto = *texture_iter; + + if (lto && lto->getID() == old_id) + { + U32 local_texlayer_index = 0; /* can't keep that as static const, gives errors, so i'm leaving this var here */ + LLVOAvatarDefines::EBakedTextureIndex baked_texind = + lto->getTexLayer(local_texlayer_index)->getTexLayerSet()->getBakedTexIndex(); + + LLVOAvatarDefines::ETextureIndex reg_texind = getTexIndex(type, baked_texind); + if (reg_texind != LLVOAvatarDefines::TEX_NUM_INDICES) + { + U32 index = gAgentWearables.getWearableIndex(wearable); + gAgentAvatarp->setLocalTexture(reg_texind, gTextureList.getImage(new_id), FALSE, index); + gAgentAvatarp->wearableUpdated(type, FALSE); + + /* telling the manager to rebake once update cycle is fully done */ + LLLocalBitmapMgr::setNeedsRebake(); + } + + } + } + } + } +} + +LLVOAvatarDefines::ETextureIndex LLLocalBitmap::getTexIndex( + LLWearableType::EType type, LLVOAvatarDefines::EBakedTextureIndex baked_texind) +{ + LLVOAvatarDefines::ETextureIndex result = LLVOAvatarDefines::TEX_NUM_INDICES; // using as a default/fail return. + + switch(type) + { + case LLWearableType::WT_ALPHA: + { + switch(baked_texind) + { + case LLVOAvatarDefines::BAKED_EYES: + { + result = LLVOAvatarDefines::TEX_EYES_ALPHA; + break; + } + + case LLVOAvatarDefines::BAKED_HAIR: + { + result = LLVOAvatarDefines::TEX_HAIR_ALPHA; + break; + } + + case LLVOAvatarDefines::BAKED_HEAD: + { + result = LLVOAvatarDefines::TEX_HEAD_ALPHA; + break; + } + + case LLVOAvatarDefines::BAKED_LOWER: + { + result = LLVOAvatarDefines::TEX_LOWER_ALPHA; + break; + } + case LLVOAvatarDefines::BAKED_UPPER: + { + result = LLVOAvatarDefines::TEX_UPPER_ALPHA; + break; + } + + default: + { + break; + } + + } + break; + + } + + case LLWearableType::WT_EYES: + { + if (baked_texind == LLVOAvatarDefines::BAKED_EYES) + { + result = LLVOAvatarDefines::TEX_EYES_IRIS; + } + + break; + } + + case LLWearableType::WT_GLOVES: + { + if (baked_texind == LLVOAvatarDefines::BAKED_UPPER) + { + result = LLVOAvatarDefines::TEX_UPPER_GLOVES; + } + + break; + } + + case LLWearableType::WT_JACKET: + { + if (baked_texind == LLVOAvatarDefines::BAKED_LOWER) + { + result = LLVOAvatarDefines::TEX_LOWER_JACKET; + } + else if (baked_texind == LLVOAvatarDefines::BAKED_UPPER) + { + result = LLVOAvatarDefines::TEX_UPPER_JACKET; + } + + break; + } + + case LLWearableType::WT_PANTS: + { + if (baked_texind == LLVOAvatarDefines::BAKED_LOWER) + { + result = LLVOAvatarDefines::TEX_LOWER_PANTS; + } + + break; + } + + case LLWearableType::WT_SHIRT: + { + if (baked_texind == LLVOAvatarDefines::BAKED_UPPER) + { + result = LLVOAvatarDefines::TEX_UPPER_SHIRT; + } + + break; + } + + case LLWearableType::WT_SHOES: + { + if (baked_texind == LLVOAvatarDefines::BAKED_LOWER) + { + result = LLVOAvatarDefines::TEX_LOWER_SHOES; + } + + break; + } + + case LLWearableType::WT_SKIN: + { + switch(baked_texind) + { + case LLVOAvatarDefines::BAKED_HEAD: + { + result = LLVOAvatarDefines::TEX_HEAD_BODYPAINT; + break; + } + + case LLVOAvatarDefines::BAKED_LOWER: + { + result = LLVOAvatarDefines::TEX_LOWER_BODYPAINT; + break; + } + case LLVOAvatarDefines::BAKED_UPPER: + { + result = LLVOAvatarDefines::TEX_UPPER_BODYPAINT; + break; + } + + default: + { + break; + } + + } + break; + } + + case LLWearableType::WT_SKIRT: + { + if (baked_texind == LLVOAvatarDefines::BAKED_SKIRT) + { + result = LLVOAvatarDefines::TEX_SKIRT; + } + + break; + } + + case LLWearableType::WT_SOCKS: + { + if (baked_texind == LLVOAvatarDefines::BAKED_LOWER) + { + result = LLVOAvatarDefines::TEX_LOWER_SOCKS; + } + + break; + } + + case LLWearableType::WT_TATTOO: + { + switch(baked_texind) + { + case LLVOAvatarDefines::BAKED_HEAD: + { + result = LLVOAvatarDefines::TEX_HEAD_TATTOO; + break; + } + + case LLVOAvatarDefines::BAKED_LOWER: + { + result = LLVOAvatarDefines::TEX_LOWER_TATTOO; + break; + } + case LLVOAvatarDefines::BAKED_UPPER: + { + result = LLVOAvatarDefines::TEX_UPPER_TATTOO; + break; + } + + default: + { + break; + } + + } + break; + } + + case LLWearableType::WT_UNDERPANTS: + { + if (baked_texind == LLVOAvatarDefines::BAKED_LOWER) + { + result = LLVOAvatarDefines::TEX_LOWER_UNDERPANTS; + } + + break; + } + + case LLWearableType::WT_UNDERSHIRT: + { + if (baked_texind == LLVOAvatarDefines::BAKED_UPPER) + { + result = LLVOAvatarDefines::TEX_UPPER_UNDERSHIRT; + } + + break; + } + + default: + { + llwarns << "Unknown wearable type: " << (int)type << "\n" + << "Baked Texture Index: " << (int)baked_texind << "\n" + << "Filename: " << mFilename << "\n" + << "TrackingID: " << mTrackingID << "\n" + << "InworldID: " << mWorldID << llendl; + } + + } + return result; +} + +/*=======================================*/ +/* LLLocalBitmapTimer: timer class */ +/*=======================================*/ +LLLocalBitmapTimer::LLLocalBitmapTimer() : LLEventTimer(LL_LOCAL_TIMER_HEARTBEAT) +{ +} + +LLLocalBitmapTimer::~LLLocalBitmapTimer() +{ +} + +void LLLocalBitmapTimer::startTimer() +{ + mEventTimer.start(); +} + +void LLLocalBitmapTimer::stopTimer() +{ + mEventTimer.stop(); +} + +bool LLLocalBitmapTimer::isRunning() +{ + return mEventTimer.getStarted(); +} + +BOOL LLLocalBitmapTimer::tick() +{ + LLLocalBitmapMgr::doUpdates(); + return FALSE; +} + +/*=======================================*/ +/* LLLocalBitmapMgr: manager class */ +/*=======================================*/ +LLLocalBitmapMgr::LLLocalBitmapMgr() +{ + // The class is all made of static members, should i even bother instantiating? +} + +LLLocalBitmapMgr::~LLLocalBitmapMgr() +{ +} + +bool LLLocalBitmapMgr::addUnit() +{ + bool add_successful = false; + + LLFilePicker& picker = LLFilePicker::instance(); + if (picker.getMultipleOpenFiles(LLFilePicker::FFLOAD_IMAGE)) + { + sTimer.stopTimer(); + + std::string filename = picker.getFirstFile(); + while(!filename.empty()) + { + LLLocalBitmap* unit = new LLLocalBitmap(filename); + + if (unit->getValid()) + { + sBitmapList.push_back(unit); + add_successful = true; + } + else + { + delete unit; + unit = NULL; + } + + filename = picker.getNextFile(); + } + + sTimer.startTimer(); + } + + return add_successful; +} + +void LLLocalBitmapMgr::delUnit(LLUUID tracking_id) +{ + if (!sBitmapList.empty()) + { + std::vector<LLLocalBitmap*> to_delete; + for (local_list_iter iter = sBitmapList.begin(); iter != sBitmapList.end(); iter++) + { /* finding which ones we want deleted and making a separate list */ + LLLocalBitmap* unit = *iter; + if (unit->getTrackingID() == tracking_id) + { + to_delete.push_back(unit); + } + } + + for(std::vector<LLLocalBitmap*>::iterator del_iter = to_delete.begin(); + del_iter != to_delete.end(); del_iter++) + { /* iterating over a temporary list, hence preserving the iterator validity while deleting. */ + LLLocalBitmap* unit = *del_iter; + sBitmapList.remove(unit); + delete unit; + unit = NULL; + } + } +} + +LLUUID LLLocalBitmapMgr::getWorldID(LLUUID tracking_id) +{ + LLUUID world_id = LLUUID::null; + + for (local_list_iter iter = sBitmapList.begin(); iter != sBitmapList.end(); iter++) + { + LLLocalBitmap* unit = *iter; + if (unit->getTrackingID() == tracking_id) + { + world_id = unit->getWorldID(); + } + } + + return world_id; +} + +std::string LLLocalBitmapMgr::getFilename(LLUUID tracking_id) +{ + std::string filename = ""; + + for (local_list_iter iter = sBitmapList.begin(); iter != sBitmapList.end(); iter++) + { + LLLocalBitmap* unit = *iter; + if (unit->getTrackingID() == tracking_id) + { + filename = unit->getFilename(); + } + } + + return filename; +} + +void LLLocalBitmapMgr::feedScrollList(LLScrollListCtrl* ctrl) +{ + if (ctrl) + { + ctrl->clearRows(); + + if (!sBitmapList.empty()) + { + for (local_list_iter iter = sBitmapList.begin(); + iter != sBitmapList.end(); iter++) + { + LLSD element; + element["columns"][0]["column"] = "unit_name"; + element["columns"][0]["type"] = "text"; + element["columns"][0]["value"] = (*iter)->getShortName(); + + element["columns"][1]["column"] = "unit_id_HIDDEN"; + element["columns"][1]["type"] = "text"; + element["columns"][1]["value"] = (*iter)->getTrackingID(); + + ctrl->addElement(element); + } + } + } + +} + +void LLLocalBitmapMgr::doUpdates() +{ + // preventing theoretical overlap in cases with huge number of loaded images. + sTimer.stopTimer(); + sNeedsRebake = false; + + for (local_list_iter iter = sBitmapList.begin(); iter != sBitmapList.end(); iter++) + { + (*iter)->updateSelf(); + } + + doRebake(); + sTimer.startTimer(); +} + +void LLLocalBitmapMgr::setNeedsRebake() +{ + sNeedsRebake = true; +} + +void LLLocalBitmapMgr::doRebake() +{ /* separated that from doUpdates to insure a rebake can be called separately during deletion */ + if (sNeedsRebake) + { + gAgentAvatarp->forceBakeAllTextures(LL_LOCAL_SLAM_FOR_DEBUG); + sNeedsRebake = false; + } +} + diff --git a/indra/newview/lllocalbitmaps.h b/indra/newview/lllocalbitmaps.h new file mode 100644 index 0000000000..c6d05c1763 --- /dev/null +++ b/indra/newview/lllocalbitmaps.h @@ -0,0 +1,130 @@ +/** + * @file lllocalbitmaps.h + * @author Vaalith Jinn + * @brief Local Bitmaps header + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, 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$ + */ + +#ifndef LL_LOCALBITMAPS_H +#define LL_LOCALBITMAPS_H + +#include "lleventtimer.h" +#include "llwearabletype.h" +#include "llvoavatardefines.h" + +class LLScrollListCtrl; + +class LLLocalBitmap +{ + public: /* main */ + LLLocalBitmap(std::string filename); + ~LLLocalBitmap(); + bool updateSelf(bool first_update = false); + + public: /* accessors */ + std::string getFilename(); + std::string getShortName(); + LLUUID getTrackingID(); + LLUUID getWorldID(); + bool getValid(); + + private: /* maintenance */ + bool decodeBitmap(LLPointer<LLImageRaw> raw); + void replaceIDs(LLUUID old_id, LLUUID new_id); + + private: /* id replacement */ + std::vector<LLViewerObject*> prepUpdateObjects(LLUUID old_id); + void updateUserPrims(LLUUID old_id, LLUUID new_id); + void updateUserSculpts(LLUUID old_id, LLUUID new_id); + void updateUserLayers(LLUUID old_id, LLUUID new_id, LLWearableType::EType type); + LLVOAvatarDefines::ETextureIndex getTexIndex(LLWearableType::EType type, LLVOAvatarDefines::EBakedTextureIndex baked_texind); + + private: /* enums */ + enum ELinkStatus + { + LS_ON, + LS_BROKEN, + }; + + enum EExtension + { + ET_IMG_BMP, + ET_IMG_TGA, + ET_IMG_JPG, + ET_IMG_PNG + }; + + private: /* members */ + std::string mFilename; + std::string mShortName; + LLUUID mTrackingID; + LLUUID mWorldID; + bool mValid; + LLSD mLastModified; + EExtension mExtension; + ELinkStatus mLinkStatus; + S32 mUpdateRetries; + +}; + +class LLLocalBitmapTimer : public LLEventTimer +{ + public: + LLLocalBitmapTimer(); + ~LLLocalBitmapTimer(); + + public: + void startTimer(); + void stopTimer(); + bool isRunning(); + BOOL tick(); + +}; + +class LLLocalBitmapMgr +{ + public: + LLLocalBitmapMgr(); + ~LLLocalBitmapMgr(); + + public: + static bool addUnit(); + static void delUnit(LLUUID tracking_id); + + static LLUUID getWorldID(LLUUID tracking_id); + static std::string getFilename(LLUUID tracking_id); + + static void feedScrollList(LLScrollListCtrl* ctrl); + static void doUpdates(); + static void setNeedsRebake(); + static void doRebake(); + + private: + static std::list<LLLocalBitmap*> sBitmapList; + static LLLocalBitmapTimer sTimer; + static bool sNeedsRebake; + typedef std::list<LLLocalBitmap*>::iterator local_list_iter; +}; + +#endif + diff --git a/indra/newview/lltexturectrl.cpp b/indra/newview/lltexturectrl.cpp index de22f2ae6b..6a64e6d76a 100644 --- a/indra/newview/lltexturectrl.cpp +++ b/indra/newview/lltexturectrl.cpp @@ -67,6 +67,9 @@ #include "lluictrlfactory.h" #include "lltrans.h" +#include "llradiogroup.h" +#include "llfloaterreg.h" +#include "lllocalbitmaps.h" static const S32 HPAD = 4; static const S32 VPAD = 4; @@ -78,6 +81,8 @@ static const F32 CONTEXT_CONE_IN_ALPHA = 0.0f; static const F32 CONTEXT_CONE_OUT_ALPHA = 1.f; static const F32 CONTEXT_FADE_TIME = 0.08f; +static const S32 LOCAL_TRACKING_ID_COLUMN = 1; + //static const char CURRENT_IMAGE_NAME[] = "Current Texture"; //static const char WHITE_IMAGE_NAME[] = "Blank Texture"; //static const char NO_IMAGE_NAME[] = "None"; @@ -142,6 +147,12 @@ public: static void onApplyImmediateCheck(LLUICtrl* ctrl, void* userdata); void onTextureSelect( const LLTextureEntry& te ); + static void onModeSelect(LLUICtrl* ctrl, void *userdata); + static void onBtnAdd(void* userdata); + static void onBtnRemove(void* userdata); + static void onBtnUpload(void* userdata); + static void onLocalScrollCommit(LLUICtrl* ctrl, void* userdata); + protected: LLPointer<LLViewerTexture> mTexturep; LLTextureCtrl* mOwner; @@ -169,8 +180,10 @@ protected: BOOL mNoCopyTextureSelected; F32 mContextConeOpacity; LLSaveFolderState mSavedFolderState; - BOOL mSelectedItemPinned; + + LLRadioGroup* mModeSelector; + LLScrollListCtrl* mLocalScrollCtrl; }; LLFloaterTexturePicker::LLFloaterTexturePicker( @@ -437,6 +450,17 @@ BOOL LLFloaterTexturePicker::postBuild() mInventoryPanel->setSelection(findItemID(mImageAssetID, FALSE), TAKE_FOCUS_NO); } + mModeSelector = getChild<LLRadioGroup>("mode_selection"); + mModeSelector->setCommitCallback(onModeSelect, this); + mModeSelector->setSelectedIndex(0, 0); + + childSetAction("l_add_btn", LLFloaterTexturePicker::onBtnAdd, this); + childSetAction("l_rem_btn", LLFloaterTexturePicker::onBtnRemove, this); + childSetAction("l_upl_btn", LLFloaterTexturePicker::onBtnUpload, this); + + mLocalScrollCtrl = getChild<LLScrollListCtrl>("l_name_list"); + mLocalScrollCtrl->setCommitCallback(onLocalScrollCommit, this); + LLLocalBitmapMgr::feedScrollList(mLocalScrollCtrl); mNoCopyTextureSelected = FALSE; @@ -464,7 +488,6 @@ BOOL LLFloaterTexturePicker::postBuild() // virtual void LLFloaterTexturePicker::draw() { - S32 floater_header_size = getHeaderHeight(); if (mOwner) { // draw cone of context pointing back to texture swatch @@ -554,10 +577,7 @@ void LLFloaterTexturePicker::draw() } // Border - LLRect border( BORDER_PAD, - getRect().getHeight() - floater_header_size - BORDER_PAD, - ((getMinWidth() / 2) - TEXTURE_INVENTORY_PADDING - HPAD) - BORDER_PAD, - BORDER_PAD + FOOTER_HEIGHT + (getRect().getHeight() - getMinHeight())); + LLRect border = getChildView("preview_widget")->getRect(); gl_rect_2d( border, LLColor4::black, FALSE ); @@ -748,7 +768,15 @@ void LLFloaterTexturePicker::onBtnSelect(void* userdata) LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; if (self->mOwner) { - self->mOwner->onFloaterCommit(LLTextureCtrl::TEXTURE_SELECT); + LLUUID local_id = LLUUID::null; + + if (self->mLocalScrollCtrl->getVisible() && !self->mLocalScrollCtrl->getAllSelected().empty()) + { + LLUUID temp_id = self->mLocalScrollCtrl->getFirstSelected()->getColumn(LOCAL_TRACKING_ID_COLUMN)->getValue().asUUID(); + local_id = LLLocalBitmapMgr::getWorldID(temp_id); + } + + self->mOwner->onFloaterCommit(LLTextureCtrl::TEXTURE_SELECT, local_id); } self->closeFloater(); } @@ -792,6 +820,112 @@ void LLFloaterTexturePicker::onSelectionChange(const std::deque<LLFolderViewItem } // static +void LLFloaterTexturePicker::onModeSelect(LLUICtrl* ctrl, void *userdata) +{ + LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; + bool mode = (self->mModeSelector->getSelectedIndex() == 0); + + self->getChild<LLButton>("Default")->setVisible(mode); + self->getChild<LLButton>("Blank")->setVisible(mode); + self->getChild<LLButton>("None")->setVisible(mode); + self->getChild<LLButton>("Pipette")->setVisible(mode); + self->getChild<LLFilterEditor>("inventory search editor")->setVisible(mode); + self->getChild<LLInventoryPanel>("inventory panel")->setVisible(mode); + + /*self->getChild<LLCheckBox>("show_folders_check")->setVisible(mode); + no idea under which conditions the above is even shown, needs testing. */ + + self->getChild<LLButton>("l_add_btn")->setVisible(!mode); + self->getChild<LLButton>("l_rem_btn")->setVisible(!mode); + self->getChild<LLButton>("l_upl_btn")->setVisible(!mode); + self->getChild<LLScrollListCtrl>("l_name_list")->setVisible(!mode); +} + +// static +void LLFloaterTexturePicker::onBtnAdd(void* userdata) +{ + if (LLLocalBitmapMgr::addUnit() == true) + { + LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; + LLLocalBitmapMgr::feedScrollList(self->mLocalScrollCtrl); + } +} + +// static +void LLFloaterTexturePicker::onBtnRemove(void* userdata) +{ + LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; + std::vector<LLScrollListItem*> selected_items = self->mLocalScrollCtrl->getAllSelected(); + + if (!selected_items.empty()) + { + for(std::vector<LLScrollListItem*>::iterator iter = selected_items.begin(); + iter != selected_items.end(); iter++) + { + LLScrollListItem* list_item = *iter; + if (list_item) + { + LLUUID tracking_id = list_item->getColumn(LOCAL_TRACKING_ID_COLUMN)->getValue().asUUID(); + LLLocalBitmapMgr::delUnit(tracking_id); + } + } + + self->getChild<LLButton>("l_rem_btn")->setEnabled(false); + self->getChild<LLButton>("l_upl_btn")->setEnabled(false); + LLLocalBitmapMgr::feedScrollList(self->mLocalScrollCtrl); + } + +} + +// static +void LLFloaterTexturePicker::onBtnUpload(void* userdata) +{ + LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; + std::vector<LLScrollListItem*> selected_items = self->mLocalScrollCtrl->getAllSelected(); + + if (selected_items.empty()) + { + return; + } + + /* currently only allows uploading one by one, picks the first item from the selection list. (not the vector!) + in the future, it might be a good idea to check the vector size and if more than one units is selected - opt for multi-image upload. */ + + LLUUID tracking_id = (LLUUID)self->mLocalScrollCtrl->getSelectedItemLabel(LOCAL_TRACKING_ID_COLUMN); + std::string filename = LLLocalBitmapMgr::getFilename(tracking_id); + + if (!filename.empty()) + { + LLFloaterReg::showInstance("upload_image", LLSD(filename)); + } + +} + +//static +void LLFloaterTexturePicker::onLocalScrollCommit(LLUICtrl* ctrl, void* userdata) +{ + LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; + std::vector<LLScrollListItem*> selected_items = self->mLocalScrollCtrl->getAllSelected(); + bool has_selection = !selected_items.empty(); + + self->getChild<LLButton>("l_rem_btn")->setEnabled(has_selection); + self->getChild<LLButton>("l_upl_btn")->setEnabled(has_selection && (selected_items.size() < 2)); + /* since multiple-localbitmap upload is not implemented, upl button gets disabled if more than one is selected. */ + + if (has_selection) + { + LLUUID tracking_id = (LLUUID)self->mLocalScrollCtrl->getSelectedItemLabel(LOCAL_TRACKING_ID_COLUMN); + LLUUID inworld_id = LLLocalBitmapMgr::getWorldID(tracking_id); + self->mOwner->setImageAssetID(inworld_id); + + if (self->childGetValue("apply_immediate_check").asBoolean()) + { + self->mOwner->onFloaterCommit(LLTextureCtrl::TEXTURE_CHANGE, inworld_id); + } + } +} + +// static void LLFloaterTexturePicker::onShowFolders(LLUICtrl* ctrl, void *user_data) { LLCheckBoxCtrl* check_box = (LLCheckBoxCtrl*)ctrl; @@ -1126,7 +1260,7 @@ void LLTextureCtrl::onFloaterClose() mFloaterHandle.markDead(); } -void LLTextureCtrl::onFloaterCommit(ETexturePickOp op) +void LLTextureCtrl::onFloaterCommit(ETexturePickOp op, LLUUID id) { LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); @@ -1139,14 +1273,24 @@ void LLTextureCtrl::onFloaterCommit(ETexturePickOp op) // (i.e. op == TEXTURE_SELECT) or texture changes via DnD. else if (mCommitOnSelection || op == TEXTURE_SELECT) mViewModel->setDirty(); // *TODO: shouldn't we be using setValue() here? - - if( floaterp->isDirty() ) + + if(floaterp->isDirty() || id.notNull()) // mModelView->setDirty does not work. { setTentative( FALSE ); - mImageItemID = floaterp->findItemID(floaterp->getAssetID(), FALSE); - lldebugs << "mImageItemID: " << mImageItemID << llendl; - mImageAssetID = floaterp->getAssetID(); - lldebugs << "mImageAssetID: " << mImageAssetID << llendl; + + if (id.notNull()) + { + mImageItemID = id; + mImageAssetID = id; + } + else + { + mImageItemID = floaterp->findItemID(floaterp->getAssetID(), FALSE); + lldebugs << "mImageItemID: " << mImageItemID << llendl; + mImageAssetID = floaterp->getAssetID(); + lldebugs << "mImageAssetID: " << mImageAssetID << llendl; + } + if (op == TEXTURE_SELECT && mOnSelectCallback) { mOnSelectCallback( this, LLSD() ); diff --git a/indra/newview/lltexturectrl.h b/indra/newview/lltexturectrl.h index b1312d641f..3abe84dcc3 100644 --- a/indra/newview/lltexturectrl.h +++ b/indra/newview/lltexturectrl.h @@ -157,7 +157,7 @@ public: void closeDependentFloater(); void onFloaterClose(); - void onFloaterCommit(ETexturePickOp op); + void onFloaterCommit(ETexturePickOp op, LLUUID id = LLUUID::null); // This call is returned when a drag is detected. Your callback // should return TRUE if the drag is acceptable. diff --git a/indra/newview/llviewertexturelist.h b/indra/newview/llviewertexturelist.h index e0a362596d..b386c73d2a 100644 --- a/indra/newview/llviewertexturelist.h +++ b/indra/newview/llviewertexturelist.h @@ -65,6 +65,7 @@ class LLViewerTextureList friend class LLTextureView; friend class LLViewerTextureManager; + friend class LLLocalBitmap; public: static BOOL createUploadFile(const std::string& filename, const std::string& out_filename, const U8 codec); diff --git a/indra/newview/llwearable.cpp b/indra/newview/llwearable.cpp index 0f7f63061b..402504933c 100644 --- a/indra/newview/llwearable.cpp +++ b/indra/newview/llwearable.cpp @@ -810,6 +810,20 @@ const LLLocalTextureObject* LLWearable::getLocalTextureObject(S32 index) const return NULL; } +std::vector<LLLocalTextureObject*> LLWearable::getLocalTextureListSeq() +{ + std::vector<LLLocalTextureObject*> result; + + for(te_map_t::const_iterator iter = mTEMap.begin(); + iter != mTEMap.end(); iter++) + { + LLLocalTextureObject* lto = iter->second; + result.push_back(lto); + } + + return result; +} + void LLWearable::setLocalTextureObject(S32 index, LLLocalTextureObject <o) { if( mTEMap.find(index) != mTEMap.end() ) diff --git a/indra/newview/llwearable.h b/indra/newview/llwearable.h index fd614ade64..3d8c53a755 100644 --- a/indra/newview/llwearable.h +++ b/indra/newview/llwearable.h @@ -106,6 +106,7 @@ public: LLLocalTextureObject* getLocalTextureObject(S32 index); const LLLocalTextureObject* getLocalTextureObject(S32 index) const; + std::vector<LLLocalTextureObject*> getLocalTextureListSeq(); void setLocalTextureObject(S32 index, LLLocalTextureObject <o); void addVisualParam(LLVisualParam *param); diff --git a/indra/newview/skins/default/xui/en/floater_texture_ctrl.xml b/indra/newview/skins/default/xui/en/floater_texture_ctrl.xml index cad7d72ed7..ffb8b842f0 100644 --- a/indra/newview/skins/default/xui/en/floater_texture_ctrl.xml +++ b/indra/newview/skins/default/xui/en/floater_texture_ctrl.xml @@ -3,15 +3,17 @@ legacy_header_height="18" can_minimize="false" can_resize="true" - height="290" + height="330" layout="topleft" - min_height="290" + min_height="330" min_width="410" name="texture picker" help_topic="texture_picker" title="PICK: TEXTURE" width="410"> - <floater.string + +<!-- top static --> + <floater.string name="choose_picture"> Click to choose a picture </floater.string> @@ -19,6 +21,16 @@ name="pick title"> Pick: </floater.string> + + <view + left="4" + top="20" + name="preview_widget" + height="165" + width="165" + follows="left|top" + /> + <text type="string" length="1" @@ -34,70 +46,94 @@ width="163"> Multiple textures </text> + + <!-- mode selector --> + <radio_group + control_name="mode_selection" + height="20" + layout="topleft" + left="18" + top_pad="80" + name="mode_selection" + follows="left|top"> + <radio_item + label="Inventory" + name="inventory" + top_delta="20" + layout="topleft" + height="16" + left="0" + value="0" + width="80" /> + <radio_item + label="Local" + left_pad="0" + layout="topleft" + top_delta="0" + height="16" + name="local" + value="1" + width="75" /> + </radio_group> + <!-- --> + <text type="string" length="1" follows="left|top" height="14" layout="topleft" - left_delta="0" + left_delta="-12" name="unknown" - top_pad="80" - width="163"> + top_pad="4" + width=""> Size: [DIMENSIONS] </text> + +<!-- middle: inventory mode --> + <button enabled="false" - follows="left|bottom" - height="20" + follows="left|top" + height="18" label="Default" label_selected="Default" layout="topleft" - left_delta="0" name="Default" - top_pad="4" - width="80" /> + width="73" + left="94" + top="215"/> <button + follows="left|top" + height="20" + label="Blank" + label_selected="Blank" + layout="topleft" + left_delta="0" + name="Blank" + top_pad="5" + width="73" /> + <button enabled="false" - follows="left|bottom" + follows="left|top" height="20" label="None" label_selected="None" layout="topleft" - left_pad="4" + left_delta="0" name="None" - top_delta="0" - width="80" /> - <button - follows="left|bottom" - height="20" - label="Blank" - label_selected="Blank" - layout="topleft" - left="4" - name="Blank" top_pad="5" - width="80" /> - <button - follows="left|bottom" + width="73" /> + <button + follows="left|top" height="28" image_selected="eye_button_active.tga" image_unselected="eye_button_inactive.tga" layout="topleft" - left_pad="50" - top_delta="3" + left_delta="-80" + top_delta="-25" name="Pipette" width="28" /> - <check_box - follows="left|bottom" - height="20" - initial_value="true" - label="Apply now" - layout="topleft" - left="4" - name="apply_immediate_check" - top="262" - width="120" /> <filter_editor follows="left|top|right" height="23" @@ -113,7 +149,7 @@ bg_alpha_color="DkGray2" border="false" follows="all" - height="200" + height="233" layout="topleft" left_delta="0" name="inventory panel" @@ -128,23 +164,89 @@ top_pad="0" left_delta="-3" width="200" /> - <button - follows="right|bottom" + +<!-- middle: local mode --> + <button + follows="left|top" + height="18" + label="Add" + label_selected="Add" + layout="topleft" + left="94" + top="215" + name="l_add_btn" + width="73" + visible="false"/> + <button + enabled="false" + follows="left|top" + height="20" + label="Remove" + label_selected="Remove" + layout="topleft" + left_delta="0" + name="l_rem_btn" + top_pad="5" + width="73" + visible="false"/> + <button + enabled="false" + follows="left|top" + height="20" + label="Upload" + label_selected="Upload" + layout="topleft" + left_delta="0" + name="l_upl_btn" + top_pad="5" + width="73" + visible="false"/> + <scroll_list + name="l_name_list" + left="170" + top="22" + width="235" + height="260" + follows="left|top|right|bottom" + column_padding="0" + can_resize="false" + draw_heading="true" + multi_select="true" + search_column="1" + visible="false"> + <column name="unit_name" label="Name" dynamicwidth="true" /> + <column name="unit_id_HIDDEN" label="ID" width="0" /> + </scroll_list> + +<!-- bottom static --> + <button + follows="bottom" height="20" label="OK" label_selected="OK" layout="topleft" - right="-120" + left="95" + top="-30" name="Select" width="100" /> <button - follows="right|bottom" + follows="bottom" height="20" label="Cancel" label_selected="Cancel" layout="topleft" - right="-10" - left_pad="5" + left_delta="120" + top_delta="0" name="Cancel" width="100" /> + <check_box + follows="left|bottom" + height="20" + initial_value="true" + label="Apply now" + layout="topleft" + left="6" + name="apply_immediate_check" + top_delta="0" + width="120" /> </floater> |